Skip to main content
TF
By Rohit V.8 min readArticle

API Rate Limiting: 429 and Retry-After Explained

TF
ToolsFuel Team
Web development tools & tips
Network equipment with status lights

Photo by Unsplash on Unsplash

What a 429 is telling you

> Quick answer: HTTP 429 Too Many Requests means you've exceeded a rate limit and should slow down. If the response carries a `Retry-After` header, that value is authoritative and you should wait exactly that long. Otherwise use exponential backoff with jitter. Retrying a 429 immediately, or in a tight loop, is how a temporary limit turns into a ban.

The important thing about a 429 is that it's not a rejection of your request. It's a rejection of your *timing*.


That distinction matters because it changes the correct response. A 400 means the request was wrong and retrying it unchanged is pointless. A 500 means something broke on their end and a retry might work. A 429 means the request was perfectly fine and you simply asked too soon.


I've seen more than one incident where a client treated 429 as a hard failure, gave up, and surfaced an error to users, when waiting two seconds would have worked. And I've seen the opposite, which is worse: a retry loop with no delay, hammering an endpoint that was already asking for mercy.


MDN's
429 status reference is the short version of the semantics. What follows is what you actually do about it.

The algorithms behind the limit

Understanding which algorithm an API uses tells you how it will behave when you push, which is useful when you're trying to go as fast as you're allowed.

Fixed window is the simplest. Count requests per calendar minute, reset at the boundary. Easy to implement and easy to abuse: send your full quota at 10:00:59 and your full quota again at 10:01:00, and you've doubled the intended rate in two seconds. That burst is exactly what breaks backends.

Sliding window fixes the boundary problem by counting over the trailing period rather than a fixed block. More accurate, slightly more expensive to track.

Token bucket is the one most good APIs use. Picture a bucket holding a maximum number of tokens, refilled at a steady rate. Each request removes one token. Empty bucket means 429. The elegance is that it permits bursts up to the bucket size while enforcing an average rate over time, which matches how real clients behave. An app that's idle for a minute and then makes ten calls at once is normal, not abusive, and token bucket handles that gracefully.

Leaky bucket inverts it: requests queue and drain at a constant rate. It smooths output completely, which suits protecting a fragile downstream system, at the cost of added latency.

Knowing you're against a token bucket is genuinely actionable. It means a short burst after idle time is fine, and sustained hammering is not, so you can shape your traffic to match instead of guessing.


You can usually infer which one you're facing without documentation. Send requests steadily and watch when the 429 arrives. If it lands abruptly at a round number and then clears exactly on the minute, that's a fixed window. If you get a generous burst and then a steady trickle of acceptances, that's a bucket refilling. I've worked this out empirically more than once on APIs whose docs said nothing useful, and it took about ten minutes of poking with a loop and a timestamp log.

Reading the headers

Most APIs tell you where you stand, if you bother to read the response headers. The naming is frustratingly inconsistent.

The common set looks like:


``` RateLimit-Limit: 100 RateLimit-Remaining: 12 RateLimit-Reset: 1754563200 Retry-After: 30 ```


`RateLimit-Limit` is your quota for the window. `RateLimit-Remaining` is what's left. `RateLimit-Reset` is when it refills, and here's a trap: some APIs give a Unix timestamp, others give seconds from now. A 1754563200 is obviously a timestamp; a 30 is obviously a duration. Guess wrong and you either retry instantly or wait until 2055.


Plenty of APIs use `X-RateLimit-` prefixed versions of the same thing. GitHub, Twitter and Stripe have all used slightly different spellings over the years. Read the docs, don't assume.


`Retry-After` is the one that matters most, and it appears on 429 and 503 responses. It's either a number of seconds or an HTTP date. When it's present, use it. It's the server telling you precisely when it will accept you again, which beats any backoff algorithm you could invent.


The habit worth building: don't wait for a 429 at all. Watch `RateLimit-Remaining` on every response and slow yourself down as it approaches zero. Reacting to a 429 is damage control. Reading the remaining count is flow control, and it keeps you off the limiter entirely. The broader
HTTP status codes rundown covers where 429 sits among its neighbours.

Backoff that doesn't make things worse

