Engineering Guide

Claude Rate Limit Errors: When to Retry and When to Stop

A Claude 429 does not always mean "wait a few seconds." It can indicate a short-term rate limit or a monthly spend cap. Start with the error details, not a retry loop. This guide separates API failures from chat-plan limits, supplies a bounded Python example, and explains what to check before sending the same request again.

Claude Rate Limit Errors: Classify the failure before retrying.

Diagnose the failure before changing your code

Save the HTTP status, error type, request ID, timestamp, selected model, and relevant limit headers. Do not copy an API key, customer prompt, or full response body into a shared log. A useful incident record answers: which workload failed, on which account and model, and what happened to its request rate?

Observation First action Retry policy for this guide
429 with error.details.error_code = enforced_spend_limit_reached Review the tier cap in Console Stop; a short delay cannot restore access
429 with a valid numeric retry-after and no spend-cap code Queue the request and reduce arrival rate Wait at least that interval, within a fixed budget
429 without a usable header or recognizable details Inspect billing, workspace limits, and the provider response Stop automatic retries until classified
529 overloaded_error Check the Claude service status A small retry budget may be appropriate
400, 401, 402, or 403 Fix the request, credentials, billing, or access Do not repeatedly send the unchanged request
Interrupted stream or timeout Establish whether a partial result or tool action occurred Do not blindly repeat the whole operation

The official error reference distinguishes invalid requests, authentication, billing, permissions, rate limits, and overload. Streaming can fail after an initial HTTP 200, so a successful status alone is not a completed answer.

A missing retry-after is a reason to investigate, not proof of a monthly cap. The explicit spend-cap detail is the stronger signal. This table describes a conservative application policy; it is not a replacement for a provider's error documentation.

A spend-cap 429 is not a temporary rate limit

Anthropic documents a tier-cap response with rate_limit_error, the detail code above, and no retry-after. API access stays paused until the cap is raised or the next monthly reset, at 00:00 UTC on the first day of the month. Repeated attempts do not help. See Reaching your spend cap.

That calendar reset is not the reset rule for every limit. A self-imposed organization or workspace spend limit normally returns 400; Claude Code workspace limits can instead return 429. The next diagnostic step is the actual Console limit and error message, not a blanket instruction to top up credit.

If an application keeps retrying a cap error, disable that queue's retry path and alert the account owner. Preserve pending work with an explicit failed or paused state. Do not silently run it on another paid account.

Low average usage can still hit a limit

RPM measures requests; ITPM and OTPM measure input and output token throughput. A low daily average can conceal a burst from parallel workers or a much larger prompt. In Anthropic's documented example, 60 RPM can be enforced as one request per second. The earlier version of this article incorrectly attached that example to 1,000 RPM. Rate-limit reference

To investigate a burst, compare the same short time window across workers. A batch of ten jobs starting together is different from ten jobs arriving evenly. Check whether a retry storm itself created the traffic spike: one failed request may have been multiplied by both a queue and an SDK.

Use your current organization and workspace limits as the configuration source. Published tier tables are maximums, not a throughput guarantee, and Evaluation-tier accounts may start lower. Do not copy a public Start-tier number into a new account's worker pool and assume it is safe.

A bounded Python retry example

The Python SDK retries selected failures twice by default. Choose one retry owner. The example below disables SDK retries and lets this one wrapper make at most three attempts, with at most 20 seconds of scheduled waiting. It stops for a spend cap, an unclassified 429, and non-retryable statuses.

"""Conservative non-streaming retry example; one retry owner only."""

import math
import random
import time

from anthropic import APIStatusError


def call_with_budget(create, *, attempts=3, wait_budget=20.0,
                     sleep=time.sleep, jitter=random.random, **request):
    if isinstance(attempts, bool) or not isinstance(attempts, int) or attempts < 1:
        raise ValueError("attempts must be a positive integer")
    if not math.isfinite(wait_budget) or wait_budget < 0:
        raise ValueError("wait_budget must be finite and nonnegative")

    waited = 0.0
    for attempt in range(attempts):
        try:
            return create(**request)
        except APIStatusError as exc:
            body = exc.body if isinstance(exc.body, dict) else {}
            error = body.get("error", body)
            error = error if isinstance(error, dict) else {}
            details = error.get("details", {})
            details = details if isinstance(details, dict) else {}
            if details.get("error_code") == "enforced_spend_limit_reached":
                raise
            if attempt == attempts - 1:
                raise

            header = exc.response.headers.get("retry-after")
            if exc.status_code == 429 and header is None:
                raise
            if exc.status_code not in (429, 500, 503, 529):
                raise
            if header is not None:
                try:
                    delay = float(header)
                except (TypeError, ValueError):
                    raise exc
            else:
                delay = float(2 ** attempt)
            if not math.isfinite(delay) or delay < 0:
                raise

            noise = jitter()
            if not math.isfinite(noise) or not 0 <= noise <= 1:
                raise ValueError("jitter must return a value between 0 and 1")
            delay += noise
            if waited + delay > wait_budget:
                raise
            sleep(delay)
            waited += delay

