How do I handle LLM API rate limits (429s) in production?

A 429 is the easy part. The hard part is what your code does next. I explain which limit you hit, how to read Retry-After, how to retry without a storm, and why agents need a budget for each run.

How Do I Handle LLM API Rate Limits (429s)? Retry Budgets for Agents

Image: METAHEURISTIC

Alexander Myasoedov

+Alexander Myasoedov Alexander writes about the operational side of shipping production AI - agents, retrieval, evals, and the guardrails that keep them from going sideways.

How do I handle LLM API rate limits (429s) in production? Do four things. Find out which limit the 429 names. Wait at least as long as the Retry-After header says. Add random jitter to every wait, and set a cap on the number of retries. Then stop your own code from sending more than the API allows, with a queue in front of the client. Most guides stop there. I build agents, and agents add a fifth problem. One user action becomes many model calls, and each call can retry. This guide covers the four basic steps and then the agent problem.

How do you handle a 429? The short answer

Read
The limit
Find the dimension that failed: requests, input tokens or output tokens. Each one needs a different fix.
Wait
Retry-After, then jitter
Use the server delay as a minimum. Add a random delay so clients do not retry together.
Pace
Queue first
Send at the rate the API allows. Most 429s then never happen.
Cap
Budget per run
Limit the retries and the total wait time for each agent run, not only for each call.
Fall back
Second route
Use a second key, region or provider only when the first one stays blocked.
Alert
Watch 429 rate
A rising 429 rate shows a capacity problem before users see it.

Which limit did you hit: RPM, TPM or a daily cap?

A 429 does not say “slow down”. It says one of several limits failed. OpenAI lists RPM, RPD, TPM, TPD and IPM. These mean requests per minute, requests per day, tokens per minute, tokens per day and images per minute. You hit a limit when you reach any one of them first. A fix for one limit does nothing for another.

Anthropic measures the Messages API in three parts for each model class: RPM, input tokens per minute (ITPM) and output tokens per minute (OTPM). The 429 error says which one you exceeded. Read the error body before you write any retry code. As an example, the same page lists 1,000 RPM, 2,000,000 ITPM and 400,000 OTPM for Claude Sonnet 5.5 in the Start tier. The Build tier lists 5,000 RPM for the same model. A job with long prompts hits the token limit long before the request limit.

Two details change how you count. Anthropic says that for most Claude models, “only uncached input tokens count toward your ITPM rate limits”. Prompt caching therefore raises your real throughput. For OpenAI, the page says your limit “is calculated as the maximum of max_tokens and the estimated number of tokens”. A large max_tokens value can use capacity that the response never needs. Anthropic says the opposite for output: OTPM counts only the tokens the model produces, so max_tokens is not a factor there.

Short bursts also count. Anthropic says that a rate of 60 requests per minute “might be enforced as 1 request per second”. Thus 60 requests in the first second of a minute can fail, although the minute total is correct. The API uses a token bucket, so capacity returns continuously and does not reset at fixed times.

Not every 429 is a rate limit Anthropic returns a 429 when an organization reaches its monthly spend cap. That response has no retry-after header, and the docs say that retries fail until access resumes. OpenAI says not to retry quota or billing errors. Check the error code first. A retry loop on a spend cap only burns time.

Should you read Retry-After?

Yes. It is the cheapest correct signal that you get. Anthropic says the header holds “the number of seconds to wait until you can retry the request. Earlier retries will fail”. The header is not on every 429, so your code needs a fallback. OpenAI gives this rule: “Treat this value as a minimum: wait at least that long.” It also says to add a small random delay, so that clients do not retry at the same time.

OpenAI also warns that “unsuccessful requests contribute to your per-minute limit”. A retry that comes too early does not just fail. It uses part of the capacity that you wait for. This is the reason that a tight retry loop makes the problem last longer.

