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-After is 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-After comes 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:

ResponseEnforced byExpires on its own?What it tells you
HTTP 429The origin application's limiterYes, per Retry-After or the quota windowYou exceeded a documented or undocumented quota
Cloudflare 1015Cloudflare's edge, before the originYes, after the operator's timeoutYou tripped an edge rate limiting rule
Cloudflare 1020Cloudflare's edge firewallNoYour request matched a block rule
HTTP 403Origin or edgeNoAccess refused — usually auth or policy
HTTP 503 + Retry-AfterOriginYesServer 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:

  1. Retry-After overrides your curve. Always. Your exponential is a fallback for when the server said nothing.
  2. Cap the attempts. Infinite retry hides a capacity problem and keeps load on a server that already asked you to stop.
  3. 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.

AlgorithmHow it behavesRetry strategy that fits
Fixed windowCounter 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 windowThe window moves continuously, so capacity returns graduallyGentle exponential backoff. Capacity is trickling back, so short waits do help
Token bucketTokens refill at a steady rate; unused ones accumulate up to a burst limitSlow your average rate below the refill rate. Bursts are allowed; sustained overspend is not
Leaky bucketRequests drain at a fixed rate; overflow is rejectedPace requests evenly. Bursts are punished even when the average is fine
Concurrency limitCaps simultaneous in-flight requests, not requests per secondReduce 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.

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?

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 typeTreatment under per-IP limitsWhy
DatacenterOften given a lower quota, or blocked outrightHosting ranges are published and easy to score as non-human
Commercial VPNFrequently shares a quota with strangersExits are enumerable and heavily shared
Shared residential poolQuota is effectively pooled with other customersYou inherit whatever the previous user spent
Mobile carrier (4G/5G)Usually treated like ordinary consumer trafficCarrier 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.

Dedicated mobile proxies, one dashboard

Real 4G/5G devices on US carrier SIMs. Sticky IP per customer, rotation on demand.

See plans