Everything worked in testing. Then traffic arrived, and the logs filled up with 529 Overloaded and the odd 500. Some of these errors are yours to fix. Others sit on Anthropic's side, and all you can do is retry sensibly. The whole trick is telling them apart: retrying a 400 forever burns money and wakes you at 3 AM for nothing. This guide puts every Claude API error in the right bucket and gives you working code.
What the 529 Overloaded error is and where it comes from
A 529 means Anthropic's servers are temporarily saturated and cannot take your request right now. Your key, your quota and your prompt have nothing to do with it: what counts is total demand on the platform at that second. In Anthropic's docs 529 is the "Overloaded" status, and the correct answer is to back off and try again shortly.
It is the most common error on the raw Anthropic API. Spikes land at peak hours and in the first days after a new model ships. The error is transient and server-side, so retrying is right. But retrying instantly and aggressively makes the congestion worse for everyone, you included.
Here is what the response looks like:
{
"type": "error",
"error": {
"type": "overloaded_error",
"message": "Overloaded"
}
}
Cause and fix for 529:
- Cause: platform-wide capacity pressure, usually at peak times or just after a launch.
- Fix: exponential backoff with jitter (code below), a cap on concurrency, and non-urgent work moved to off-peak windows or the Batch API.
- Client control: a bounded queue, lower concurrency and an explicit failure once retries run out. Gateway access does not add capacity.
One honest note: no header tells you when the overload will clear. Unlike 429, a 529 rarely carries a useful retry-after. Your backoff schedule is the only lever you have. Keep the jitter: without it, every client in the world retries on the same beat and recreates the spike.
What to do about a 500 error in the Claude API
A 500 Internal Server Error means something broke on Anthropic's side while handling a perfectly valid request. You did nothing wrong, and the same request usually succeeds on retry. Treat it like 529: back off and try again.
The two differ in nature. A 500 is an unexpected fault, a 529 is expected load-shedding. Your code handles them the same way, because both are retryable server-side errors. What you should not do is dig through the error body for clues and change the request. There is nothing to change.
{
"type": "error",
"error": {
"type": "api_error",
"message": "Internal server error"
}
}
If 500s keep coming for several minutes, that is no longer transient. Check the Anthropic status page and look at your own payload for anything bulky enough to trip a server-side edge case (extremely large tool schemas, for instance). In 99% of cases, though, one retry clears it.
What a 503 Service Unavailable means
A 503 says the service is temporarily down or under maintenance. It is another transient, retryable condition. The playbook matches 500 and 529: exponential backoff, respect for retry-after when it is present, and a graceful give-up after a sane number of attempts.
Remember the trio as one: 529, 500, 503 = retry. The causes differ (overload, internal fault, unavailability) but your response does not. One piece of retry logic should catch all three.
Where the 429 rate limit error comes from
A 429 Too Many Requests means you exceeded your requests-per-minute, tokens-per-minute or daily token budget. Unlike the 5xx family, this one is about your usage, and the API tells you how long to wait in the retry-after header.
{
"type": "error",
"error": {
"type": "rate_limit_error",
"message": "Number of requests has exceeded your per-minute limit."
}
}
When retry-after is present, respect it. Measure token usage, cache repeated context where supported, and cap the request rate in your client. Gateway access does not create independent quotas. We cover client queues and provider quota handling in the Claude API rate limits guide.
Haiku is in the catalog too: claude-haiku-5.5 and claude-haiku-4.5, with prices on the models page. They run in the Premium channel, not in the Cheap Haiku channel.
What causes a 400 Bad Request
A 400 means the request itself is malformed. The API rejected it before doing any work, and retrying the identical request will fail every time. This is a code bug, not a transient hiccup. Stop, read the message, fix the payload.
The most common causes, in rough order of frequency:
| Cause | What it looks like | Fix |
|---|---|---|
Missing max_tokens | max_tokens: Field required | Always send max_tokens: it is mandatory on the Messages API |
| Bad JSON | invalid_request_error on a request that "looks fine" | Validate the JSON, watch for trailing commas and unescaped quotes |
| Wrong role order | messages: roles must alternate | Roles user / assistant must alternate, starting with user |
| Empty messages array | messages: at least one message required | Send at least one message |
max_tokens too high | max_tokens exceeds model maximum | Keep it under the model's output ceiling |
| System prompt in messages | role system rejected | Pass the system prompt in the top-level system field, not as a message |
Here is a 400 from forgetting max_tokens:
{
"type": "error",
"error": {
"type": "invalid_request_error",
"message": "max_tokens: Field required"
}
}
And the correct request in curl, with everything in place:
curl https://api.codexkey.online/v1/messages \
-H "x-api-key: $CODEXKEY_KEY" \
-H "anthropic-version: 2023-06-01" \
-H "content-type: application/json" \
-d '{
"model": "claude-sonnet-5",
"max_tokens": 1024,
"messages": [
{"role": "user", "content": "Hello, Claude."}
]
}'
When you get a 400, log the full error.message. It is specific and usually points straight at the broken field. Don't bury it.
What 401 and 403 errors mean
A 401 Unauthorized means the key is missing, malformed or invalid. A 403 Forbidden means the key is valid but is not allowed to do what you asked. Neither is worth retrying: fix the credentials or permissions and move on.
Checklist for 401:
- Is the key actually sent? Look at the
x-api-keyheader (orAuthorization: Beareron the OpenAI-compatible endpoint). - Did an environment variable load as an empty string?
- Are the key and the base URL mismatched? An Anthropic
sk-ant-…key won't authenticate against a different base URL, and a codexkeysk_cdx_…key targetshttps://api.codexkey.online. - Did a space or newline sneak into the key? It happens more often than you would think.
With a 403, the key works but lacks access: for example, you asked for a model your account or plan does not have, or hit an endpoint outside your permissions. Read the message, since it names the restriction.
{
"type": "error",
"error": {
"type": "authentication_error",
"message": "invalid x-api-key"
}
}
The 413 Request Too Large error
A 413 means the payload is bigger than the API will accept, usually because you stuffed the context window or attached oversized images or documents. As it stands the request is not retryable, and you have to shrink it.
Common triggers and what to do about them:
- Context overflow: you are pushing past the model's input limit. Trim history, summarize old turns, or split the work. Tactics for keeping payloads lean are in our post on saving on the Claude API.
- Large images or PDFs: downscale images and split big documents before sending.
- Runaway message history: in a chat loop, prune or summarize earlier messages instead of appending forever.
If you keep bumping into size limits, treat it as a design signal: move to retrieval (send only the relevant chunks) rather than dumping everything into every call.
Timeouts and connection errors: living with them
Timeouts (408), dropped connections and read timeouts are transient network problems, not API rejections. They are retryable, and they show up most on long generations, where the response takes a while to stream back.
Two things matter. First, a sane client timeout: too short and you abort valid long responses, too long and a stuck connection hangs your worker. Second, prefer streaming for long outputs. Tokens start arriving immediately, so the chance of an idle timeout drops sharply.
import anthropic
# Generous timeout for long generations; stream to avoid idle timeouts
client = anthropic.Anthropic(timeout=120.0)
with client.messages.stream(
model="claude-sonnet-5",
max_tokens=4096,
messages=[{"role": "user", "content": "Write a long report."}],
) as stream:
for text in stream.text_stream:
print(text, end="", flush=True)
Connection errors should land in the same retry bucket as 529/500/503. The implementation below catches them.
The complete status code reference
Keep this table next to your error handler. The "Action" column is the whole game: retry the transient errors, fix the rest.
| Code | Meaning | Retryable? | Action |
|---|---|---|---|
| 400 | Bad request (malformed payload) | No | Fix the request: max_tokens, JSON, role order |
| 401 | Authentication failed | No | Check the API key and headers |
| 403 | Forbidden (no permission) | No | Check model/endpoint access |
| 408 | Request timeout | Yes | Retry; raise client timeout; stream |
| 413 | Request too large | No | Shrink context, images, history |
| 429 | Rate limit exceeded | Yes | Backoff + honor retry-after |
| 500 | Internal server error | Yes | Backoff and retry |
| 503 | Service unavailable | Yes | Backoff and retry |
| 529 | Overloaded | Yes | Backoff with jitter; reduce concurrency |
Exponential backoff with jitter: production retry code
Every Claude integration needs this code. It retries the transient errors (429, 500, 503, 529, timeouts, connection drops), honors retry-after when present, adds jitter so clients don't synchronize, and, most important, does not retry the errors that are your bug (400, 401, 403, 413).
Python:
import anthropic
import time
import random
RETRYABLE_STATUS = {408, 429, 500, 503, 529}
def call_with_retry(
client: anthropic.Anthropic,
*,
max_retries: int = 6,
base_delay: float = 1.0,
max_delay: float = 60.0,
**kwargs,
) -> anthropic.types.Message:
for attempt in range(max_retries):
try:
return client.messages.create(**kwargs)
except (anthropic.APIConnectionError, anthropic.APITimeoutError) as e:
if attempt == max_retries - 1:
raise
_sleep(base_delay, max_delay, attempt, None)
except anthropic.APIStatusError as e:
status = e.status_code
# Don't retry client errors you caused — fix the request instead.
if status not in RETRYABLE_STATUS or attempt == max_retries - 1:
raise
retry_after = e.response.headers.get("retry-after") if e.response else None
_sleep(base_delay, max_delay, attempt, retry_after)
raise RuntimeError("unreachable")
def _sleep(base: float, cap: float, attempt: int, retry_after) -> None:
if retry_after:
delay = float(retry_after)
else:
delay = min(cap, base * (2 ** attempt))
# Full jitter: pick a random point in [0, delay].
time.sleep(random.uniform(0, delay))
TypeScript:
import Anthropic from "@anthropic-ai/sdk";
const RETRYABLE = new Set([408, 429, 500, 503, 529]);
const sleep = (ms: number) => new Promise((r) => setTimeout(r, ms));
export async function callWithRetry(
client: Anthropic,
params: Anthropic.MessageCreateParamsNonStreaming,
{ maxRetries = 6, baseDelay = 1000, maxDelay = 60000 } = {},
): Promise<Anthropic.Message> {
for (let attempt = 0; attempt < maxRetries; attempt++) {
try {
return await client.messages.create(params);
} catch (err) {
const last = attempt === maxRetries - 1;
if (err instanceof Anthropic.APIConnectionError) {
if (last) throw err;
} else if (err instanceof Anthropic.APIError && err.status) {
// 400/401/403/413 are your bug — surface them immediately.
if (!RETRYABLE.has(err.status) || last) throw err;
} else {
throw err;
}
const retryAfter = (err as Anthropic.APIError)?.headers?.["retry-after"];
const base = retryAfter
? parseFloat(retryAfter) * 1000
: Math.min(maxDelay, baseDelay * 2 ** attempt);
await sleep(Math.random() * base); // full jitter
}
}
throw new Error("unreachable");
}
A few design choices deserve an explanation. Full jitter (a random value in [0, delay]) prevents synchronized retries better than fixed exponential delays: the AWS architecture blog made this case years ago, and it still holds. The max_delay cap stops the backoff from ballooning into absurd waits. And separating retryable from non-retryable status codes is exactly what keeps a 400 from spinning forever.
Does a gateway help with 529 errors
A gateway changes the endpoint and account your app uses. That does not add request capacity or guarantee that provider overload errors disappear. Treat 529 Overloaded as a possible failure and use bounded retries for retryable errors.
In codexkey a key can select Premium or Cheap, with an optional Premium fallback for Cheap under provider policy. Verify model and channel availability before you send traffic. Provider availability and policies still apply: the key settings do not give you separate pooled capacity or automatic rerouting, and they do not suppress upstream 529 errors. Your client has to handle these failures.
Switching is a one-line change, with the same SDKs and the same request shapes:
import anthropic
# Same Anthropic SDK, pointed at the gateway.
client = anthropic.Anthropic(
api_key="sk_cdx_your_key",
base_url="https://api.codexkey.online",
)
response = client.messages.create(
model="claude-sonnet-5",
max_tokens=1024,
messages=[{"role": "user", "content": "Hello"}],
)
import Anthropic from "@anthropic-ai/sdk";
const client = new Anthropic({
apiKey: "sk_cdx_your_key",
baseURL: "https://api.codexkey.online",
});
There is also an OpenAI-compatible endpoint, so Cursor, Claude Code and OpenCode plug in unchanged. Billing is pay-per-token with no subscription: Sonnet from $0.33/1M tokens, Opus from $0.50/1M, tracking Anthropic's rates, plus gpt-5.6-luna in the catalog for cheap, high-volume work. You can pay via SBP (the Russian Faster Payments System), Russian cards, crypto (USDT/BTC/ETH) or MTS. See pricing and the API reference for details, or accessing Claude from Russia in 2026 for the regional angle.
FAQ
Is the 529 Overloaded error my fault?
No. An Anthropic 529 indicates provider overload, not a malformed request. Use bounded client retries with backoff and jitter, and reduce concurrency during the spike. A gateway does not guarantee alternate capacity or suppress the error, so return an explicit failure when retries are exhausted.
Should I retry a 400 Bad Request error?
Never. A 400 means the payload is malformed, so an identical retry fails identically and you only waste calls. Read error.message, which usually names the broken field, and fix that. The usual culprits are a missing max_tokens, invalid JSON, or messages that don't alternate user/assistant and start with something other than user.
What's the difference between 429 and 529?
A 429 concerns the provider's quota policy: respect retry-after when present and slow the client workload. An Anthropic 529 indicates platform overload. Answer temporary failures with bounded retries and backoff, and report failure if they persist. A gateway does not guarantee failover to another capacity pool. The rate limits guide covers client-side controls.
How many times should I retry before giving up?
For transient errors, 5-6 attempts with capped exponential backoff is a sensible default. It rides out a brief spike without hanging a request for minutes. Always cap the per-attempt delay (say, 60 seconds) and give the caller a clear error once retries are exhausted, so a stuck request doesn't pass for a success.
Why do I keep getting 401 even though my key looks right?
The usual suspects: an environment variable that loaded as an empty string, a space or newline pasted into the key, the wrong header (x-api-key vs Authorization: Bearer), or a key/base-URL mismatch. An sk-ant-… key won't authenticate against a non-Anthropic base URL, and an sk_cdx_… key must target https://api.codexkey.online. Log the key's length and prefix (never the full value) to spot these fast.
Keep client retries bounded and tell temporary provider failures apart from invalid requests. Check model and channel availability: a gateway does not guarantee that overload errors disappear. Start with the quickstart. For help with an integration, contact us or write on Telegram: https://t.me/codex_key.