Handling 429 and 503 errors from an LLM API
Every LLM API fails sometimes. The two errors you will meet most often in production are 429, you sent too much, and 503, there is nothing to serve you right now. Both are temporary. Retrying the wrong errors, or retrying too fast, turns a short blip into an outage. Here is what to retry, how long to wait, and when to switch models instead.
Retry, or fix the request?
| Status | Retry? | What to do |
|---|---|---|
| 400 | No | The request is malformed. Fix it. |
| 401, 403 | No | Wrong, revoked, or wrong-type key. |
| 402 | No | Out of balance. Top up. |
| 404 | No | Unknown model id. Check the model list. |
| 408 | Yes | Timed out. Retry with backoff. |
| 429 | Yes | Rate limited. Wait for Retry-After, then retry. |
| 500, 502 | Yes, a few times | Server or upstream error. Back off. |
| 503 | Yes, or fall back | No capacity. Retry later or use another model. |
The rule of thumb: 4xx means change the request, except 408 and 429, which mean wait. 5xx means wait.
429: slow down
A 429 means your key went over its rate limit. Good APIs tell you how long to wait in a Retry-After header, in seconds. Respect it. Retrying sooner only burns attempts against the same limit.
On AIKeys, the default limit is 60 requests a minute per key, counted in one-minute windows. A 429 comes back as rate_limit_exceeded with Retry-After: 60. If you hit it often, spread requests out with a client-side queue, or use more than one key for separate workloads.
503: no capacity
A 503 means the service could not place the request. On a marketplace like AIKeys it has a specific meaning: no publisher is serving that model at or below the price cap right now. The code is no_capacity, and you are not charged.
Before you see a 503, the proxy has already tried. If a publisher’s upstream times out, rate-limits, or is overloaded, the proxy retries once on a different publisher before it answers. So a 503 is worth handling in your code: wait and retry, or try a different model id.
What the SDKs already do
The official OpenAI and Anthropic SDKs retry connection errors, 408, 429, and 5xx responses twice by default, with exponential backoff. They read Retry-After when it is present. Set max_retries when you create the client to change that. For many apps, the defaults plus a fallback list is all you need.
A fallback list in Python
import os
from openai import OpenAI, APIStatusError
client = OpenAI(
base_url="https://api.aikeys.co/v1",
api_key=os.environ["AIKEYS_CO_KEY"],
max_retries=1, # retry each model once, then move on
)
# Same job, in order of preference.
MODELS = ["cc/claude-sonnet-4-6", "ag/claude-sonnet-4-6", "cx/gpt-5.6-terra"]
def complete(messages):
last_error = None
for model in MODELS:
try:
return client.chat.completions.create(model=model, messages=messages)
except APIStatusError as error:
if error.status_code in (503, 404): # no capacity, or id not listed
last_error = error
continue
raise # 400, 401, 402, 403: retrying will not help
raise last_error
reply = complete([{"role": "user", "content": "Summarize this ticket in one line: ..."}])
print(reply.choices[0].message.content)
Two details matter. The list mixes upstreams: the same Claude Sonnet model is listed as both cc/claude-sonnet-4-6 and ag/claude-sonnet-4-6, so a capacity gap on one may not exist on the other. And it re-raises the errors that a retry cannot fix, so a bad key or an empty balance fails fast instead of looping.
Reading the error body
AIKeys answers in the shape of the endpoint you called. Chat Completions and Responses use the OpenAI shape:
{ "error": { "message": "No serving offer at or below the cap.", "type": "server_error", "code": "no_capacity" } }
Messages uses the Anthropic shape:
{ "type": "error", "error": { "type": "no_capacity", "message": "No serving offer at or below the cap." } }
Branch on the status code first and the error code second. Both SDKs expose them on the exception.
Checklist
- Retry 408, 429, and 5xx. Never retry 400, 401, 402, 403, or 404.
- Honor
Retry-After. Add jitter to your own backoff so clients do not retry in lockstep. - Cap total attempts, and cap total time for anything a user is waiting on.
- Keep a fallback model list for 503.
- Log the status, the error code, and the model id, so you can see which failures are yours and which are capacity.
For setup, see how to call Claude, GPT and Gemini with one key. Every error code is in the API reference.