HTTP APIs · beginner
API 429 burst limit: map caller rate to the provider's response window
When an HTTP API returns 429, the failure is almost never "the API is down" — it is a burst-window rejection. This guide shows how to map the caller's actual request emission rate onto the provider's short-term rate policy so that the 429s stop being mysterious. The argument: a 429 only makes sense once you can read the provider's window, the requester's rhythm, and the gap between them.
The symptoms
- •Clients receive HTTP 429 Too Many Requests responses clustered around predictable wall-clock intervals rather than spread evenly across time.
- •Workloads that look modest on a per-second average still trip the limit, because emissions are bursty (cron ticks, retry storms, parallel fan-out, queue flushes).
- •Endpoints that are nominally "read" or "metadata" return 429 under load, even though the provider documents the path as cheap or uncounted.
- •Retry-after or rate-limit hints are ignored or treated as advisory, so the same caller keeps producing new 429s each window.
- •429s appear only on a subset of endpoints, regions, or API keys, indicating the cap is per-resource, not global.
Likely causes
- •The caller's peak burst rate exceeds the provider's short-window cap, even though the long-window average stays under any documented quota.
- •Multiple workers, replicas, or browser tabs share one API key or token and collectively burst past a per-credential ceiling.
- •A retry loop re-emits the failed request faster than the rate-limit window resets, compounding the burst instead of relieving it.
- •The provider counts a "request" differently from the caller: sub-requests, prefetches, secondary API calls, or websocket frames may all be billed toward the same window.
- •Clock drift between the caller and the provider's window edge causes synchronized emissions to land on the worst possible 100 ms or 1 s slice.
- •A caching layer or client SDK transparently retries on idempotent reads, silently inflating the real emission rate above what the application code emits.
First ten minutes
- 01Confirm the status code is exactly 429 and capture the response headers verbatim, including any RateLimit-* / Retry-After / X-RateLimit-* headers the provider sends.
- 02Record the timestamp of each 429 with sub-second precision so you can see whether rejections cluster at window edges or spread evenly.
- 03Identify the provider's documented rate-limit window (per-second, per-minute, per-IP, per-token) from its API reference, not from memory.
- 04Count the caller's emissions in the same window the provider documents, separating reads from writes and per-endpoint paths if the cap is scoped that way.
- 05Decide whether the 429s come from a single caller, a fleet sharing one credential, or a fan-out trigger such as a cron, retry, or queue drain.
- 06Check whether the client is honoring the Retry-After hint, ignoring it, or retrying faster than the window resets.
Evidence to collect
- •Full 429 response including all RateLimit, X-RateLimit, Retry-After, and any provider-specific quota headers, with timestamps.
- •Server-side access logs filtered to the affected credential, region, or endpoint, showing request arrival times relative to the window.
- •Application-side emit timestamps for the same window, ideally from a metrics counter that distinguishes 2xx, 429, and 5xx outcomes.
- •Provider documentation excerpts naming the exact window length, the resource the cap is keyed on, and how partial windows are counted.
- •Inventory of all callers, SDKs, sidecar proxies, and retry layers that can emit requests under the same identity.
- •Clock-skew evidence between caller and provider, since aligned ticks make bursts look larger than they are.
Where to look
- •At the provider boundary: response headers on the 429 itself, plus the API reference page that defines the rate-limit policy and its window.
- •At the client SDK boundary: default retry policy, idempotency handling, and any silent request fan-out (prefetch, list pagination, conditional GETs).
- •At the application boundary: emit-side counters per endpoint and per credential, not aggregate request volume, since the cap is rarely global.
- •At the orchestration boundary: cron schedules, queue consumer concurrency, worker replica counts, and any "drain the backlog" code path that fans out under pressure.
- •At the network boundary: connection reuse vs. new connections, HTTP/2 stream multiplexing, and any proxy or API gateway that buffers or replays requests.
Diagnostic steps
- 01Plot the 429 timestamps on a timeline and mark the provider's documented window boundaries; if 429s cluster at the start of each window, the caller is bursting past the cap each cycle.
- 02Compute peak emissions in the smallest window the provider documents (e.g., requests per 1 s, 10 s, or 60 s) and compare against the stated ceiling, not the long-term average.
- 03Bucket the 429s by credential, region, and endpoint to determine whether the cap is per-key, per-route, or per-IP, since the fix differs for each scope.
- 04Diff the response headers of the 429 against a successful 200 response from the same endpoint to see which counters the provider exposes (remaining, reset, policy).
- 05Replay a synthetic, paced request stream from a controlled caller and observe whether 429s appear at the same wall-clock points, which isolates provider behavior from application behavior.
- 06Inspect the client retry path: confirm whether retries are exponential, jittered, and gated on the Retry-After value, or whether they re-fire inside the same window.
- 07Check for hidden emitters: health checks, readiness probes, metrics scrapes, and SDK prefetches often count toward the same window as user-driven calls.
Common mistakes
- •Trusting a per-minute average and ignoring per-second peaks, which is exactly what a burst-limit policy is designed to catch.
- •Treating Retry-After as a suggestion and retrying immediately, which lands the retry inside the same window the provider just closed.
- •Sharing one API key across many replicas or workers and reasoning about per-instance rate, while the provider caps the credential.
- •Assuming all endpoints share one budget when the provider scopes the cap per-endpoint or per-method, so "cheap" calls still get rejected.
- •Reading 429 as a server health problem and paging on-call, when the fix is pacing the client, not restarting the service.
- •Silently downgrading to a slower path on 429 without recording the trigger, which masks the real burst and prevents a proper pacing fix.
Safe fixes
- •If the provider exposes RateLimit-* headers, have the client honor them as the source of truth for remaining budget and reset time before emitting the next request.
- •Introduce a token-bucket or leaky-bucket scheduler on the emit side, sized to the provider's documented per-window cap, and cap concurrency so the bucket cannot be overdrawn in parallel.
- •Spread scheduled work (cron, queue drain, backfill) with jitter so emissions do not align on the provider's window boundary; aim for sub-window granularity, not just sub-minute.
- •Shard the workload across additional credentials, regions, or tenants when the provider caps per-identity, rather than retrying harder on the existing one.
- •Replace immediate retries with exponential backoff plus jitter, gated on the Retry-After value, and bound total retries so a partial outage does not turn into a sustained burst.
- •Disable transparent SDK prefetch, speculative retries, or silent list-paging fan-out when those paths are not strictly necessary for the user-visible behavior.
Prove the fix
- 01429 rate on the affected endpoint and credential drops to zero across at least one full provider window, with emit-side pacing metrics showing peak rate stays under the documented cap.
- 02RateLimit-* headers from the provider indicate non-zero remaining budget during the same window the workload is running, confirming the client is reading the window correctly.
- 03Retry-After is honored: a synthetic retry storm respects the header and the second attempt does not produce another 429 inside the same window.
- 04Burst test: a controlled fan-out at the previous peak rate no longer produces 429s, while a deliberately higher rate still does, proving the cap is the boundary and the fix is the limiter.
- 05Jitter check: scheduled jobs no longer align their emits on the provider's window edge, visible as a flatter emit histogram rather than synchronized spikes.
Prevention and next steps
- •Encode the provider's documented rate-limit window as a first-class configuration value, not as a comment, and validate emit code against it in CI where possible.
- •Export per-credential and per-endpoint emit counters so a burst shows up before users see 429s, and alert on a budget burn rate rather than only on raw 429 counts.
- •Default client retries to jittered exponential backoff bounded by the window length, and require an explicit override for tighter loops.
- •Review any new fan-out path (cron, queue, backfill, prefetch) for its worst-case peak rate before deploy, and cap concurrency at the call site.
- •Periodically re-read the provider's rate-limit documentation, since window length, scope, and counter semantics change without a major version bump.
Safe commands and checks
awk '$9==429{print $4}' /var/log/app/access.log | head -n 50 # show timestamps of recent 429 responses from a caller-side access log
grep -E 'RateLimit-|X-RateLimit-|Retry-After' /var/log/app/access.log | head -n 20 # extract rate-limit response headers from the access log to compare against provider docs
awk '$9==429{print $7}' /var/log/app/access.log | sort | uniq -c | sort -rn | head -n 20 # count 429s per endpoint path to identify which routes the cap actually applies to
awk '$9==429{print $11}' /var/log/app/access.log | sort | uniq -c | sort -rn | head -n 20 # count 429s per credential or API key id, placeholder <credential-field> is the logged token alias