When there's no `Retry-After`, you back off. The standard approach is exponential: wait 1 second, then 2, then 4, then 8, capped at some ceiling.

Exponential alone has a nasty failure mode though. If a thousand clients all get a 429 at the same moment, they all wait exactly 1 second, then all retry at exactly the same instant. You've synchronised your entire client fleet into a thundering herd that arrives together, gets 429'd together, and repeats. The retries become the outage.


The fix is jitter, meaning randomness added to the delay:


```js const base = 1000; // 1s const cap = 30000; // 30s function delay(attempt) { const exp = Math.min(cap, base * 2 ** attempt); return Math.random() * exp; // full jitter } ```


That's "full jitter", picking uniformly between zero and the exponential value. It spreads retries across the window and it consistently outperforms the more intuitive "exponential plus a small random nudge" in published testing on this. Counterintuitive but well established.


Three more rules I'd treat as non-negotiable.


Cap the number of attempts. Infinite retry is not resilience, it's a denial of service you wrote yourself against a partner's API.


Cap the delay. Nobody benefits from a retry scheduled seventeen minutes out; fail the operation and let the caller decide.


Only retry what's safe to retry. A GET is fine. A POST that creates something needs an idempotency key first, or your retry creates a duplicate, which is the exact problem covered in the
idempotency guide.

Rate limiting your own API

Flip to the other side. If you're the one imposing limits, a few things separate a good limiter from an annoying one.

Return the headers. An API that 429s without telling clients the limit, the remaining count or when to retry forces every integrator to guess, and they will guess badly. This costs you nothing and saves your support inbox.


Always send `Retry-After` on a 429. It's the single most useful thing you can give a client.


Limit per identity, not per IP, wherever you can. IP based limiting punishes everyone behind a corporate NAT or a mobile carrier gateway. Key off the API key or account ID.


Set different limits for different costs. A cheap read and an expensive report generation shouldn't share a bucket. Weighted token consumption, where a heavy endpoint costs five tokens, is more honest than one flat number.


Return 429 rather than 503. A 503 suggests your service is unhealthy, which changes how clients and monitoring systems react. You're healthy, you're just declining this request.


And decide deliberately what happens at the boundary. Rejecting is the honest option. Queueing feels kinder but hides latency and can push memory pressure onto you. I'd reject and be clear about it, which is the same instinct behind returning a real error rather than a silent partial response in a
webhook delivery, where a caller genuinely needs to know whether to try again.

Frequently Asked Questions

What does HTTP 429 mean?

Too Many Requests. You've exceeded a rate limit and should slow down and retry later. It isn't a rejection of the request itself, only of the timing, so the same request will usually succeed once you wait. Treating it as a permanent failure is a common client bug.

How long should I wait after a 429?

If the response has a Retry-After header, wait exactly that long, because the server is telling you when it will accept you again. Without one, use exponential backoff with jitter, capping both the delay and the number of attempts. Never retry immediately in a loop.

What is the Retry-After header?

A response header giving either a number of seconds or an HTTP date indicating when to try again. It appears on 429 and 503 responses. When present it's authoritative and beats any backoff calculation you'd do yourself, so always check for it before falling back to your own algorithm.

What is jitter in retry logic?

Randomness added to a backoff delay so that clients hitting a limit at the same moment don't all retry at the same moment. Without it, a fleet of clients synchronises into a thundering herd that keeps re-triggering the limit. Full jitter, picking randomly between zero and the exponential delay, works well in practice.

Should I use 429 or 503 for rate limiting?

Use 429. A 503 signals your service is unhealthy or unavailable, which is misleading when you're healthy and simply declining a request. It also causes monitoring and client libraries to react differently. Pair the 429 with Retry-After and rate limit headers, and see the [status code guide](/blog/http-status-codes-explained-200-301-404-500) for how the families differ.

Is it safe to retry any request that returns 429?

Only if the operation is safe to repeat. GET requests are fine. A POST that creates a resource can produce duplicates when retried, so it needs an idempotency key first, as covered in the [idempotency guide](/blog/what-is-idempotency-in-apis-explained). The 429 tells you the request wasn't processed, but you can't always be certain of that during a timeout.

Try ToolsFuel

23+ free online tools for developers, designers, and everyone. No signup required.

Browse All Tools