Both providers also send headers on successful responses. OpenAI sends x-ratelimit-remaining-requests and x-ratelimit-remaining-tokens. Anthropic sends anthropic-ratelimit-requests-remaining and matching input and output token headers. Log them. When the remaining value is near zero, your pacing layer can slow down before the first 429.

One more point about SDKs. OpenAI says its official SDKs retry eligible 429 and 503 responses. If you also write your own retry loop, the two loops multiply. Turn off one of them, or count the SDK retries in your budget.

Why does backoff need jitter?

Say that 60 clients fail at the same moment. A fixed wait makes all 60 retry at the same moment again. The server sees the same spike every time. The AWS Architecture Blog studied this in Exponential Backoff And Jitter. Their result: backoff alone “helps a small amount, but doesn’t solve the problem”. The fix is a “small change to the sleep function”. Each client picks a random delay. With 100 contending clients in their simulation, jitter cut the number of calls by more than half compared with no jitter. They call the no-jitter version “the clear loser”.

The blog compares three jitter types. Full jitter waits a random time between zero and the current backoff limit. Equal jitter keeps some of the backoff and randomizes the rest. Decorrelated jitter bases the next limit on the last random value. The blog finds that equal jitter takes much longer than full jitter, and that decorrelated jitter makes slightly more calls. I use full jitter because it is the simplest of the three.

The figure below shows the effect. It is a toy model with made-up numbers.

Figure - 60 clients hit a rate limit at once; the retry rule sets the load (toy model)
retry rule
Pick a retry rule and press Replay. Watch the bars above the capacity line: the red part of each bar is rejected and comes back as more load. Capacity, client count and wait times are made up for illustration. Only the shape is the point.

Sixty clients send one request in slot 0. The API accepts 10 per slot. Start with Retry now and watch the red parts of the bars. In my run, the clients send 210 requests in total to get 60 answers. Fixed wait sends the same 210, but in spikes of 60, 50, 40, 30, 20 and 10 every fourth slot. Backoff + jitter sends 145 requests. After the first spike, the load stays at 30 or less in every slot and ends in 9 slots. In this toy, Retry now finishes first. A real API counts the rejected requests against your limit, as OpenAI says, so that speed is not free.

What does a correct retry function look like?

This is the function that I use. It reads Retry-After, uses full jitter when the header is missing, stops after a fixed number of attempts, and stops after a total time limit.

import random, time

def call_with_retry(send, max_attempts=5, base=1.0, cap=30.0, deadline=60.0):
    start = time.monotonic()
    for attempt in range(max_attempts):
        resp = send()
        if resp.status_code != 429:
            return resp
        header = resp.headers.get("retry-after")
        if header:
            delay = float(header) + random.uniform(0, 1)  # minimum, plus jitter
        else:
            delay = random.uniform(0, min(cap, base * 2 ** attempt))  # full jitter
        out_of_time = time.monotonic() - start + delay > deadline
        if attempt == max_attempts - 1 or out_of_time:
            return resp  # give the 429 to the caller
        time.sleep(delay)

The function returns the last 429 and lets the caller decide. The caller knows if the user can wait. OpenAI asks for the same two limits: “Limit both the number of attempts and the total time spent retrying.” For async code, replace time.sleep with await asyncio.sleep.

How do you stop sending too much before the 429?

Retries repair a failure. A queue stops the failure. Put one limiter in front of every call to a given model, and set it below the published limit. Use a token bucket with the same shape as the provider limit. Take one request token for each call. Take input tokens by an estimate of the prompt size. Release the call when the bucket has enough. Let the bucket refill at a steady speed.

Make the limiter shared. A limit belongs to an organization or a project, not to one process. If you run ten workers that each allow 100 requests per minute, the API sees 1,000. Keep the counter in one place, for example in a gateway or in Redis. I described a gateway for 10,000 requests per second in how to build LLM guardrails. The same place is a good home for rate limits, because every request already passes it.

