HTTP 429 Too Many Requests — What It Means and How to Fix It Properly
Key takeaways
- 429 Too Many Requests is the standard HTTP status for rate limiting, defined in RFC 6585 and returned by the origin server — the application itself decided you exceeded its quota.
- Cloudflare Error 1015 is the edge equivalent: the request is stopped before the origin sees it. Same symptom, different owner, different fix.
- Honouring
Retry-Afteris the single most effective change you can make. It is the server telling you exactly when to come back, and ignoring it is how a short throttle becomes a ban. Retry-Aftercomes in two formats — delay seconds (Retry-After: 120) or an HTTP-date (Retry-After: Wed, 29 Jul 2026 08:00:00 GMT). A client that only parses integers will crash or misbehave on the second.- Exponential backoff without jitter causes retry storms, because every client that failed together retries together. Randomising the wait is what breaks the synchronisation.
- The right backoff depends on the algorithm: fixed windows reset at a clock boundary, sliding windows reset gradually, and token buckets refill continuously and tolerate short bursts.
- If the client is already correct and 429s persist, per-IP quotas mean the address is the constraint — a different problem from a per-key or per-account quota, which more addresses will never solve.
HTTP 429 Too Many Requests is the internet's standard way of saying slow down. It is defined in RFC 6585, it is deliberately temporary, and — unlike most errors you will chase — the server usually tells you exactly how to fix it in a header most clients throw away.
This page covers what a 429 is, how it differs from the Cloudflare error people confuse it with, how to read Retry-After correctly in both of its formats, and how to write backoff that matches the rate limit algorithm you actually hit.
What a 429 actually is, and who sent it
A 429 is returned by the origin server — the application you are calling — after its own rate limiter decided your requests exceeded a quota. The request travelled all the way to the server, was authenticated or identified, was counted, and was then rejected. It is a temporary condition by definition, not an access decision.
That "who sent it" part is what separates 429 from its lookalikes, and getting it wrong sends you debugging the wrong system entirely:
| Response | Enforced by | Expires on its own? | What it tells you |
|---|---|---|---|
| HTTP 429 | The origin application's limiter | Yes, per Retry-After or the quota window | You exceeded a documented or undocumented quota |
| Cloudflare 1015 | Cloudflare's edge, before the origin | Yes, after the operator's timeout | You tripped an edge rate limiting rule |
| Cloudflare 1020 | Cloudflare's edge firewall | No | Your request matched a block rule |
| HTTP 403 | Origin or edge | No | Access refused — usually auth or policy |
HTTP 503 + Retry-After | Origin | Yes | Server overloaded, not necessarily your fault |
The practical consequence: if you are getting a 429, the API developer can raise your quota. If you are getting a 1015, they may not even know, because the rule lives in Cloudflare rather than in their code. Sending a bug report to the wrong party is a common way to lose a week.
Read Retry-After — it is the whole fix
If the response carries a Retry-After header, respecting it resolves the majority of 429 problems on its own. The server has told you when it will accept you again. Nothing you invent — no backoff curve, no rotation strategy, no delay you guessed — will be more accurate than the number the limiter itself produced.
The trap is that Retry-After has two legal formats, and a client that only handles one will fail on the other:
Retry-After: 120
Retry-After: Wed, 29 Jul 2026 08:00:00 GMT
The first is delay-seconds. The second is an HTTP-date. Both are valid per the specification, and different APIs choose differently — so int(response.headers["Retry-After"]) is a bug waiting to happen.
import email.utils, time
def retry_after_seconds(resp, default=60, cap=300):
"""Parse Retry-After in either legal format. Returns seconds to sleep."""
raw = resp.headers.get("Retry-After")
if not raw:
return default
raw = raw.strip()
if raw.isdigit():
return min(int(raw), cap)
parsed = email.utils.parsedate_to_datetime(raw) # HTTP-date form
if parsed is None:
return default
return max(0, min(parsed.timestamp() - time.time(), cap))
Two details worth keeping. Cap it — a hostile or misconfigured server can send a very large value, and an uncapped sleep hangs your job. Have a default — plenty of APIs return 429 with no Retry-After at all, and that is where backoff takes over.
Also check for the sibling headers many APIs send: X-RateLimit-Limit, X-RateLimit-Remaining and X-RateLimit-Reset, or the newer standard RateLimit header field. Reading Remaining and slowing down before you hit zero is strictly better than reacting after the fact.
Exponential backoff with jitter
When there is no Retry-After, you back off: wait, retry, and wait longer each time it fails again. Exponential backoff alone is not enough, though, because every client that got limited at the same moment will also retry at the same moment — and the synchronised second wave is often worse than the first.
Jitter is the fix. Randomising each wait spreads the retries out so they stop arriving in lockstep.
import random, time
import requests
def get_with_backoff(url, session, max_attempts=6, base=1.0, cap=64.0):
for attempt in range(max_attempts):
resp = session.get(url, timeout=30)
if resp.status_code != 429:
return resp
# Server's instruction always wins over our guess.
wait = retry_after_seconds(resp, default=None)
if wait is None:
# Full jitter: uniform over [0, exponential ceiling]
ceiling = min(cap, base * (2 ** attempt))
wait = random.uniform(0, ceiling)
time.sleep(wait)
raise RuntimeError(f"still rate limited after {max_attempts} attempts: {url}")
Three rules this encodes, and they matter more than the exact numbers:
Retry-Afteroverrides your curve. Always. Your exponential is a fallback for when the server said nothing.- Cap the attempts. Infinite retry hides a capacity problem and keeps load on a server that already asked you to stop.
- Use full jitter, not "exponential plus a small random." Spreading uniformly across the whole interval de-synchronises clients far more effectively than nudging each one by a fraction of a second.
Which rate limit did you hit? It changes your backoff
Rate limiters are not all the same algorithm, and the right waiting strategy differs between them. You can usually identify which one you are behind from how the 429s arrive, and that tells you whether to wait for a boundary or just slow your average pace.
| Algorithm | How it behaves | Retry strategy that fits |
|---|---|---|
| Fixed window | Counter resets at a clock boundary (top of the minute, hour) | Wait for the boundary. X-RateLimit-Reset usually names it. Retrying before it always fails |
| Sliding window | The window moves continuously, so capacity returns gradually | Gentle exponential backoff. Capacity is trickling back, so short waits do help |
| Token bucket | Tokens refill at a steady rate; unused ones accumulate up to a burst limit | Slow your average rate below the refill rate. Bursts are allowed; sustained overspend is not |
| Leaky bucket | Requests drain at a fixed rate; overflow is rejected | Pace requests evenly. Bursts are punished even when the average is fine |
| Concurrency limit | Caps simultaneous in-flight requests, not requests per second | Reduce worker count. Adding delay between requests will not help |
The most common misdiagnosis in this table is the last row. A team adds a one-second sleep between requests, keeps twenty parallel workers, and is baffled that nothing improved — because the limit was never about pace. It was about how many requests were open at once. The same trap catches people fighting Cloudflare Error 1015: concurrency is what edge limiters count, not the delay inside each worker.
The fixed window row explains a symptom people find spooky: everything works fine for fifty seconds, then dies, then recovers precisely on the minute. That is a fixed-window counter, and the only correct response is to wait for the reset.
What real rate limits tend to look like
Published limits vary enormously between providers and tiers, so treat any specific number you read online as a guess about someone else's account. What is stable is the shape of the limits, and recognising the shape tells you what to instrument.
- Free and trial tiers are typically low and often expressed per minute, with a hard daily ceiling on top. Two limits stack, so passing the per-minute check does not mean you will finish the day.
- Paid tiers commonly move to per-second or per-key quotas with a burst allowance, which is a token bucket in practice.
- Authenticated APIs almost always key on the API key or account, not the IP. This is the single most important thing to check before assuming a network fix.
- Unauthenticated and public endpoints usually key on the IP, which is why the same endpoint behaves differently signed in and signed out — and why this is the one category where the type of address you leave from changes anything.
- Write endpoints are frequently limited far more strictly than reads, sometimes by an order of magnitude.
- Search and export endpoints often have their own separate, much lower budget that is invisible until you touch it.
Find the provider's documentation, then verify it against the response headers, because gateways in front of an API sometimes enforce stricter limits than the API's own docs describe.
When this becomes an infrastructure problem
There is a real point at which client-side work stops paying. You are honouring Retry-After, your concurrency is tuned, you cache aggressively, you use conditional requests, and you are still limited. At that point the question is only: what is the quota keyed to?
- Keyed to your API key or account — more IP addresses do nothing. Every request carries the key, and the key is what is counted. Your options are a higher tier, more keys if the provider's terms allow it, or less work.
- Keyed to your IP address — the address is now the constraint, and this is the only case where network changes help.
Do that check first. It is the difference between solving the problem and buying infrastructure that cannot possibly affect it.
Where the limit genuinely is per-IP, address type matters, because reputation scoring runs before quota counting on many platforms:
| IP type | Treatment under per-IP limits | Why |
|---|---|---|
| Datacenter | Often given a lower quota, or blocked outright | Hosting ranges are published and easy to score as non-human |
| Commercial VPN | Frequently shares a quota with strangers | Exits are enumerable and heavily shared |
| Shared residential pool | Quota is effectively pooled with other customers | You inherit whatever the previous user spent |
| Mobile carrier (4G/5G) | Usually treated like ordinary consumer traffic | Carrier CGNAT puts real phone users behind the same address |
That last row is the mechanism behind mobile proxies as a category, covered properly in what is a mobile proxy. Whether you want one stable address or a rotating set depends on whether your workload is session-based, which is the subject of dedicated vs rotating proxies.
And the honest limits, because this is where the category oversells itself:
A different IP does not raise a quota that was never about IPs. Most 429s from authenticated APIs are per-key. No proxy changes that, and any vendor implying otherwise is selling you something that cannot work.
A mobile IP does not make you undetectable. It alters one input to a scoring system that also weighs request pattern, session continuity, header consistency and TLS fingerprint. The same caveat applies to Cloudflare 1020 and to YouTube's bot prompt.
A shared mobile pool reintroduces pooled quota. If other customers use the same addresses, their consumption counts against you exactly as it would on a shared residential pool. A dedicated device with a single tenant is what removes that variable. QuantumProxy sells that specific shape — real 4G/5G devices on US carrier SIMs, one tenant per device, rotation on demand — but the reasoning applies whoever you buy from.
The check to run before anything else
One test tells you which half of the problem you are in, and it takes about a minute:
Make the same request from a different IP address, with the same credentials. Then make it from the original IP, with different credentials.
If the first one still 429s, the quota is keyed to your account or key, and no network change will ever help. If the second one succeeds, the quota is keyed to the IP, and the address is a genuine constraint worth engineering around.
Run that before you write a rotation layer. It is the cheapest test on this page and it is the one that most often stops a project spending money on the wrong thing.
Frequently asked questions
What is the difference between HTTP 429 and Cloudflare Error 1015?
A 429 comes from the origin application's own limiter, so the request reached the server and the server chose to reject it. Cloudflare Error 1015 is enforced at the edge by a rate limiting rule the site owner configured, so the origin never saw the request at all. The symptom looks identical from the outside, but only one of them is something the application developer can change.
How long should I wait after a 429?
Exactly as long as Retry-After says, if the header is present. If it is absent, start at one or two seconds and double on each subsequent failure, with random jitter added, up to a sensible ceiling. Never retry immediately — an immediate retry is what turns a brief throttle into an extended block on many implementations.
Does a 429 mean I am banned?
No. 429 is explicitly temporary — the specification describes it as the client having sent too many requests in a given time. A ban is normally a 403, or on Cloudflare an Error 1020. If waiting and slowing down restores access, it was a rate limit working as designed.
Why do I get 429 even though I am under the documented limit?
Several reasons are common: the limit is per IP and something else shares your address, a burst inside the window exceeded a per-second cap even though the per-minute total was fine, retries from earlier failures are counted, or a shared gateway in front of the API applies a stricter limit than the API documents.
Will more IP addresses fix my 429s?
Only if the quota is keyed to the IP. If the limit is enforced per API key or per account — which is normal for authenticated APIs — every address in the world will hit the same wall, because the key is what is being counted. Check which one you are dealing with before spending anything.
Should I retry a 429 forever?
No. Cap both the number of attempts and the total backoff, then fail loudly and surface it. A client that retries indefinitely hides a real capacity problem, keeps the server under load, and on many platforms escalates from a temporary throttle to a firewall-level block.
Related reading
- Cloudflare Error 1015 (You Are Being Rate Limited): causes and fixes
- Cloudflare Error 1020 (Access Denied): what actually triggers it
- YouTube "Sign in to confirm you're not a bot": causes and fixes
- What is a mobile proxy, and when do you actually need one?
- Dedicated vs rotating proxies: which survives detection?
Dedicated mobile proxies, one dashboard
Real 4G/5G devices on US carrier SIMs. Sticky IP per customer, rotation on demand.
See plans