← Blog

Claude API Rate Limit Error Handling in Python

2026-11-06 · 4 min read · SubToAPI Team

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:

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.

Turn your Claude access into an HTTPS API

SubToAPI gives you application API keys, streaming, tool use and usage insights on top of your existing Claude access — set up in minutes.

Start free  Read the quickstart →