Skip to main content
kRouter
All posts
Fix an error

429 is not one error: what kRouter does with each

A 429 can be a per-minute limit, a quota with a reset time, a daily cap or a monthly spend cap. How kRouter tells them apart and how long it waits.

Kodelyth · The team behind kRouter
· Updated
12 min read

Your coding agent stops on HTTP 429, and the status code is the least useful part of the response. The same three digits come back when you sent too many requests this minute, when a quota is used up until a reset time, when a free tier's daily allowance is gone, and, on Anthropic's API, when your organization has reached its monthly spend cap. A provider that is simply full answers with something close: 529, 503, sometimes a 429 of its own.

Those failures last from a second to a month. A router that treats them alike gets it wrong one way or the other: it keeps sending requests to an account that cannot answer until next month, or it parks a healthy account for hours because of a one-minute limit.

This post follows a failed chat request through kRouter 0.5.166: the order in which it checks the error, what it parks, and for how long. The short reference is the table in When kRouter moves on; how kRouter picks between your accounts in the first place is in Multiple accounts per provider.

Three decisions per failure

What to park. Most failures lock one model on one account, and the account keeps serving your other models. A few lock the whole account, because the limit is the account's and every model on it would fail the same way.

For how long. From two seconds to a month, occasionally longer.

Where the request goes now. Almost always somewhere else, at once: to the provider's next account, and when none is left, to the next entry of your combo. Four kinds of failure in the rule table below skip your other accounts, because every account on that provider would give the same answer: the model no longer exists, the prompt is too long, Google reports "no capacity available", or — new in 0.5.166 — the 4xx is about the request rather than about the credential. That last one still moves a combo on to its next entry, because that entry is a different model, which may accept what this one refused.

The order kRouter checks

kRouter asks these questions in order and stops at the first match. The order matters twice over. The two monthly holds sit near the top because the rate-limit rule further down used to claim their errors first. And 0.5.166 swapped the two in the middle: a reset time the provider names now outranks the 90-second per-minute guess, which used to override it whenever kRouter's cached quota looked healthy — including when that cache was simply cold and had nothing to say.

Decision tree: kRouter checks a GitHub Copilot monthly 402, an Anthropic spend cap or limit, OpenRouter's free daily cap, a reset time named by the provider, and a 429 worded as a rate limit that names no reset, in that order, before falling back to a rule table
Chat requests in kRouter 0.5.166. The first question that matches decides the scope and the length of the hold.

1. A monthly allowance: hold the whole account

Two monthly limits get rules of their own, and kRouter recognises them by their wording.

GitHub Copilot. A 402 saying "you've reached your additional usage limit for your plan" holds the account until 00:00 UTC on the 1st of next month. Any other GitHub 402 usually gets a two-minute cooldown on that model.

Anthropic spend limits. This rule covers the Anthropic provider, Claude Code, and any Anthropic-compatible provider you add. When an organization reaches its tier's monthly spend cap, Anthropic answers 429 rate_limit_error with error_code enforced_spend_limit_reached and no retry-after header. A spend limit you set yourself answers 400, with a message that begins "You have reached your specified API usage limits" (or "workspace API usage limits"). Both name the date access returns (Anthropic's rate limits page shows the exact shape).

kRouter holds the account until that date when it is in the future and no more than 32 days away, otherwise until 00:00 UTC on the 1st of next month. If the date has already passed, nothing is held and the error is handled like any other. This check comes before the reset-time rule further down, so a reset header pointing a few seconds ahead cannot cut a month-long hold short.

Before 0.5.164 the spend cap took the ordinary rate-limit path, costing a wasted round trip every few minutes for the rest of the month. After raising the limit in the Claude Console, click Test connection on the account to clear the hold at once. Anthropic rate limits explained covers the limits themselves.

2. OpenRouter's free daily cap: hold one model until midnight UTC

On kRouter's OpenRouter provider, a 429 that mentions free-models-per-day parks that model on that account until the reset time in the error body (OpenRouter's X-RateLimit-Reset). If there is none, or it is more than 25 hours away, the hold runs to the next 00:00 UTC; if it has already passed, kRouter tries again after a minute.

Paid models on the same key keep working. OpenRouter counts all free-model requests against one daily allowance, though, so each other :free model you send through that key fails once and is parked the same way. The per-minute free limit (free-models-per-min) falls through to the rules below. OpenRouter free tier 429 explains the limits themselves.