Increase traffic slowly. Anthropic says that a sharp increase in usage can cause 429 errors from “acceleration limits”. Its advice is to “increase your traffic gradually and maintain consistent usage patterns”. A batch job that starts at full speed can fail at once, although its average rate is below the limit. Ramp it up over a few minutes.

Pace first, retry second A good queue makes retries rare. Retries then handle only the bursts that the queue could not predict.

When should you fall back to another key or provider?

Fall back when the first route stays blocked after your backoff has run. Do not fall back at the first 429. A second route has its own limits, its own prices and its own model behavior. If you switch on every error, you move the load and the retry storm to the next provider.

Two kinds of fallback exist, and they are different. A second key on the same organization does not help. Anthropic says limits are set at the organization level, and OpenAI sends project-scoped headers as well. Check whose limit you hit before you add keys. A second provider, or a second model with its own limit, adds real capacity. Anthropic notes that some model families share one pool, for example the combined Opus 4.x bucket, while Opus 5.5 has a separate limit.

Same-provider fallback

  • Keeps the prompt and output style
  • A model in a separate limit pool can work
  • Does not help if the organization limit failed

Cross-provider fallback

  • Adds new capacity and survives an outage
  • Needs tested prompts for each model
  • Output quality and price change without warning

Open a circuit when a route fails often. Send no traffic to it for a short time, then test it with one request. This protects the failing route and your own latency. Test the fallback in staging before you need it. A fallback that nobody ran is not a plan.

What do agents change?

An agent turns one user action into many model calls. A chat message makes one call. An agent task reads files, calls tools and plans again. Each of those steps is a model call, and each call can hit a 429. I describe this in the agent loop. The loop runs until the model stops asking for tools.

Retries multiply with the steps. As my own example, take a task with 12 steps and a retry limit of 5 for each call. In the worst case that is 60 calls for one user action. Now run 200 such tasks at the same time. The queue fills with work that the user may never see. The limit stays full, and every new task starts with a 429. I think this is why agent systems fail in a different way from chat apps. They do not fail on one request. They fail on the sum of all the retries.

So put a budget on the run, as well as on the call. Give each run a maximum number of model calls, a maximum number of retries in total, and a maximum wall time. When the run reaches a limit, stop it and return a clear result. I wrote about the same idea for loops in preventing tool-call cycles. There, a hard step budget turns an endless loop into one that is “expensive but finite”. A retry budget does the same for rate limits.

Budget
Calls per run
A hard maximum on model calls for one user action. The run stops when it reaches the limit.
Budget
Retries per run
One counter for all retries in the run. Ten steps do not get ten full retry allowances.
Budget
Wall time
A deadline for the whole run. After it, return a partial result and do not retry.

Add three more rules for agents. Give each task a priority, so that a user-facing task goes before a background task when the queue is full. Make the run resumable, so that a stopped run can continue from its last good step and does not start again. Last, make tool calls idempotent. A retried step must not send the same email twice.

Count retries per run A per-call retry cap protects one call. A per-run retry budget protects your whole limit from one runaway task.

What should you monitor?

Track the 429 rate for each model, the retries for each run, the time spent in waits, and the remaining-capacity headers. Alert on a rising 429 rate, before it gets high. A slow rise shows that traffic is near the limit. Anthropic shows hourly peak usage in the Claude Console, so you can compare it with your own logs.

So how do you handle LLM API rate limits (429s) in production?

Read which limit failed and do not retry a spend cap. Wait at least as long as Retry-After, add full jitter, and cap both the attempts and the total time. Put a shared queue in front of the API so that most 429s never happen. Fall back to a second provider only after the first route stays blocked. For agents, add a budget for each run, because one task makes many calls and every call can retry. If you want help with this layer in your own agent system, I take this kind of work, and you can read more at AI agent developer.

Follow in Google

Make Metaheuristic a preferred source.

One tap and posts like this one surface higher in your Top Stories.

Work with us

Production AI, with guardrails.

Start with a fixed-scope AI Workflow Audit. We map the opportunity and quote a build.

Start a Discovery Sprint →