How to Handle Claude API Rate Limits
When you call the Claude API faster than your account's allowed throughput, you get back a 429 Too Many Requests response (or sometimes a 529 overload error). Handling this correctly means catching those status codes, backing off with increasing delays, and spreading requests out so you don't hit the ceiling again immediately. This article walks through the actual mechanics: what the limits look like, how to implement retry logic that works in production, and how to redesign your request pattern so rate limiting becomes a rare event instead of a daily headache.
Rate limits on the Claude API are enforced per organization and are split across requests-per-minute (RPM), input-tokens-per-minute (ITPM), and output-tokens-per-minute (OTPM). You can hit any one of these three independently — a burst of short requests can trip the RPM limit while a handful of huge prompts can trip ITPM. Anthropic returns rate-limit headers on every response so you can see how close you are before you actually fail.
Reading the Rate Limit Response
A 429 response typically includes a retry-after header (in seconds) and headers showing your current limit status:
HTTP/1.1 429 Too Many Requests
retry-after: 12
anthropic-ratelimit-requests-remaining: 0
anthropic-ratelimit-tokens-remaining: 1500
The first rule of handling rate limits: respect retry-after instead of guessing. If the server tells you how long to wait, use that number rather than a fixed backoff.
Implementing Exponential Backoff with Jitter
If retry-after isn't present, or you want a general-purpose fallback, use exponential backoff with jitter. Fixed-delay retries cause synchronized retry storms when many requests fail at once — jitter spreads them out.
async function callWithRetry(fn, maxRetries = 5) {
let attempt = 0;
while (true) {
try {
return await fn();
} catch (err) {
const isRateLimit = err.status === 429 || err.status === 529;
if (!isRateLimit || attempt >= maxRetries) throw err;
const retryAfter = err.headers?.['retry-after'];
const baseDelay = retryAfter
? Number(retryAfter) * 1000
: Math.min(1000 * 2 ** attempt, 30000);
const jitter = Math.random() * 0.3 * baseDelay;
await new Promise(r => setTimeout(r, baseDelay + jitter));
attempt++;
}
}
}
This pattern works whether you're calling Anthropic directly or through a wrapper — the important parts are: cap the maximum delay, stop after a reasonable number of retries, and always add jitter.
Queuing Requests Instead of Retrying Blind
Retrying after failure is reactive. A better approach for high-volume applications is a client-side token bucket or queue that throttles outgoing requests to stay under your known limits in the first place.
class RateLimiter {
constructor(maxPerMinute) {
this.maxPerMinute = maxPerMinute;
this.timestamps = [];
}
async acquire() {
const now = Date.now();
this.timestamps = this.timestamps.filter(t => now - t < 60000);
if (this.timestamps.length >= this.maxPerMinute) {
const waitMs = 60000 - (now - this.timestamps[0]);
await new Promise(r => setTimeout(r, waitMs));
return this.acquire();
}
this.timestamps.push(now);
}
}
Call await limiter.acquire() before each API call. This keeps your request rate under the limit proactively instead of discovering it with a 429.
Reducing the Chance of Hitting Limits
Beyond retry logic, a few structural changes reduce rate limit pressure significantly:
- Batch where possible. If you're summarizing 50 documents, don't fire 50 parallel requests — queue them with controlled concurrency (e.g., 3–5 in flight at a time).
- Cache repeated prompts. If the same system prompt and context get reused across requests, check whether you actually need a fresh call each time.
- Trim context. Large inputs consume ITPM fast. Strip irrelevant history, truncate documents, or summarize before sending.
- Separate burst traffic from background jobs. Give interactive user-facing requests priority; let batch/background jobs run on a slower, queued schedule so they don't compete for the same per-minute budget.
- Monitor your usage headers. Log
anthropic-ratelimit-tokens-remainingon every response so you can see a limit approaching before you hit it, not after.
Handling Rate Limits Across a Team
Rate limits are usually set at the organization level, which means every service and every developer on your team is drawing from the same pool. If you have multiple apps or environments (staging, production, a side project) all calling the same Claude account, one noisy service can exhaust the shared limit and cause 429s in an unrelated app.
This is one of the reasons teams put an API layer in front of raw Claude access. SubToAPI turns your existing Claude access into separate sub_live_... application keys, so each app or environment gets its own key and usage is visible per key in a dashboard — making it much easier to see which service is actually driving rate limit pressure instead of guessing. It also handles streaming and tool use through the same interface, so your retry and queuing logic stays consistent whether you're building one app or several. If you're currently sharing one Claude account across multiple projects, check the docs or start a free trial to see if separating keys solves your rate limit confusion before you build more retry logic than you need.
Testing Your Rate Limit Handling
Don't wait for production traffic to find out if your retry logic works. Simulate 429s locally by wrapping your API client in a mock that returns a rate-limit error on a percentage of calls, and confirm your backoff delays and retry counts behave as expected. It's much cheaper to find a bug in a unit test than to find it during a traffic spike.
Questions
What status code does the Claude API return for rate limits? 429 Too Many Requests for exceeding your RPM/ITPM/OTPM limits, and occasionally 529 when Anthropic's servers are overloaded independent of your account limits. Both should be retried with backoff.
Should I retry every failed request? No. Only retry on 429, 529, and transient network errors. Retrying on 4xx errors like 400 (bad request) or 401 (auth) just repeats a failure that backoff can't fix.
How do I know how close I am to my rate limit? Check the anthropic-ratelimit-*-remaining headers on every response. Logging these over time lets you see usage trends before you actually hit a 429, rather than finding out from an error.