A 429 from the Claude API means you crossed one of Anthropic's per-minute limits — requests, input tokens, or output tokens. The fix is rarely "wait longer": it's honoring retry-after, adding jittered backoff, and smoothing bursts. Here is the complete playbook.
The error
{
"type": "error",
"error": {
"type": "rate_limit_error",
"message": "Number of request tokens has exceeded your per-minute rate limit"
}
}Causes and fixes at a glance
| Cause | Fix |
|---|---|
| Requests-per-minute (RPM) limit hit | Queue requests client-side; honor the retry-after header before retrying. |
| Input-tokens-per-minute (ITPM) hit — large prompts, few requests | Trim retrieved context, enable prompt caching so cached tokens stop counting against you on supported plans. |
| Output-tokens-per-minute (OTPM) hit | Set max_tokens realistically — OTPM is often the first ceiling for long generations. |
| Burst traffic (cron fires everything at :00) | Add jitter to schedules; spread batch jobs across the minute. |
Read the response before retrying
Anthropic returns a retry-after header with the seconds to wait, and the error message names which limit you crossed. Retrying instantly without reading it is how a single 429 becomes a 429 storm.
Add exponential backoff with jitter
Retry only retryable statuses (429, 500, 529), never auth or validation errors. This snippet works unchanged against Anthropic directly or any OpenAI-compatible endpoint:
import time, random
from openai import OpenAI, APIStatusError
client = OpenAI(base_url="https://api.kunavo.com/v1", api_key="sk-kn-...")
def with_backoff(fn, max_retries=5):
for attempt in range(max_retries):
try:
return fn()
except APIStatusError as e:
if e.status_code not in (429, 500, 529):
raise # don't retry auth/validation errors
retry_after = e.response.headers.get("retry-after")
delay = float(retry_after) if retry_after else min(2 ** attempt, 30)
time.sleep(delay + random.uniform(0, 0.5)) # jitter avoids herds
raise RuntimeError("retries exhausted")
resp = with_backoff(lambda: client.chat.completions.create(
model="claude-sonnet-4-6",
messages=[{"role": "user", "content": "ping"}],
max_tokens=32,
))
print(resp.choices[0].message.content)Reduce the tokens, not just the requests
If the message says token limits, backoff alone won't save you. Cut retrieved chunks, cap max_tokens, and turn on prompt caching — a large stable system prompt at 10% of the input rate also relieves ITPM pressure.
If you’re calling through Kunavo
Calling Claude through Kunavo doesn't magically remove rate limits, but it changes the failure economics: failed requests (including 429s) are never billed, and the per-key usage dashboard shows exactly which key and model is bursting so you can smooth it. The same backoff snippet above works as-is — just the base_url differs. Rate limits are a throughput ceiling, not a price — for what a Claude call actually costs per 1M tokens, with Anthropic's official list beside ours, see the Anthropic Claude API price list.
FAQ
Does upgrading my Anthropic tier remove 429s?
Higher tiers raise the per-minute ceilings, so 429s get rarer, but any fixed ceiling can be hit by a burst. Production code needs backoff regardless of tier.
Should I retry a 429 immediately?
No — honor the retry-after header (or use exponential backoff with jitter if it's absent). Immediate retries extend the rate-limited window and can escalate into a longer lockout.
Do failed 429 requests cost money?
Anthropic doesn't charge for rejected requests, and neither does Kunavo — failed requests are never billed. The cost of a 429 is latency, not dollars.
Related guides
- AI cost optimization — the complete guide to cutting 70-90% off your LLM bill
- OpenAI API rate limits — which limit you hit, how to read it, and the retry that fixes it
More error semantics live in the error reference; getting a key takes a minute via sign up and the authentication docs.