Claude API Rate Limit Error Handling in Python
Claude API rate limit error handling in Python
When your Python code hits Claude's rate limits, you'll get an HTTP 429 response with a specific error body, and sometimes overloaded_error responses too. The right way to handle this is to catch the exception, read the retry-after header when present, back off, and retry — rather than letting the request fail or hammering the API in a tight loop.
This article shows exactly how to detect rate limit errors in Python, what information Claude gives you to recover gracefully, and how to structure retry logic that won't make the problem worse under load.
What a Claude rate limit error actually looks like
Claude's API enforces limits on requests per minute, tokens per minute, and tokens per day, depending on your plan tier. When you exceed one of these, the API returns a 429 status with a body like this:
{
"type": "error",
"error": {
"type": "rate_limit_error",
"message": "Number of request tokens has exceeded your per-minute rate limit."
}
}
If you're using the official anthropic Python SDK, this surfaces as anthropic.RateLimitError. A separate anthropic.APIStatusError with a 529 status covers overloaded_error, which means Anthropic's infrastructure is temporarily saturated — not that you personally exceeded a quota. Both need backoff, but worth distinguishing in your logs.
Catching the error with the Python SDK
import time
import anthropic
client = anthropic.Anthropic(api_key="sk-ant-...")
def ask_claude(prompt, max_retries=5):
for attempt in range(max_retries):
try:
return client.messages.create(
model="claude-opus-4-20250514",
max_tokens=1024,
messages=[{"role": "user", "content": prompt}],
)
except anthropic.RateLimitError as e:
retry_after = e.response.headers.get("retry-after")
wait = float(retry_after) if retry_after else (2 ** attempt)
print(f"Rate limited. Waiting {wait}s (attempt {attempt + 1})")
time.sleep(wait)
except anthropic.APIStatusError as e:
if e.status_code == 529:
wait = 2 ** attempt
print(f"Overloaded. Waiting {wait}s")
time.sleep(wait)
else:
raise
raise RuntimeError("Max retries exceeded")
A few things that matter in this snippet:
- Check
retry-afterfirst. Anthropic sends this header on 429s, and it's more accurate than a guessed backoff interval — use it when it's there. - Fall back to exponential backoff when the header is missing. Doubling the wait time on each attempt prevents retries from piling up during sustained load.
- Cap your retries. Five attempts is usually enough; beyond that you should surface the failure to the caller instead of blocking indefinitely.
- Don't retry blindly on every exception. A 400 (bad request) or 401 (auth) won't fix itself with a retry — only retry on 429, 529, and transient network errors.
Raw requests instead of the SDK
If you're calling the HTTP API directly instead of using the SDK, the same logic applies to the status code and headers:
import time
import requests
def call_claude(prompt, api_key, max_retries=5):
url = "https://api.anthropic.com/v1/messages"
headers = {
"x-api-key": api_key,
"anthropic-version": "2023-06-01",
"content-type": "application/json",
}
payload = {
"model": "claude-opus-4-20250514",
"max_tokens": 1024,
"messages": [{"role": "user", "content": prompt}],
}
for attempt in range(max_retries):
resp = requests.post(url, headers=headers, json=payload)
if resp.status_code == 200:
return resp.json()
if resp.status_code in (429, 529):
retry_after = resp.headers.get("retry-after")
wait = float(retry_after) if retry_after else (2 ** attempt)
time.sleep(wait)
continue
resp.raise_for_status()
raise RuntimeError("Max retries exceeded")
This version works identically whether you're calling Anthropic directly or any API that follows the same convention, which matters if you're routing through a proxy.
Adding jitter and concurrency limits
If multiple workers or threads are calling Claude at once, synchronized backoff timers make the problem worse — everyone retries at the same moment and you get a second wave of 429s. Add random jitter:
import random
wait = (2 ** attempt) + random.uniform(0, 1)
For services with concurrent requests, it's also worth throttling at the source using a semaphore or a token-bucket limiter so you rarely hit 429 in the first place, rather than relying purely on reactive retries. Rate limit errors are easier to prevent than to recover from gracefully.
Logging and metadata you should capture
When a rate limit error happens, log at minimum: the model used, the token count of the request, the timestamp, and the retry-after value if present. This lets you tell the difference between a short-lived spike and a structural problem — for example, consistently hitting your tokens-per-minute ceiling because of long conversation histories, which backoff alone won't fix. In that case the actual fix is trimming context, batching requests, or upgrading your tier.
Where SubToAPI fits
If you're building a product on top of Claude and want rate limit handling, retries, and usage metadata without writing and maintaining this logic yourself, SubToAPI exposes Claude through a standard HTTPS API with per-key usage tracking and streaming support built in. You get an application key (sub_live_...) per project or team seat, so you can see exactly which part of your system is driving rate limit pressure instead of debugging it blind. Check the quickstart or the messages docs to see request and response shapes, and pricing if you want to compare plans.
FAQ
Why do I get a 429 even though I haven't sent many requests? Rate limits in Claude's API are based on tokens per minute as well as requests per minute, so a few requests with large prompts or big max_tokens values can exhaust your quota just as fast as many small ones.
Should I retry on every error type? No. Only retry on 429 (rate limit), 529 (overloaded), and genuine network timeouts. Errors like 400 (invalid request) or 401 (bad API key) need a code fix, not a retry loop.
Is exponential backoff enough, or do I need the retry-after header too? Use retry-after when it's present — it reflects the server's actual recommendation. Fall back to exponential backoff with jitter only when the header is missing, which keeps concurrent clients from retrying in sync.