Summary
Handle API 429 Too Many Requests generically across the app and show a throttling overlay with a retry countdown, localized messaging, and automatic recovery back to the same screen.
Problem
Throttling can happen from many component-level API calls, not just app bootstrap. The frontend needs a generic handling path for 429 responses so users do not see crashes, broken views, or ambiguous messaging.
During testing, these issues came up:
- Initial handling only covered the
toggles() request on app load.
- Component requests could still receive the
429 in normal success handlers and crash if they assumed a successful payload shape.
- The throttling message sometimes showed a generic message instead of saying whether the minute or daily limit was exhausted.
- The cooldown flow should preserve the user’s place instead of replacing the current page with a separate error route.
- Some throttling messages were hardcoded instead of coming from translations.
Expected Behavior
- Detect
429 globally for normal API requests.
- Read
Retry-After and show a live countdown.
- Read
X-LimitRemaining-Minute and X-LimitRemaining-Day and show whether the exhausted limit is:
- Show throttling as an overlay so the current page stays mounted underneath.
- Keep the overlay semi-transparent so the user can still recognize the underlying screen.
- When the countdown ends, automatically dismiss the overlay and let the user continue where they left off.
- Show the “rate limit ended” message via i18n, not hardcoded text.
- Keep throttling strings localized in
en, es, and zh.
Implementation Notes
APIService now handles 429 generically and notifies the app through a shared throttle listener.
App subscribes once to the throttle event and renders a throttling overlay.
- The overlay uses
Retry-After for the ticking countdown.
- The overlay does not unmount the active route/component tree.
- On expiry, the overlay dismisses automatically and shows a localized success alert.
raw=true request behavior remains intact for callers that intentionally inspect raw error responses.
- Defensive guards were added so pages do not crash if a throttled or unexpected payload shape slips into page logic.
Files Touched
src/services/APIService.js
src/components/app/App.jsx
src/components/errors/ThrottlingError.jsx
src/components/map-projects/MapProjects.jsx
src/i18n/locales/en/translations.json
src/i18n/locales/es/translations.json
src/i18n/locales/zh/translations.json
API Dependency / Known Limitation
If the frontend still cannot determine whether the minute or day limit was exhausted, the browser is likely not receiving those headers in readable form.
The API response needs to expose:
Retry-After
X-LimitRemaining-Minute
X-LimitRemaining-Day
If requests are cross-origin, the API must include these in Access-Control-Expose-Headers.
Acceptance Criteria
- Any API call that returns
429 shows the throttling overlay.
- The countdown matches the
Retry-After value.
- The overlay message correctly says whether the minute, day, or both limits were exhausted.
- The current page remains mounted beneath the overlay.
- The overlay is translucent enough that the underlying page is visible.
- When the timer ends, the overlay disappears automatically.
- A localized “rate limit ended” message is shown.
- No component crashes when a
429 happens mid-flow.
en, es, and zh translations exist for the throttling flow.
Reproduction
- Open any screen that makes API requests.
- Force the API to return
429.
- Confirm the overlay appears instead of the page crashing.
- Verify the countdown uses
Retry-After.
- Verify the message reflects minute/day exhaustion from the limit headers.
- Wait for the timer to reach zero.
- Confirm the overlay disappears and the user can continue on the same screen.
Screenshot:

Summary
Handle API
429 Too Many Requestsgenerically across the app and show a throttling overlay with a retry countdown, localized messaging, and automatic recovery back to the same screen.Problem
Throttling can happen from many component-level API calls, not just app bootstrap. The frontend needs a generic handling path for
429responses so users do not see crashes, broken views, or ambiguous messaging.During testing, these issues came up:
toggles()request on app load.429in normal success handlers and crash if they assumed a successful payload shape.Expected Behavior
429globally for normal API requests.Retry-Afterand show a live countdown.X-LimitRemaining-MinuteandX-LimitRemaining-Dayand show whether the exhausted limit is:en,es, andzh.Implementation Notes
APIServicenow handles429generically and notifies the app through a shared throttle listener.Appsubscribes once to the throttle event and renders a throttling overlay.Retry-Afterfor the ticking countdown.raw=truerequest behavior remains intact for callers that intentionally inspect raw error responses.Files Touched
src/services/APIService.jssrc/components/app/App.jsxsrc/components/errors/ThrottlingError.jsxsrc/components/map-projects/MapProjects.jsxsrc/i18n/locales/en/translations.jsonsrc/i18n/locales/es/translations.jsonsrc/i18n/locales/zh/translations.jsonAPI Dependency / Known Limitation
If the frontend still cannot determine whether the minute or day limit was exhausted, the browser is likely not receiving those headers in readable form.
The API response needs to expose:
Retry-AfterX-LimitRemaining-MinuteX-LimitRemaining-DayIf requests are cross-origin, the API must include these in
Access-Control-Expose-Headers.Acceptance Criteria
429shows the throttling overlay.Retry-Aftervalue.429happens mid-flow.en,es, andzhtranslations exist for the throttling flow.Reproduction
429.Retry-After.Screenshot: