Skip to content
All posts
·7 min readerrorstutorialdebuggingapi

Claude API Error Codes: Fix 401, 429, 529 Fast

Every Claude API error code (4xx and 5xx) explained with causes and fixes. Plus how OpenAI-compat errors map. Real-talk debugging for vibe coders.

When Your Claude API Call Just Won't Work

You hit run, got a wall of red, now what.

Claude API errors are consistent and most have obvious fixes once you know what they mean. This post lists the codes you'll actually run into, the cause, and the fix.

If you're using aiapi.cheap, the same errors show up — we mirror Anthropic's response format faithfully. We also support OpenAI-format calls (one key for Claude, GPT, Gemini, Grok, DeepSeek), and at the bottom there's a quick map of how the two error styles relate.

For the official spec, Anthropic's error reference is the source of truth.

Error Code Cheat Sheet

| Code | Name | What it means | First thing to check |

| --- | --- | --- | --- |

| 400 | Bad Request | Your request body is broken | Required fields, JSON shape |

| 401 | Unauthorized | API key missing or wrong | Header name, key value, env var |

| 403 | Forbidden | Key valid but not allowed for this | Plan tier, model permission |

| 404 | Not Found | Wrong URL or unknown model name | Model ID typo, endpoint path |

| 413 | Payload Too Large | Request body is too big | Token count, attachment size |

| 429 | Too Many Requests | You're hitting limits | Backoff, slow down, upgrade plan |

| 500 | Server Error | Something broke upstream | Retry with backoff |

| 529 | Overloaded | API is swamped right now | Retry with backoff, wait longer |

400 — Bad Request

Cause: Your request body is malformed.

Common reasons:

  • Missing model or messages field
  • max_tokens missing, zero, or negative
  • messages array is empty
  • Role other than user or assistant
  • Trailing comma or syntax error in JSON
  • Fix: Log the full error body. Anthropic returns error.message describing what's wrong. 90% of 400 errors are obvious once you read that field.

    try:
        response = client.messages.create(...)
    except anthropic.BadRequestError as e:
        print(e.body)  # exact reason is here
        raise

    Validate locally before sending — at minimum, check that model, max_tokens, and messages are all set.

    401 — Unauthorized

    Cause: API key is missing, wrong, or revoked.

    Common reasons:

  • Forgot the x-api-key (Anthropic) or Authorization: Bearer (OpenAI) header
  • Typo in the key — extra space, missing character, copy-paste cut off
  • Wrong environment (staging vs prod)
  • Key was rotated and you're still using the old one
  • Fix: Print the key prefix (never the full value) to confirm what your code sends:

    import os
    key = os.environ.get("ANTHROPIC_API_KEY", "")
    print(f"prefix: {key[:8]}... len: {len(key)}")

    If the prefix isn't sk-aic- (aiapi.cheap) or sk-ant- (direct Anthropic), wrong env var. Regenerate if the key is genuinely invalid.

    403 — Forbidden

    Cause: Key valid, but you're not allowed to do the thing.

    Usually a model not enabled on your tier, an account flag, or a feature not on your plan.

    Fix: Read the error message — it tells you what's restricted. Switch model or upgrade plan. On aiapi.cheap, all 5 vendors work on both plans, so 403 is rare and usually a model name typo.

    404 — Not Found

    Cause: URL or model doesn't exist. Usually a typo in the model ID or a deprecated model name.

    Fix: Check the model overview or the docs for current model names.

    413 — Payload Too Large

    Cause: Request bigger than the API will accept (long context, huge images, giant tool defs).

    Fix: Trim the prompt, split documents, downsize images, or switch to a model with a bigger context window.

    429 — Too Many Requests

    Cause: You're hitting fair-use limits. Most common runtime error. Not a bug — a guardrail.

    Usually a burst of parallel requests, multiple services sharing one key, or a script firing as fast as it can.

    Fix: Exponential backoff. Don't retry instantly. See the rate limits post for the full retry pattern with Retry-After handling. If 429s persist, batch smarter or upgrade plan tier — Pro has more headroom than Basic.

    500 — Internal Server Error

    Cause: Something broke upstream. Not your fault. Transient infrastructure hiccup or model timeout.

    Fix: Retry with exponential backoff (1s, 2s, 4s). If it persists across many retries with different inputs, the issue may be vendor-side — check status pages. Don't retry tight in a loop or you'll turn a glitch into a thousand-error incident.

    529 — Overloaded

    Cause: Model capacity saturated. Anthropic-specific.

    Fix: Same as 500 but be more patient. Wait longer between retries. If it persists, switch model — Sonnet often has more headroom than Opus during busy periods. On aiapi.cheap, this is a good reason to have a fallback ladder: try Claude, fall back to GPT or Gemini.

    Retry Pattern That Works

    A single backoff loop that handles 429, 500, and 529 cleanly:

    import time
    import random
    import anthropic
    
    client = anthropic.Anthropic(
        api_key="sk-aic-your-key-here",
        base_url="https://aiapi.cheap/api/proxy",
    )
    
    RETRYABLE = (
        anthropic.RateLimitError,
        anthropic.InternalServerError,
        anthropic.APIStatusError,  # catches 529 etc.
    )
    
    def safe_call(messages, model="claude-sonnet-4-6", max_attempts=5):
        for attempt in range(max_attempts):
            try:
                return client.messages.create(
                    model=model,
                    max_tokens=1024,
                    messages=messages,
                )
            except RETRYABLE as e:
                if attempt == max_attempts - 1:
                    raise
                # Honor Retry-After if the API gave us one
                wait = getattr(e, "retry_after", None)
                if wait is None:
                    wait = (2 ** attempt) + random.uniform(0, 1)
                print(f"Retry {attempt + 1}/{max_attempts} after {wait:.1f}s")
                time.sleep(wait)

    Three things this does right:

    1. Distinguishes retryable from non-retryable. A 401 won't get better with retries — fail loudly.

    2. Honors the server's hint when Retry-After is sent.

    3. Adds jitter so a fleet of clients doesn't retry in lockstep.

    OpenAI-Format Errors on aiapi.cheap

    If you use the OpenAI SDK on our /v1/chat/completions endpoint (routes to all 5 vendors via the model field), errors come back OpenAI-style. The codes map cleanly:

    | HTTP code | Anthropic-style | OpenAI-style |

    | --- | --- | --- |

    | 400 | BadRequestError | BadRequestError |

    | 401 | AuthenticationError | AuthenticationError |

    | 403 | PermissionDeniedError | PermissionDeniedError |

    | 404 | NotFoundError | NotFoundError |

    | 429 | RateLimitError | RateLimitError |

    | 500 | InternalServerError | APIError |

    | 529 | APIStatusError (529) | APIError |

    Rule of thumb: status code is what matters. The class name varies, but e.status_code is the same on both sides. Build retry logic around the status code, not the exception class, and your code works across both formats.

    Debugging Habits That Pay Off

  • Log the full error body, not just the code
  • Print the request payload when debugging — half of 400s are obvious typos
  • Don't retry 4xx except 429. Bad input doesn't fix itself
  • Both anthropic and openai SDKs have retry hooks built in — configure them
  • Add a request ID to your logs so you can trace what failed
  • Quick Recap

  • 400 = your input is wrong. Read the message, fix the input.
  • 401 = key issue. Check the env var.
  • 403 = not allowed. Different model or tier.
  • 404 = wrong URL or model name.
  • 413 = too big. Trim it.
  • 429 = slow down. Backoff with jitter.
  • 500 = server hiccup. Retry.
  • 529 = overloaded. Retry with longer waits.
  • Keep this open in a tab the next time something explodes.