Usage with the official SDK:

from anthropic import Anthropic
from retry_policy import call_with_budget

with Anthropic(max_retries=0, timeout=10.0) as client:
    message = call_with_budget(
        client.messages.create,
        model="claude-sonnet-5-5",
        max_tokens=1024,
        messages=[{"role": "user", "content": "Explain a token bucket."}],
    )

This usage example would make a billable call if executed with a real key. It was not executed for this revision. The local tests use fabricated SDK exceptions and fake sleep; they check branching and budgets, not live Anthropic availability.

The wrapper deliberately accepts only a numeric seconds header. A malformed header or HTTP-date value is handed back to the caller rather than guessed at. It also propagates connection errors, timeouts, and stream interruptions. Its 20-second allowance covers scheduled sleeps, not the total request deadline; configure transport timeouts and an application deadline separately.

When the server asks for longer than the remaining wait allowance, this example raises the original error. It never shortens the server's delay just to squeeze in another attempt. Put longer waits on a delayed queue with cancellation and expiry rather than keeping a web request open indefinitely.

Download and verify the retry policy without a live key

Download retry_policy.py and its offline SDK tests. The downloadable module matches the example above, so the import does not depend on reconstructing a file from the page. The tests use fabricated responses and a mock transport; they do not send a model request.

python3 -m unittest -v test_retry_policy.py

Run in a disposable environment with the required Anthropic SDK already installed. No real API key is needed. Passing these tests establishes the error branches and waiting budget only, not provider availability or your queue's total deadline.

Reduce pressure at the queue, not just at the exception

Start with the worker pool that sends the requests. Cap concurrent calls, smooth job starts, and keep only one layer responsible for retries. Record success after a complete result, not when a job merely enters the queue. If a request initiates external actions, protect those actions from duplication before enabling retries.

Read the anthropic-ratelimit-requests-remaining and input/output token remaining/reset headers where available. Use them to adjust admission of new jobs; a reset timestamp is not a promise that queued work will complete. Track retry count and final disposition alongside latency so delayed failures do not disappear from your dashboard.

Repeated context is another pressure source. For most Claude models, cache reads are excluded from ITPM, while uncached input and cache writes still count; Haiku 3.5 is an exception. Caching does not remove request, output, or spend limits. Verify actual cache usage before calculating headroom. Cache-aware ITPM

Do not treat a high max_tokens setting as a free throughput fix. It is an output ceiling, not a concurrency control, and a long response can still occupy a worker. If the response is complete with fewer tokens, reduce the task's requested output rather than truncating necessary information.

Chat-plan limits and suspension are separate cases

In the Claude app, follow the specific usage notice and reset information displayed for your account. Do not substitute an API tier table for a Free, Pro, or Max plan limit. This guide does not establish a universal chat reset time or diagnose an account from a screenshot of "rate exceeded."

An explicit suspension notice needs a different response. Use the Claude account suspension and appeal guide for a truthful public appeal template. Retries, alternate accounts, and routing changes are not an appeal process.

Fallback needs approval and a separate evaluation

A gateway may provide a second authorized endpoint, but it does not remove account rules or guarantee an equivalent answer. Before allowing fallback, approve the destination's data handling and budget, test required tool/schema behavior, and decide whether the user must know that the model changed. Keep source and fallback results distinguishable.

For a rate-limited queue, delaying work can be preferable to changing its model. A time-sensitive, already evaluated task may tolerate fallback; a sensitive prompt or untested tool loop should fail visibly. The routing and failover guide covers the mechanism, while the LLM API error reference helps decode other providers. Neither makes a gateway an exemption from a restriction.

FAQ

Should every Claude 429 be retried?

No. Stop for the documented spend-cap code and investigate unclassified 429s. A valid retry header can support a bounded retry, but it does not justify an infinite loop.

Is Claude 529 the same as a rate limit?

No. It indicates overload. Reduce pressure and use a small retry budget; only switch providers when the alternate path has already been authorized and evaluated.

Does caching prevent all rate-limit errors?

No. It can reduce repeated-input pressure, but request limits, output limits, billing caps, and cache misses still matter.

How do I stop SDK and queue retries multiplying?

Give one layer ownership of retries. In this example the SDK has max_retries=0; the wrapper owns the attempt and wait limits. A production queue must not add another independent retry loop.

Written by

Prepared with AI assistance. This revision checks documentation, consolidates dated records and tests offline examples. It does not claim new model, integration or hardware runs.