429 Too Many Requests — by the time you see it, the automatic retries are already gone
- Look for
retry-afterfirst: present = throttling, backoff and lower concurrency work; absent = quota exhausted, and backoff can never work. - Stop retrying by hand — a temporary 429 was already retried with backoff before you saw it, so a visible 429 is not temporary.
- If a plugin client keeps getting 429 while curl on the same credential succeeds, switch to the OpenAI-compatible path first; concurrency is the secondary fix.
A temporary 429 is retried for you — up to 10 attempts with exponential backoff, honouring retry-after when the response carries it. So a 429 that actually reaches your screen has usually already survived that gauntlet. Hitting enter again is re-running a hypothesis that was just falsified ten times. The question worth asking is not how long should I wait; it's does this response have a retry-after header — because that one header separates two completely different problems that share a status code.
What you're seeing
API Error: 429 {"type":"error","error":{"type":"rate_limit_error","message":"..."}}
429 Too Many Requests
API Error: Request rejected (429) · this may be a temporary capacity issue. If it persists, check the status page.
API Error: Request rejected (429) · spend limit reached (daily; resets ... UTC)
The first three are throttles. The fourth is not a throttle at all — it is the same status code carrying the opposite meaning, and every instinct that helps with the first three makes the fourth worse.
Two 429s that share a number and nothing else
| Throttle — you're going too fast | Exhausted — you're out of allowance | |
|---|---|---|
retry-after header | present | absent |
| Error type | rate_limit_error | rate_limit_error — identical |
| Distinguishing field | — | error.details.error_code = enforced_spend_limit_reached on the Messages API |
| Retry hint header | — | x-should-retry: false on a gateway spend-limit 429 |
| Does backing off help? | Yes — that's exactly what it's for | Never. Automatic retries fail too, until access resumes |
| Recovers on its own? | Yes, in seconds to minutes | Only at a stated reset time, or when someone raises the limit |
| What actually fixes it | Lower concurrency, cache more, spread the burst | Raise the tier or limit, or take a different path |
Two more shapes worth knowing, because they break the "429 means rate limit" reflex in the other direction:
- A spend limit you set on your own organisation or workspace comes back as HTTP 400
invalid_request_error, not 429, with a message beginningYou have reached your specified API usage limits. People search for a 429 they never received. - Limits scoped specifically to a coding-agent workspace are checked separately and can return a 429 that does carry
retry-after— so aretry-aftertells you backoff is worth trying, not that your quota is healthy.
Read the headers, not the message
The message text is written for humans and varies by provider. The headers are the actual contract.
# <model-id>: copy an id from https://api.9coding.com/v1/models
curl -sS -D - -o /dev/null https://api.9coding.com/v1/messages \
-H "Authorization: Bearer $ANTHROPIC_AUTH_TOKEN" \
-H "content-type: application/json" \
-H "anthropic-version: 2023-06-01" \
-d '{"model":"<model-id>","max_tokens":16,"messages":[{"role":"user","content":"hi"}]}' \
| grep -iE 'http/|retry-after|x-should-retry|anthropic-ratelimit'
What you're looking for:
| Header | What it tells you |
|---|---|
retry-after | Seconds to wait. Its presence is the diagnosis; retrying earlier just fails |
anthropic-ratelimit-requests-remaining | Request headroom left |
anthropic-ratelimit-input-tokens-remaining | Input-token headroom (rounded to the nearest thousand) |
anthropic-ratelimit-output-tokens-remaining | Output-token headroom |
anthropic-ratelimit-*-reset | When that bucket is fully replenished, RFC 3339 |
Three readings that save real time:
retry-afterpresent, some*-remainingat or near zero → an ordinary throttle. The zeroed one names which limit you hit, and that determines which fix works. Being out of output tokens is not fixed by sending fewer requests.retry-afterabsent → stop retrying now. Nothing about waiting will change this. Go to step 5.- No
anthropic-ratelimit-*headers at all → your endpoint isn't passing them through. You can still useretry-after, but you've lost your headroom instrumentation, and you should know that about a route you depend on before an incident rather than during one.
One counter-intuitive property: limits are replenished continuously, not reset on a clock. A limit expressed per minute can be enforced over much shorter windows — 60 requests per minute may behave as roughly one per second. A burst can trip a limit you are nowhere near on average. If your usage graph looks fine and you're still getting throttled, look at the shape of your traffic, not the total.
Check in this order
1 · Decide which 429 you have#
Run the header check above. Everything below branches on it, and steps 2–4 are wasted effort on an exhausted quota.
2 · Stop stacking your own retries on top of the built-in ones#
If you've wrapped the client in a shell loop, or you're sitting on the enter key, you are adding delay to a backoff schedule that already ran to completion. Worse, you're issuing fresh requests into a bucket that needs quiet time to refill — the retry itself becomes the reason it stays empty.
Distinguish the two display states before reacting at all: a retry in progress is a countdown with an attempt number, and it is doing its job — leave it alone. An error printed without a countdown is the terminal state, after the budget is gone.
3 · Lower concurrency before you raise anything#
Throttling is usually shaped by parallelism, not by total work.
export CLAUDE_CODE_MAX_TOOL_USE_CONCURRENCY=2
Also worth doing: run fewer parallel subagents, avoid firing several sessions at the same credential simultaneously, and if you're ramping a new workload, ramp it gradually — a sharp step-change in traffic can trigger acceleration limits even when your steady-state volume is well within your tier.
4 · Raise effective throughput without raising your limit#
The cheapest fix on this page, and the one almost nobody reaches for. On most models, tokens read from cache do not count toward your input-token-per-minute limit — only uncached input and tokens being written to cache do. Concretely: cache_read_input_tokens is free as far as the limiter is concerned, while input_tokens and cache_creation_input_tokens are not.
The consequence is large. With a 2,000,000 input-token-per-minute limit and an 80% cache hit rate, you can push roughly 10,000,000 total input tokens per minute through the same limit. So cache the things you resend every turn — system instructions, tool definitions, large context documents, conversation history. A workload that was throttled at noon can be comfortable at the same volume in the afternoon with no limit change at all.
Check your own numbers first: if cache_read_input_tokens is near zero on a long session, this is your single biggest lever.
5 · If it's exhausted, stop debugging and go change something#
Confirm which credential the client is actually using — the frequent surprise is that it's not the one you topped up:
/status
Then: check the provider console for the active limits and current usage, and raise the tier or limit. If access is paused until a stated reset time, that time is the answer; no configuration on your side moves it.
6 · Unattended runs need a different setting, not a different attitude#
For CI jobs and long autonomous sessions where a mid-run failure costs more than waiting, the client can be told to keep retrying capacity errors instead of giving up after the default budget. That behaviour, its default attempt count, and its cap have all moved between versions — check the environment-variable reference for the client version you're actually running rather than copying a value from a forum post. One property has been consistent and is the important one: it still fails immediately on a spend-limit 429, because retrying that forever is just a slower way to fail.
The 429 that isn't about your volume
Here is a pattern worth naming, because it sends people to the wrong fix for days.
A plugin-style client — a browser translation extension is the clearest example — sends a default prompt that is byte-identical for every one of its users. Thousands of installations produce a stream of requests that look like one source: same system prompt, same shape, same cadence, arriving in a tight burst whenever a page loads. Upstream, traffic like that is easy to classify as a single actor working around a per-account allowance, and the response is a 429 that has nothing to do with your consumption.
The tell is that it doesn't behave like a throttle. Your own volume is trivial. Lowering concurrency changes nothing. Headroom headers, if you get them, show plenty left. And it tends to be all-or-nothing — either that client works or it doesn't — rather than degrading under load.
The fix that works is changing the request path, not the request rate. Routing that client through the OpenAI-compatible endpoint instead of the native one gives the traffic a different shape and a different classification, and it typically clears immediately. Turning down concurrency is the secondary measure; on its own it usually does nothing, which is precisely why people conclude their quota is broken.
curl against the same credential succeeds, you are not out of quota, and no amount of backoff is going to help.
When it isn't your problem
You're looking at something upstream, not your usage, when:
- your
anthropic-ratelimit-*-remainingheaders show real headroom and you're still getting 429s, - other clients pointed at the same endpoint start failing in the same window,
- it began without any change in your workload,
- and it clears on its own without you raising anything.
That combination points at a shared upstream pool or a general capacity event. Check the status page before you spend money on a higher tier that would not have helped.
When reporting it, include the request id — 9Coding error responses carry one in the form (request id: 2026...). That id plus the timestamp, the model name and the full error text lets the exact call be traced. A report that only says it doesn't work cannot be investigated.
429 and 529 are different failures
A 529 means the service is overloaded — a capacity problem on their side, affecting everyone, unrelated to your allowance. Both are retried automatically; both surface only once the budget is spent. The difference is what to do afterwards: a 429 has a lever on your end (concurrency, caching, tier). A 529 does not — you wait, or you fail over. Reducing concurrency to fix a 529 is harmless and useless. See 529 overloaded.
Related
- 529 overloaded — capacity on their side, not allowance on yours
- 401 Unauthorized — two layers of authentication, one error message
- TLS certificate errors — the failure with no retry budget at all
- All Claude Code errors — the quick reference table