Skip to content

Mapper handling API throttling #2483

@snyaggarwal

Description

@snyaggarwal

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:
    • minute
    • day
    • both
  • 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

  1. Open any screen that makes API requests.
  2. Force the API to return 429.
  3. Confirm the overlay appears instead of the page crashing.
  4. Verify the countdown uses Retry-After.
  5. Verify the message reflects minute/day exhaustion from the limit headers.
  6. Wait for the timer to reach zero.
  7. Confirm the overlay disappears and the user can continue on the same screen.

Screenshot:

Image Image

Metadata

Metadata

Assignees

Labels

signal/large-scopeAffects multiple areas or systemsstage/triagedAI triage complete — scored and classifiedtype/featureNew or improved functionality

Type

No type

Projects

Status

In review

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions