← Blog

Claude API Rate Limit Exceeded: Error Handling Guide

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

A "rate limit exceeded" error from the Claude API means you've hit one of Anthropic's throttling thresholds — requests per minute, tokens per minute, or concurrent connections — and the API is temporarily rejecting new calls until the window resets. The fix isn't to panic-retry as fast as possible; it's to detect the specific error, back off correctly, and architect your request flow so you rarely hit the ceiling in the first place.

This article covers what the error actually looks like, how to handle it correctly in code, and how to design around it so it stops being a recurring production incident.

What a Rate Limit Error Looks Like

When you exceed a limit, the API returns an HTTP 429 Too Many Requests status with a JSON error body similar to:

{
  "type": "error",
  "error": {
    "type": "rate_limit_error",
    "message": "Number of request tokens has exceeded your per-minute rate limit."
  }
}

Three distinct limit types can trigger this, and the message usually tells you which one:

The response usually includes retry-after style guidance in headers, which you should read programmatically rather than guessing a delay.

Step 1: Detect the Error Correctly

Don't treat every non-2xx response the same way. A 429 should be handled differently from a 400 (bad request, don't retry) or a 500 (server error, retry with caution).

async function callClaude(payload) {
  const res = await fetch("https://api.anthropic.com/v1/messages", {
    method: "POST",
    headers: {
      "x-api-key": process.env.ANTHROPIC_API_KEY,
      "anthropic-version": "2023-06-01",
      "content-type": "application/json",
    },
    body: JSON.stringify(payload),
  });

  if (res.status === 429) {
    const retryAfter = res.headers.get("retry-after");
    throw new RateLimitError(retryAfter ? Number(retryAfter) : null);
  }

  if (!res.ok) {
    throw new Error(`Claude API error: ${res.status}`);
  }

  return res.json();
}

Keeping a dedicated RateLimitError class makes it easy to branch your retry logic without re-parsing status codes everywhere.

Step 2: Implement Exponential Backoff With Jitter

Fixed-delay retries cause synchronized retry storms when many requests fail at the same moment. Exponential backoff with jitter spreads retries out and respects the server's hint if one is provided.

async function withRetry(fn, { maxAttempts = 5 } = {}) {
  for (let attempt = 1; attempt <= maxAttempts; attempt++) {
    try {
      return await fn();
    } catch (err) {
      if (!(err instanceof RateLimitError) || attempt === maxAttempts) {
        throw err;
      }
      const base = err.retryAfterSeconds
        ? err.retryAfterSeconds * 1000
        : 2 ** attempt * 500;
      const jitter = Math.random() * 300;
      await new Promise((r) => setTimeout(r, base + jitter));
    }
  }
}

A few rules worth following:

Step 3: Queue and Throttle Proactively

Reactive retries treat rate limiting as an exception. A more durable fix is to never send requests fast enough to trigger it. A simple token-bucket or sliding-window limiter on the client side keeps you under the ceiling:

class RequestQueue {
  constructor(maxPerMinute) {
    this.max = maxPerMinute;
    this.timestamps = [];
  }

  async schedule(fn) {
    const now = Date.now();
    this.timestamps = this.timestamps.filter((t) => now - t < 60000);
    if (this.timestamps.length >= this.max) {
      const waitMs = 60000 - (now - this.timestamps[0]);
      await new Promise((r) => setTimeout(r, waitMs));
    }
    this.timestamps.push(Date.now());
    return fn();
  }
}

This is especially important for batch jobs — summarizing thousands of documents, running evals, or backfilling embeddings — where it's tempting to fire everything concurrently. Throttle at the queue level and you'll finish the job reliably instead of fighting 429s the whole way through.

Step 4: Reduce Token Pressure, Not Just Request Count

TPM limits are often hit by oversized prompts, not request volume. Before adding more retry logic, check whether you're:

Trimming payload size is often a bigger win than any retry strategy, since it reduces how often you touch the limit at all.

Step 5: Separate Limits by Workload

If one background job (e.g., bulk classification) can starve your user-facing chat feature of rate limit headroom, split them. Running separate API keys or separate rate budgets per workload — one for interactive, latency-sensitive calls and one for batch — prevents a backlog job from causing user-visible errors.

This is one of the reasons teams move off raw provider keys and onto a gateway layer: it's easier to enforce per-workload quotas, see usage broken down by key, and avoid one misbehaving script taking down the whole integration. SubToAPI sits in front of your Claude access and issues separate sub_live_ application keys per service or team, along with per-key usage metadata, so a batch job and a production chatbot aren't drawing from the same invisible bucket. It won't eliminate rate limits — those are enforced upstream — but it gives you the visibility and separation to debug and distribute load sensibly. See the quickstart and messages API docs for how keys and requests map together.

Step 6: Monitor Before It Becomes an Incident

Track your 429 rate as a first-class metric, not just an error log line. A sudden spike usually means either traffic growth outpaced your plan, a deploy introduced a retry loop bug, or a batch job leaked into your interactive queue. Alerting on 429 rate (not just raw error count) catches rate-limit problems before they turn into user complaints.

Checklist

FAQ

Does retrying immediately after a 429 ever work? Rarely. If you're over a per-minute limit, the window needs to roll forward before capacity frees up. Immediate retries just add to the pressure and risk triggering stricter throttling.

Should backoff logic differ between RPM, TPM, and concurrency errors? The retry mechanics are the same, but the fix differs: RPM errors need request throttling, TPM errors need smaller payloads, and concurrency errors need a lower in-flight request cap. Read the error message to know which lever to pull.

Can a gateway or proxy eliminate rate limit errors entirely? No — the limits are enforced by the upstream provider based on your account's plan. A gateway like SubToAPI can isolate workloads with separate keys and give you clearer per-key usage data, which helps you diagnose and distribute load, but the underlying limits still apply.

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 →