On 7 October Anthropic posted this to the Claude status page:
We have identified an issue which resulted in some organizations to be incorrectly paused for having reached their spend limit, resulting in refused requests on the Claude API, Claude.ai, Claude Code, Claude Security, and Claude Cowork. This is now resolved.
Claude status, “Issue with spend limits”
That’s the whole incident note. It was posted as resolved at 21:23 UTC with no earlier updates, no duration and no root cause. Six days before, purchased credits were slow to reach account balances, “resulting in some requests failing for insufficient credit balance”. That one ran from 16:20 UTC (investigating) to 22:36 UTC (resolved).
Neither was a model outage. The models were up and the API was answering. It was answering “no”, to organisations whose standing with the provider had gone wrong, and it would have said the same thing for every model they asked for. The usual fallback chain (try the small Claude model, then the bigger one) walks straight through a failure like that and comes out the other side with nothing.
This post is news analysis followed by design analysis with illustrative code. I haven’t run the code, I have no inside knowledge of either incident, and where the sources don’t say what callers saw I’ve marked my guesses as guesses. My model gateway post now has one paragraph on account failures as a fourth error class. This is the longer version, with a different example.
The running example: a receipt reader
Someone photographs a receipt, and a model fills in the expense claim: merchant, date, total, VAT. The route chain was written a while ago and is the kind that gets written once and left alone:
- Claude Haiku 5.5 (
claude-haiku-5-5) - Claude Sonnet 5.5 (
claude-sonnet-5-5) gpt-4.1-nanoon OpenAI, added as the “different provider” route and never looked at again- A blank form the person fills in by hand
Hold on to route 3. It matters later.
Three classes, sorted by reach
The gateway post sorted errors by whether to retry. That’s one question. The other is how much of your chain a failure has just taken out, and I think that one deserves its own field.
- A model failure takes out one route. One model is overloaded, rate limited or retired.
- A provider failure takes out every route at one provider. Their API is down or erroring across models.
- An account failure also takes out every route at one provider, but only for you. A spend limit, a credit balance, a key, a billing detail.
Provider and account failures reach the same distance, so why separate them? Because of what happens next. A provider outage ends when the provider fixes it, and their status page will tell you. An account failure may never end by itself, because the fix can be a person logging in to a billing console, and no status page is going to mention your expired card.
The incidents, classified
| Incident or failure | What the caller saw, per the source | Class | What the receipt reader’s chain does |
|---|---|---|---|
| Claude, 5 and 6 October: elevated errors for Mythos 5.1 and Fable 5.1 (impact 12:40 to 13:10 UTC), then for Opus 5.5 (opened 12:24, resolved 12:43 UTC) | “Elevated errors” on requests to the named models. No status codes given. | Model | Works as designed. This is the failure a model-to-model chain was built for. |
| OpenAI, 6 October: “Elevated errors across ChatGPT, Codex, and the API including the Agents API”, one of nine incidents in the feed that day | Around 30 components listed, Responses and Realtime among them. No status codes or cause given. | Provider | Route 3 fails. Harmless here, because routes 1 and 2 are elsewhere. A chain made only of OpenAI models has nowhere to go. |
| 3 September: OpenAI, Anthropic and xAI down during overlapping hours | From a secondary write-up, which finds no evidence of a shared cause. I haven’t checked it against the status pages. | Provider, twice | Routes 1 to 3 all fail. Only the blank form is left. |
| Claude, 1 October: delayed credits | “some requests failing for insufficient credit balance”. No status code given. My guess: the 402 billing_error in the errors doc. |
Account | Route 1 fails, route 2 fails the same way. Route 3 saves it, if route 3 works. |
| Claude, 7 October: organisations wrongly paused at their spend limit | “refused requests”. My guess: the documented spend-cap response, a 429 with no retry-after. Anthropic haven’t said. |
Account | Sees a 429 and retries route 1 with backoff. Then does it again on route 2. Route 3 answers in the end, after both backoffs. |
Cloudflare AI Gateway, from 6 October: provider rejects the credential on POST /ai/run |
401 with error code 2009. With Unified Billing, 503 instead. |
Account for the 401. I’d file the 503 under provider (my reading, below). | If the chain runs through the gateway: stops on the 401 if 401 is fatal, which abandons a working route 3. Retries the 503. |
| LiteLLM before v1.103.0: a fallback target the caller’s budget doesn’t cover | Nothing. As I read the notes, the request went through on the fallback. | Account (your own limit) | Falls back to a paid model past the budget that was meant to stop it. |
OpenAI, 23 October: legacy snapshots shut down, gpt-4.1-nano among them |
Hasn’t happened yet. The page says the model “will no longer be accessible”. My guess: a not-found error. | Model, permanent | Nothing, until the day routes 1 and 2 are both out. Then route 3 fails too. |
The pattern in the last column is that a chain ordered by model does the right thing for exactly one row.
What each class should trigger
Model failure. Retry if the signal is transient (overload, a rate limit with retry-after), then switch route. Staying on the same provider is fine. If it’s permanent (the model ID doesn’t exist), don’t retry, take the route out of the policy and open a ticket. Nobody needs waking up.
Provider failure. Skip every remaining route on that provider and go to the next provider or the degraded mode. Alert if it lasts. There’s nothing for a person to do except watch the status page, so I wouldn’t page for it unless the degraded mode is unacceptable.
Account failure. Don’t retry. Skip the provider. Page a human now, with the provider, the error body and the request ID in the page. This is the only class where a person can fix it and waiting can’t.
That last one has a dependency that’s easy to miss: the person paged has to be able to do something. If on-call can’t open the provider’s billing page, top up credits or raise a limit, the page wakes someone up to read an error. I’d check that before I wrote any of the code.
For the receipt reader, 7 October should have gone: one refused request on route 1, skip route 2 without trying it, serve from route 3, page. One wasted call per request until a provider-wide breaker opens, and then none.
Status codes don’t hand you the class
It’s tempting to read the class off the HTTP status. You can’t, and the docs are quite open about it.
Anthropic’s rate limits page says that at your tier’s monthly spend cap, requests return a 429 whose error type is rate_limit_error, “the same as for a rate limit, but the response has no retry-after header”. The field that tells them apart is error.details.error_code, set to enforced_spend_limit_reached. A spend limit you set yourself is different again: a 400 invalid_request_error whose message begins You have reached your specified API usage limits. Most retry code treats a 400 as “the request is wrong, give up”, which is the right amount of retrying and the wrong amount of telling anyone.
OpenAI’s error codes guide lists several 429s that are about the account: credit_balance_exhausted, organization_spend_limit_exceeded, project_spend_limit_exceeded and organization_usage_limit_exceeded. They sit beside the ordinary 429 whose advice is to follow Retry-After.
Then there’s the gateway in between. Cloudflare’s 6 October change makes a rejected provider credential on POST /ai/run a 401 with error code 2009. Before that, the changelog’s own table says, Google Vertex returned a 500 “with upstream retries” and other providers a 402 “or another provider-specific status”. The exception is Unified Billing, where a rejected credential is now a 503. I read that as deliberate: under Unified Billing the credential is Cloudflare’s, so it isn’t your account that’s broken. The changelog doesn’t say whether that 503 is safe to retry, and I haven’t tested it.
So the classifier has to read the body. Here’s the shape I’d start from (illustrative code, unrun, covering only the cases above):
type Scope = "model" | "provider" | "account";
type Failure = { scope: Scope; retry: boolean; page: boolean };
type ProviderError = {
status: number;
code?: string; // Anthropic error.details.error_code, OpenAI error.code
message?: string;
};
const ACCOUNT_CODES = new Set([
"enforced_spend_limit_reached", // Anthropic tier spend cap (429)
"credit_balance_exhausted", // OpenAI (429)
"organization_spend_limit_exceeded", // OpenAI (429)
"project_spend_limit_exceeded", // OpenAI (429)
"organization_usage_limit_exceeded", // OpenAI (429)
]);
export function classify(e: ProviderError): Failure {
const account: Failure = { scope: "account", retry: false, page: true };
if (e.code && ACCOUNT_CODES.has(e.code)) return account;
if ([401, 402, 403].includes(e.status)) return account;
if (e.status === 400 && e.message?.startsWith("You have reached your specified")) {
return account; // Anthropic: a spend limit you set
}
if (e.status === 404) { // guess: what a retired model ID returns
return { scope: "model", retry: false, page: false };
}
if (e.status === 429 || e.status >= 500) {
return { scope: "model", retry: true, page: false };
}
throw new FatalRequestError(e); // other 400s: our request is wrong
}
Two things about it. Availability errors start as model failures, because one response can’t tell you whether the whole provider is down. The calling loop promotes them: if two different routes at the same provider fail with availability errors inside one request, skip the rest of that provider. And an unknown 4xx never becomes an account failure by default, or a bug in a prompt template would page someone every night.
The string match on the 400 is ugly and will break when the wording changes. I’d still rather have it and a test that fails loudly than treat a spend limit as a malformed request.
Your gateway’s budgets are account state too
The account doesn’t have to be the provider’s. If you run a proxy with per-team budgets, that’s a spend limit you operate, and it has the same failure modes in both directions.
LiteLLM v1.103.0, released on 28 September, lists “fix!: re-check budget on router fallback targets”. The release notes put it as “Budgets are re-checked on every router fallback target”, and explain that a request that starts on a free model and falls back to a paid one is now gated at the fallback. I take that to mean it wasn’t before: the fallback chain was a way round the budget. v1.103.3 fixes streamed chat completions through a dotted alias recording $0 spend, because pricing looked up the alias and not the underlying model. A budget can only refuse what the spend log has counted.
In reconciliation work the rule was that a control has to sit on every path to the ledger, including the correction path nobody uses. A fallback route is that path. I haven’t tested LiteLLM for this post, so this is a reading of release notes, but I’d check which version produced any spend numbers I was relying on.
Before 23 October: check every route ID
Back to route 3. OpenAI’s deprecations page has a notice dated 22 April 2026 with a long list of snapshots shutting down on 23 October 2026. gpt-4.1-nano is on it, with gpt-5.6-luna as the recommended replacement. So are gpt-4-turbo, gpt-4o-2024-05-13, o1, o3-mini and o4-mini. The page says OpenAI emails customers who are “actively using the model”. A fallback route that never fires isn’t active use, so I wouldn’t count on that email arriving for route 3.
Four checks, cheapest first:
- Search the config. Grep every route policy, env file and eval baseline for the IDs on the 23 October list. Fine-tunes of the listed base models are on the page too.
- Ask the API. OpenAI’s
GET /models“lists the currently available models”, and the reference shows an optionalshutdown_dateon each one. A CI job can fail when a route ID is missing from the list or has a date set. I haven’t checked how reliably that field is populated. - Do the same for Anthropic. The model deprecations page has
claude-sonnet-4-5-20250929retiring on 30 November 2026 and says plainly that “requests to retired models will fail”. It also describes a usage export, by API key and model, from the Console. - Run a route canary. Once a day, send one tiny real request down every route, fallbacks included, with each route’s real key. This is the only check that also catches an expired key, an empty credit balance and a paused organisation on a provider you rarely call. It costs a few billed requests.
Then swap route 3 for the replacement and run the receipt eval set against it before the date. A replacement that has never seen your schema is a new route.
What I’d do on Monday
- Grep for the 23 October IDs in every route, fallback and eval config, and replace them this week.
- Add a scope to your error type: model, provider or account, separate from whether to retry.
- Classify from the body. Handle Anthropic’s
enforced_spend_limit_reachedand self-set limit 400, and OpenAI’s spend and credit 429 codes, as account failures. - Make account failures skip the provider and page, and confirm the person paged can reach the billing console.
- Start a daily route canary over every route with its real credentials.
- Check gateway behaviour: if you’re on Cloudflare AI Gateway, how your code treats 401 with
2009and the Unified Billing 503. If you’re on LiteLLM, whether your version checks budgets on fallback targets.
Limitations
This is design analysis. The code is a sketch and hasn’t been run, and the classifier covers only the error shapes I found in the docs on 13 October. Those docs will change.
The bigger gap is that I don’t know what either Claude incident looked like on the wire. Anthropic’s notes say “refused requests” and “insufficient credit balance” and nothing more, so the two rows that started this post rest on guesses about status codes. I also don’t know what a retired OpenAI model ID returns, and I’ll find out with everyone else on the 23rd. The 3 September row comes from a secondary write-up. And a post telling you to page a human about billing has no evidence that the human will be awake.
Sources
- Claude status: issue with spend limits (7 October 2026)
- Claude status: delayed credits on the Claude Platform (1 October 2026)
- Claude status history feed
- OpenAI status history feed
- Oliver Willis: September 2026 AI service outage explained
- Claude API errors
- Claude API rate limits and spend limits
- Claude model deprecations
- OpenAI API error codes
- OpenAI API deprecations
- OpenAI API reference: list models
- Cloudflare AI Gateway changelog
- LiteLLM v1.103.0 release notes
- LiteLLM v1.103.3 release notes
- LiteLLM v1.103.0 on GitHub