Back to posts
AINews

Build a fallback path for when Claude or OpenAI's API goes down

Claude had two separate outages inside 24 hours this week, confirmed on status.claude.com with exact timestamps. Most apps calling the API directly went down with it. Here is what is retryable, which response headers tell you when to back off, and the fallback chain that keeps a product running through the next one.

Claude had two separate outages inside 24 hours this week. The first ran from 19:49 to 22:36 UTC on July 29 — elevated error rates across every model, resolved in about three hours. The second started at 05:57 UTC on July 30 and rolled through the model lineup one at a time: Opus 5 recovered by 06:26, Sonnet 5 spiked and recovered by 08:06, Fable 5 spiked next. Both are documented with timestamps on status.claude.com. OpenAI's own status page carries the same shape of history — a 503 labeled "The engine is currently overloaded, please try again later" is a documented, expected response code, not an edge case.

If your product calls one provider directly with whatever retry behavior the SDK ships by default, that outage was your outage too. The fix is not "add a try/catch." It is knowing which errors are worth retrying, reading the headers that tell you how long to wait, and having a second path ready before you need it.

Not every error is worth retrying

Both providers document the same split: some errors mean "wait and try again," others mean "this request will never succeed, stop retrying immediately."

Claude API (documented error types):

CodeTypeRetry?
429rate_limit_errorYes — wait for the window to reopen
500api_errorYes — Anthropic's own docs say retry with exponential backoff
529overloaded_errorYes — temporary, capacity-related
504timeout_errorYes, or switch to streaming
400invalid_request_errorNo — fix the request
401 / 403auth / permissionNo — fix credentials

OpenAI API (documented error codes):

CodeMeaningRetry?
429Rate limit reached for requestsYes, with backoff
429Credit balance / spend limit exhaustedNo — billing problem, retrying does nothing
500Server errorYes, after a brief wait
503Engine overloadedYes, exponential backoff
503"Slow Down" (traffic-spike throttling)Yes — but drop to your original request rate for at least 15 minutes first, per OpenAI's own guidance, or the throttle re-triggers

The distinction matters because a blind "retry everything three times" loop burns your rate limit budget on the 401s and 400s that were never going to succeed, while giving up too early on the 529s that just needed forty more seconds.

Both official SDKs already retry transient failures for you — Anthropic's SDKs retry twice by default with exponential backoff, honoring the retry-after header when present. Two retries covers a blip. It does not cover a three-hour outage. Check what your framework does before assuming it is handled: if you are calling through a custom HTTP client, a queue worker, or a wrapper library, the built-in retry logic may not be in the path at all.

Read the rate limit headers instead of guessing the backoff

Anthropic's rate limit responses carry a retry-after header plus a full set of anthropic-ratelimit-* headers showing exactly how much budget remains and when it resets:

{
  "retry-after": "13",
  "anthropic-ratelimit-requests-remaining": "0",
  "anthropic-ratelimit-tokens-remaining": "1250",
  "anthropic-ratelimit-tokens-reset": "2026-07-30T14:02:11Z"
}

Read retry-after and sleep exactly that long instead of hardcoding a backoff constant. On a real overload, Anthropic's own systems know the recovery time better than a guessed 2^attempt curve does. OpenAI's guidance is close but less prescriptive: "implement a backoff mechanism or retry logic that respects the response headers and the rate limit" — no documented retry-after header contract, so exponential backoff with jitter is the safer default there.

Check the status page before you burn a retry budget

Both providers run public status pages with a machine-readable endpoint — no auth required:

curl -s https://status.claude.com/api/v2/status.json
# {"status":{"indicator":"none","description":"All Systems Operational"}}

curl -s https://status.openai.com/api/v2/status.json
# {"status":{"indicator":"none","description":"All Systems Operational"}}

indicator moves through noneminormajorcritical. Wire a check against this into your circuit breaker: if the indicator is anything but none for the provider you are about to call, skip straight to the fallback path instead of spending three retries finding out the hard way. It is one HTTP call, cacheable for 30–60 seconds, and it turns "wait for a timeout" into "know immediately."

The actual fix: a fallback chain, not a longer retry loop

Retries buy you seconds. A fallback buys you the outage. The pattern: try the primary model, catch the retryable errors specifically, fall through to a secondary model or provider, and log which path served the request so you can see how often the fallback fires.

import anthropic

client = anthropic.Anthropic(max_retries=2)

FALLBACK_CHAIN = [
    ("claude-sonnet-5", client),
    ("claude-opus-5", client),   # different capacity pool, same provider
]

def call_with_fallback(prompt: str) -> str:
    last_error = None
    for model, c in FALLBACK_CHAIN:
        try:
            msg = c.messages.create(
                model=model,
                max_tokens=1024,
                messages=[{"role": "user", "content": prompt}],
            )
            return msg.content[0].text
        except (anthropic.RateLimitError, anthropic.InternalServerError) as e:
            last_error = e
            continue
    raise last_error

Note the second rung uses a different model, not just a retry of the same one — Anthropic's rate limits and the July 30 outage both applied per model class, so Sonnet 5 being overloaded does not mean Opus 5 is. A true cross-provider fallback (Claude → GPT-5.6) needs a second SDK client and a shared response interface, which is more code to own.

The faster path to the same result is routing through an aggregator that already implements fallback as a request parameter. OpenRouter supports a models array — an ordered priority list where, per its own routing docs, "if the first model returns an error, OpenRouter will automatically try the next model in the list," triggered specifically by rate-limiting, downtime, context-length errors, or moderation flags. If you are already calling Claude through the Messages-compatible endpoint, the equivalent parameter is fallbacks: [{ model: "model-id" }]. One request, one array, no custom retry code to maintain — the tradeoff is an extra network hop through OpenRouter's own infrastructure and its per-token routing fee on top of the underlying model's price.

What to change today

  1. Stop treating all errors the same. Retry 429/500/529/503/504. Do not retry 400/401/403 or a billing-exhausted 429 — fix those, do not loop on them.
  2. Read retry-after on Claude responses instead of a fixed backoff constant.
  3. Add a status-page pre-check (status.claude.com/api/v2/status.json, status.openai.com/api/v2/status.json) to your circuit breaker so you skip to fallback instead of discovering the outage through three failed retries.
  4. Wire at least one fallback rung — a second model on the same provider is a five-line change; a second provider via OpenRouter's models array is close to a one-line change if you route through it already.

None of this prevents the next outage. It changes what your users see when it happens: a slightly slower response from the fallback model, instead of a blank screen for three hours.

Get the next post when it ships

One email on Sunday with the new post and a short list of what shipped that week — new guides, tool updates, and a couple of links worth reading.