← Blog

How to Handle Claude API Rate Limits

2026-10-30 · 5 min read · SubToAPI Team

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:

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.

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 →