3. A reset time the provider names: until then, six hours at most

If the provider says when to come back, kRouter parks that model until then. It reads Retry-After (seconds or a date), Anthropic's anthropic-ratelimit-tokens-reset and anthropic-ratelimit-requests-reset, and the x-ratelimit-reset family, and takes the earliest reset among them. Codex's usage_limit_reached error, Antigravity's errors and Gemini CLI's RetryInfo.retryDelay carry the reset in the body instead, and kRouter reads it there; when Antigravity names no time, kRouter estimates one from the wording.

The hold is capped at six hours, so a wrong estimate cannot strand an account for days. An account that is out for a week comes back after six hours, fails once, and is parked again. The rule works for any status code, not only 429, but only on chat requests: the image, speech and embedding handlers do not pass a reset time.

Two ways of naming a reset were being thrown away before 0.5.166. Gemini CLI's was parsed out of the error and written to a field nothing downstream read, so a 429 that named its own wait fell through to the generic backoff. Every other kind was lost whenever the same message also read like a rate limit, which is what the reordering above fixes.

4. Worded like a rate limit, naming no reset: 90 seconds

A 429 whose text contains "rate limit", "too many requests" or "quota reached", and which named no reset time above, is treated as a per-minute limit: that model is parked for 90 seconds. Two things still veto it:

  • the message says the reset is hours or days away ("Resets in 3h", "resets in 2 days"), or
  • kRouter's cached quota for that model on that account is at 10% or less.

The second veto only exists where kRouter reads quota per model, Antigravity for example. For most providers there is no figure, and no figure counts as healthy, so the rule assumes a per-minute window. That is a sensible default on its own and a bad one in combination: until 0.5.166 it also fired when the provider had named a reset three hours out, so the model was retried, and refused, every 90 seconds until the real reset. A cold cache produced the same result on a provider that does report quota.

The reasoning behind the guess itself has not changed: a per-minute window has cleared after 90 seconds. When the guess is wrong, the cost is one failed request every 90 seconds until the real reset; that is what happened to OpenRouter's daily cap before it got its own rule.

5. Everything else: the rule table

What is left goes through a fixed table, text rules first and then the status code. A 429 that got this far, an error mentioning "overloaded" or "capacity", and kRouter's own "busy, try another" 503 all get exponential backoff on that model: 2 seconds, then 4, then 8, reaching the five-minute ceiling on the ninth such failure in a row. The backoff level belongs to the account, not the model, and goes back to zero on its next success.

A 401, 402, 403 or 404 gets two minutes on that model, and a 500 to 504 gets 30 seconds. Every other 4xx gets nothing at all. Since 0.5.166 a 4xx that matched no rule above is read as a complaint about the request — a malformed body, a parameter the provider does not take, a failed validation — so no lock is written and your other accounts are not tried; a combo still moves on to its next entry. The old 30-second default punished the account for the request's mistake, and on a single connection that meant every request in the next half-minute came back with a copy of the first error.

Three causes are exempt, named by their wording because they really are about the credential: Anthropic's empty credit balance, Gemini's invalid API key, and Gemini's "user location is not supported". Those still fail over to your next account and lock this one's model for two minutes. Cursor is exempt wholesale: everything its executor returns that is not a rate limit comes back as a 400, auth and plan failures included, so the status there says nothing about the cause and kRouter keeps the old default.

Two kinds of text rule take the whole account out. "Verify your account", suspension and deactivation messages lock it and mark it banned, so it stays out of rotation until a Test connection succeeds, even after its 24-hour lock has run out. Kiro's monthly-limit wording locks it for 30 days, longer if it happens again before a success; one of its phrases, "reached the limit", matches any provider's error, not only Kiro's. The combos docs list the remaining rules.

How long that is

The holds span six orders of magnitude, so the scale below is logarithmic. The top row is the one that is not a hold at all.

Bar chart on a log time scale: no lock at all for a 4xx about the request, backoff from 2 seconds to 5 minutes, 90 seconds for a 429 worded as a rate limit that names no reset, up to 6 hours for a reset named by the provider, up to a day for OpenRouter's free daily cap, up to a month for Anthropic's spend cap and GitHub Copilot's monthly limit
Each dot on the backoff bar is one more failure in a row. Pale bars depend on when the error arrives: a monthly cap reached on the 28th is held for a few days, not a month.

