Claude API Rate Limit Handling Strategies That Work
Claude API rate limits are enforced per organization across requests-per-minute (RPM), input tokens-per-minute (ITPM), and output tokens-per-minute (OTPM). When you exceed any of these, you get a 429 response with a retry-after header, and if your app doesn't handle that gracefully, users see failed requests instead of slightly slower ones.
The core strategies that actually solve this in production are: exponential backoff with jitter, client-side request queuing that respects token budgets (not just request counts), reading the rate limit headers Anthropic returns on every response, and — for teams that outgrow a single set of limits — distributing load across multiple API keys or accounts. Below is how to implement each one.
Read the Rate Limit Headers First
Before writing retry logic, use the information the API already gives you. Every response includes headers like:
anthropic-ratelimit-requests-limit: 50
anthropic-ratelimit-requests-remaining: 12
anthropic-ratelimit-requests-reset: 2024-01-15T10:32:00Z
anthropic-ratelimit-tokens-remaining: 8000
anthropic-ratelimit-tokens-reset: 2024-01-15T10:32:00Z
Parse these on every response, not just on 429s. If tokens-remaining is dropping fast relative to reset, throttle proactively instead of waiting for a hard failure. This turns rate limiting from a reactive problem into something you can smooth out before it happens.
function trackRateLimit(headers) {
return {
requestsRemaining: parseInt(headers.get('anthropic-ratelimit-requests-remaining')),
tokensRemaining: parseInt(headers.get('anthropic-ratelimit-tokens-remaining')),
resetAt: new Date(headers.get('anthropic-ratelimit-requests-reset')),
};
}
Exponential Backoff with Jitter
When you do hit a 429, don't retry immediately — that just adds to the congestion. Use exponential backoff with random jitter so concurrent requests don't all retry at the same instant:
async function callWithBackoff(fn, maxRetries = 5) {
for (let attempt = 0; attempt < maxRetries; attempt++) {
try {
return await fn();
} catch (err) {
if (err.status !== 429 || attempt === maxRetries - 1) throw err;
const retryAfter = err.headers?.get('retry-after');
const base = retryAfter ? parseInt(retryAfter) * 1000 : 2 ** attempt * 1000;
const jitter = Math.random() * 500;
await new Promise(r => setTimeout(r, base + jitter));
}
}
}
Always prefer the retry-after header value when present — it's more accurate than a guessed backoff curve, especially when the limit reset is only a few seconds away.
Queue Requests by Token Budget, Not Just Count
RPM limits are usually generous compared to token limits. A single request with a long system prompt or large context window can consume a big chunk of your per-minute token budget even if you're nowhere near your request count limit. A queue that only throttles on request count will still get 429s from token limits.
Build a simple token-aware queue:
class TokenBudgetQueue {
constructor(tokensPerMinute) {
this.limit = tokensPerMinute;
this.used = 0;
this.windowStart = Date.now();
}
async acquire(estimatedTokens) {
const elapsed = Date.now() - this.windowStart;
if (elapsed > 60000) {
this.used = 0;
this.windowStart = Date.now();
}
if (this.used + estimatedTokens > this.limit) {
const wait = 60000 - elapsed;
await new Promise(r => setTimeout(r, wait));
this.used = 0;
this.windowStart = Date.now();
}
this.used += estimatedTokens;
}
}
Estimate tokens roughly (character count / 4 is a common heuristic) before the call, then reconcile with the actual usage reported in the response.
Prioritize and Shed Load Gracefully
Not all requests are equal. A user-facing chat completion is more time-sensitive than a background summarization job. When you're near your limit, prioritize interactive requests and delay or batch non-interactive ones:
- Give interactive requests a short queue with immediate retry.
- Give background/batch jobs a longer queue with larger backoff windows.
- Consider a circuit breaker: if you get repeated 429s within a short window, pause non-critical traffic entirely for a few seconds rather than hammering the API.
This keeps your app responsive for the requests users are actually waiting on, instead of treating every request as equally urgent.
Cache and Deduplicate Aggressively
Rate limits only bite when you're actually sending requests. Two cheap wins reduce volume before you even need backoff logic:
- Cache repeated prompts. If multiple users or sessions send near-identical requests (FAQs, common queries), cache the response for a short TTL.
- Deduplicate in-flight requests. If the same request is triggered twice in quick succession (double-click, retry logic firing early), coalesce them into a single API call.
Both reduce your effective request rate without any change to how Claude itself handles limits.
Scale Beyond a Single Key
If you've implemented backoff, token budgeting, and caching and you're still bumping into limits during peak traffic, the next step is distributing load. This usually means either requesting a limit increase from Anthropic for high-volume production use, or splitting traffic across multiple API keys tied to different accounts or seats.
This is where a layer like SubToAPI is useful if you're already on a Claude subscription rather than a metered API account — it exposes your access as a standard HTTPS API with application-level keys (sub_live_...), so you can issue separate keys per service or environment and manage usage from one dashboard instead of juggling credentials manually. Setup takes a few minutes; see the quickstart or pricing for the seat-based plans if you're distributing usage across a team.
Putting It Together
A production-grade rate limit handling stack usually looks like:
- Read and log rate limit headers on every response.
- Queue requests against a token budget, not just a request counter.
- Retry 429s with exponential backoff + jitter, honoring
retry-after. - Prioritize interactive traffic over background jobs.
- Cache and deduplicate to reduce raw request volume.
- Distribute across keys or request higher limits once you've optimized the above.
Most teams only need steps 1–3 to eliminate visible failures. Steps 4–6 matter once you're running Claude in production at meaningful scale.
FAQ
What's the difference between RPM and token-per-minute limits? RPM caps how many requests you can send per minute regardless of size. ITPM/OTPM cap the total input/output tokens processed per minute. A few large requests can hit token limits well before you hit the request count limit.
Should I retry every 429 automatically? Yes, but with backoff and a retry cap (3-5 attempts is typical). Retrying instantly or indefinitely can worsen congestion and won't resolve a limit that resets on a fixed schedule.
Can multiple API keys bypass rate limits? Multiple keys under the same organization typically share the same limits. To meaningfully distribute load, you need separate accounts, a higher limit tier from Anthropic, or a management layer that issues distinct keys per environment, like SubToAPI.