Quick answer: When 429 responses arrive in a burst, the first thing to check is the client's retry behavior, not the server's limit. A burst usually means callers are retrying at the same instant and amplifying the load. Read the Retry-After header, wait that long before the next attempt, and use exponential backoff with jitter so retries spread out instead of landing together.
What a 429 burst actually is
A single 429 means one request crossed a rate or quota window. A burst means many callers hit the limit together, or one caller's retries are generating fresh traffic faster than the window can recover. The two feel identical from the client, but they have different fixes. Raising the server limit only helps if genuine demand exceeds it; if the burst is a retry storm, a higher limit just delays the next failure.
The signature is a wall of 429s clustered on the same second, often after one slow response or a brief outage. Every caller saw the failure, every caller retried, and the retries arrived together, which pushed the server back into the limit before the first retry even completed.
Read Retry-After correctly
Retry-After tells the client how long to hold off, and it arrives in one of two forms: a number of seconds, such as Retry-After: 30, or an HTTP date, such as Retry-After: Wed, 09 Nov 2026 10:00:00 GMT. A client that ignores the header and retries on a fixed timer is guessing, and the guess is usually shorter than the real window, so it burns another request against a server that is still rejecting calls.
Honor the header as the minimum wait, not a suggestion. If the header is absent, fall back to your own backoff rather than hammering the endpoint. Treat the value as applying to the same caller and the same resource; switching to a different endpoint or identity does not reset a shared limit.
How retries make the problem worse
Without jitter, identical clients compute the same backoff and wake up at the same instant. Ten callers that each wait one second do not produce ten evenly spaced retries; they produce a second wave of ten simultaneous requests, then a third. Adding jitter, a random fraction added to each delay, breaks the synchronization so retries scatter across the window.
Retries also need to respect the method. Repeating a safe, idempotent GET is low risk, but automatically retrying a non-idempotent POST can create duplicate orders or charges. Gate automatic retries behind idempotency, and for methods that are not safe to repeat, surface the failure and let the caller decide.
Symptoms, causes, and fixes
| Symptom | Likely cause | Fix |
|---|---|---|
| 429s clustered on the same second | Retries firing in lockstep | Add jitter to the backoff |
| One slow response triggers a flood | Clients retry on tight fixed timers | Honor Retry-After, widen the base delay |
| Burst repeats on a steady cycle | Backoff does not grow per attempt | Use exponential backoff with a cap |
| Duplicate writes appear after 429 | Retrying non-idempotent POSTs | Limit auto-retry to idempotent calls |
| Burst only under parallel load | All workers share one quota | Stagger workers and share the rate budget |
| Header present but ignored | Client parses only one Retry-After form | Parse both delta-seconds and HTTP-date |
Reproduce it with a scenario, not a hunch
The reliable way to confirm the fix is to drive the real behavior. Build a scenario that sends the request, then asserts on the status and headers at each step so a 429 with a Retry-After value is observed rather than assumed. Run the same sequence with parallel workers to see whether retries synchronize, and confirm that after the fix the attempts spread across the window and eventually return 200.
For offline work on the retry logic, a local mock can return 429 with a chosen Retry-After on the first call and 200 after the wait, while recording the requests it receives, so you can read the actual retry timing without touching the live service. The mock does not replace the real backend; once the client behavior is correct locally, send the real request to confirm the production limit and header behave the way the mock modeled.
Powerduck runs these ordered scenarios in the desktop main process with assertions on status, headers, and response fields, and the local mock can stand in for the rate-limited service while you tune the backoff. For more on this workflow, see the API debugging guide hub.
Honest limits
A passing scenario proves the client honored the header on the requests you sent; it does not prove every code path retries correctly, and it cannot predict how a shared quota behaves under other tenants. A mock that returns scripted 429s models your logic, not the real server's windowing, which is why the final check has to hit the live service. If a run cannot execute because a script is missing, treat that step as unverified rather than passed.
Frequently asked questions
Should I retry immediately on a 429? No. Wait at least the Retry-After value, or use exponential backoff with jitter when the header is missing. Immediate retries feed the burst.
What if Retry-After is missing? Apply your own growing backoff, cap the number of attempts, and stop retrying once the cap is reached so the client does not loop forever.
Is it safe to retry every method? No. Automatically repeat only safe, idempotent calls; for non-idempotent writes, return the error to the caller to avoid duplicate side effects.
How do I prove the fix works? Reproduce the burst with a scenario, assert on the 429 and Retry-After, then confirm the retries spread out and recover to 200, first against a local mock and then against the live service.
The bottom line
A 429 burst is more often a coordination failure on the client than a capacity problem on the server. Honor Retry-After, grow the delay with exponential backoff, add jitter so callers stop waking in unison, and keep automatic retries to idempotent methods. Prove the timing with a recorded scenario and a real request, and merge only once the retries scatter instead of stacking.