A 429 means the server understood your request, would have been happy to serve it, and is refusing because you have made too many too quickly. It is not a failure — it is a scheduling instruction, and the correct response is to slow down rather than to retry harder.
Which is exactly what a lot of client code does not do. The common pattern is a retry loop with no delay, which turns a temporary throttle into a sustained hammering, gets the client blocked outright, and occasionally takes down the service that was trying to protect itself. Here is how to be a good client, and how to rate limit your own API without producing the same problem for other people.
Table of contents
- What the response should tell you
- Backing off correctly
- Not getting rate limited in the first place
- Rate limiting your own API properly
- How this fits the rest of the stack
- FAQ
What the response should tell you
A well-implemented 429 carries the information a client needs to recover. Read it rather than guessing.
Retry-After— the single most important header. Either a number of seconds or an HTTP date. If it is present, wait that long. Do not calculate your own delay when the server has told you the answer.X-RateLimit-Limit— how many requests the window allows.X-RateLimit-Remaining— how many you have left in the current window.X-RateLimit-Reset— when the window resets, usually a Unix timestamp.
The X-RateLimit-* family is a de facto convention rather than a standard, so exact names vary and some APIs use a RateLimit-* form without the prefix. There is a standards-track effort to unify this; in the meantime, read the API’s documentation once and handle what it actually sends.
The remaining counter is the more valuable one, because it lets you avoid 429s entirely. A client that watches Remaining and slows down as it approaches zero never gets throttled. A client that ignores it and reacts only to 429s is always operating at the boundary.
One trap: Retry-After may be a date rather than a number, and a client that assumes seconds will parse it as zero and retry immediately. Handle both forms.
Backing off correctly
When there is no Retry-After, the client has to choose a delay, and the correct algorithm is exponential backoff with jitter.
Exponential means each successive retry waits roughly twice as long — one second, two, four, eight — up to a ceiling. This gives a temporarily overloaded service room to recover instead of receiving the same load again immediately.
Jitter means adding randomness to that delay, and it is the part people leave out. Without it, every client that got throttled at the same moment retries at the same moment, producing a synchronised thundering herd that re-triggers the limit. With jitter, retries spread out and the load smooths.
The full policy, worth implementing once in a shared client rather than per call site:
- Respect
Retry-Afterwhen present; otherwise exponential backoff with full jitter. - Cap the maximum delay — a minute or so — so a retry does not sit for an hour.
- Cap total attempts, then fail with a clear error rather than retrying forever.
- Retry 429, 502, 503 and 504. Do not retry 400, 401, 403 or 404 — those will not become correct by being repeated.
- Be careful retrying non-idempotent requests. A POST that timed out may have succeeded; use an idempotency key so a retry cannot create a duplicate.
A circuit breaker is the next layer: after repeated failures, stop attempting for a while entirely, then probe with a single request before resuming. It protects both the upstream service and your own resources, which otherwise sit blocked in retry loops.
Not getting rate limited in the first place
Backoff is the recovery path. The better position is not needing it, and the techniques are ordinary.
- Cache. The cheapest request is the one you do not make. Cache responses per their headers, and cache aggressively for data that changes slowly. A great many rate-limit problems are the same resource fetched repeatedly within seconds.
- Batch. Many APIs offer endpoints that accept multiple identifiers in one call. One request for fifty records instead of fifty requests is a fifty-fold reduction and usually faster too.
- Use webhooks instead of polling. Polling every thirty seconds to see whether something changed is the archetypal rate-limit consumer. If the service can call you when something changes, that is both cheaper and more responsive.
- Queue and pace. Put outbound calls through a queue with a rate limiter set just below the published limit. This converts unpredictable bursts into a steady rate that never trips a throttle.
- Spread scheduled work. Everyone’s cron runs at the top of the hour. Offset yours by a random number of minutes and you avoid competing with everyone else for the same window.
The queue approach is the most robust because it makes rate compliance a property of the system rather than a behaviour each caller has to remember. One shared limiter that all outbound calls pass through is much easier to reason about than backoff logic scattered across a codebase.
Rate limiting your own API properly
On the other side, if you are protecting a service, the implementation details determine whether legitimate users notice.
Choose the algorithm deliberately. A fixed window counter is simplest and allows a burst of double the limit at a window boundary. A sliding window fixes that at slightly more cost. A token bucket allows controlled bursting while enforcing an average rate, which is usually closest to what people actually want.
Choose the key deliberately. Per-IP is the obvious choice and it punishes everyone behind a shared address — an office, a mobile carrier, a proxy. Per-API-key or per-account is fairer and requires authentication. Most services need both: per-account for authenticated traffic, per-IP for anonymous.
Behind a proxy, use the real client address, taken from the forwarded header, not the connecting address. Otherwise you are rate limiting the proxy, which means rate limiting your entire user base. This is a genuinely common outage cause.
Always send Retry-After, and send the limit headers. A 429 with no guidance forces every client into guessing, and their guesses will be worse than your answer.
Do not rate limit health checks or your own monitoring, and be careful about search engine crawlers — throttling those affects how often your content is indexed.
Log throttling events. A sudden rise in 429s is either an abusive client or a legitimate integration that grew, and those need different responses. You cannot tell which without the data.
How this fits the rest of the stack
Rate limiting exists because capacity is finite, which makes it partly a capacity question: how much a service can absorb before it needs to say no. The RunxBuild hosting calculator lets you model the service, database and bandwidth so the headroom is a number rather than a hope. RunxBuild autoscales web services between a floor and a ceiling plan you set — up around 80% CPU, back down at 20% — which raises the point at which throttling becomes necessary while keeping the ceiling a figure you chose in advance.
Useful related references:
- Too Many Requests Error: Reading a 429 and Backing Off Properly
- Math Domain Error Explained: Meaning and How to Debug
- DevOps Meaning: What It Was, What It Became, What to Do
- Services on RunxBuild
FAQ
What does HTTP 429 mean?
Too Many Requests — the server understood your request and is refusing it because you have sent too many in a given period. It is a throttle rather than a failure, and the correct response is to slow down. Retrying immediately typically makes it worse and can get the client blocked entirely.
How long should I wait after a 429?
If the response includes a Retry-After header, wait exactly that long — it may be a number of seconds or an HTTP date, so handle both. Without it, use exponential backoff with jitter: roughly doubling delays, randomised, up to a capped maximum and a capped number of attempts.
What is jitter and why does it matter?
Randomness added to retry delays. Without it, every client throttled at the same moment retries at the same moment, producing a synchronised burst that re-triggers the limit. Jitter spreads retries out so load smooths instead of oscillating.
How do I avoid getting rate limited?
Cache responses, batch requests where the API supports it, use webhooks instead of polling, route outbound calls through a queue with a limiter set just below the published limit, and offset scheduled jobs off the top of the hour. The queue approach is most robust because compliance becomes a system property rather than per-caller discipline.
What is the best way to rate limit my own API?
A token bucket keyed on account for authenticated traffic and on IP for anonymous, using the real client address from the forwarded header rather than the connecting address when behind a proxy. Always send Retry-After and the limit headers, exempt health checks and monitoring, and log throttling events so you can tell abuse from growth.