Apart from the banned mark, every lock expires on its own, and a successful Test connection clears every lock on the account early, along with that mark. Locks are written to kRouter's database, so restarting kRouter does not put a blocked account straight back into rotation.

Overloaded is not your limit

A 529 overloaded_error from Anthropic, or a 503 from a full provider, says nothing about your account; Anthropic's errors page calls the 529 a temporarily overloaded API. For most providers kRouter first retries a 502, 503 or 504 on the same endpoint two or three times, a few seconds apart; a 529 is not retried there. After that, "overloaded" and "capacity" errors get the backoff above, and plain 5xx errors 30 seconds.

kRouter then tries your next account on that provider, which rarely helps: it reaches the same busy service. The useful fallback is a combo entry on a different provider; Claude Code 529 Overloaded walks through that setup.

One 503 is kRouter's own and has nothing to do with the provider. Each account has a limit on how many requests it runs at once, and a request that waits for a free slot and does not get one is answered Account <id> busy, try another. That matched no rule until 0.5.166 and took the 30-second default, so a burst of parallel requests from one IDE could park every account for half a minute although the slots free up in seconds. It now takes the short backoff instead: 2 seconds, doubling.

An error saying "no capacity available", Google's wording, is the exception when it names no reset time. kRouter cools that model for five seconds and returns the error without trying your other accounts, on the reasoning that another account will not find spare capacity either. A combo stops there too, so the error reaches your tool. The no capacity available page has the details.

What your tool sees

When every account for that provider and model is already parked, kRouter does not call the provider at all. It answers 503, with a Retry-After header and a message ending in something like "(reset after 2h 14m)". Both times come from the earliest lock on those accounts, which can belong to another model, so read them as "not before then" rather than as a promise.

The 503 is new in 0.5.166. The reply used to carry whichever status had parked the accounts, which was worse than useless when that was a stale 401: Claude Code reads a 401 as a bad key and stops asking, though the key is fine and the account is only waiting. Embeddings, web search and web fetch answer the same way now.

The request that parks the last account is different: it gets that account's own error back, without a Retry-After. In a combo, the next entry usually takes the request either way; when every entry is parked, the combo passes on the earliest Retry-After of the lot. It used to drop that wait, because an account parked before the request arrives puts it in the header and nowhere else, and the combo only read the body.

Each account on the provider's page shows why it is parked and counts down to the moment it resumes. The countdown used to follow the earliest lock on the account, expired ones included — and only a success clears an expired lock, which an account on a month-long hold never gets. An account held until the 1st, sitting next to a two-second lock from last week, therefore showed no countdown at all. It now follows the latest lock still running. The Console Log page shows an [AUTH] line for each account kRouter locks and moves past.

What kRouter does not do

It does not raise a limit or wait out a reset for you; it only stops sending traffic to accounts that refused. It also cannot see a limit nobody describes: on a provider that reports no quota, a daily allowance whose error reads like a per-minute one and names no reset time gets the 90-second guess every time.

Common questions

Does kRouter honour Retry-After?

On chat requests, yes. A reset named in Retry-After or a reset header parks the model until then, for six hours at most, and since 0.5.166 it holds even when the same message also reads like a rate limit — that combination used to collapse to the 90-second guess. Two holds still override it, because they have a better date of their own: the monthly account holds and OpenRouter's free daily cap. The image, speech and embedding handlers do not read it at all.

Why is the whole account paused when only one model failed?

Because the limit belonged to the account: Anthropic's spend cap applies to the organization, Copilot's and Kiro's monthly limits to the account, and "verify your account" blocks the account until you act. Every other failure parks only the model that failed.

How do I clear a hold early?

Fix the cause first, such as raising the spend limit or verifying the account, then click Test connection on that account in the dashboard. A successful test clears every lock on it, including a month-long hold, and the banned mark a "verify your account" error leaves.

Signing in to that account again, or editing its key so the new one validates, also clears its per-model locks and resets the backoff — but not a running account-wide hold or the banned mark, which still need the test. Before 0.5.166 a fresh login left every lock in place, so the new credential went on being skipped.

Kodelyth · The team behind kRouter

Published by Kodelyth, the team that builds kRouter. Posts are drafted with AI assistance and reviewed by a person before they go out. kRouter is free and MIT licensed.

Install kRouter