Back to Home
Gateway Practices9 min readSource: Admesh 技术团队

429, 5xx, and Timeouts: Engineering Practices for LLM Gateway Failover

Upstream rate limiting and failures are the norm, not the exception, for LLM applications. This article breaks down the correct handling for 429/5xx/timeout/401, when you can automatically switch channels, when you must never retry, and how to avoid double billing.

Classify first, then decide how to handle it

'Retry every error three times' is the most common wrong approach. Different error codes have completely different semantics, and the handling strategy must differ too. The table below is the strategy our gateway actually uses, and it can serve as a reference for your own implementation.

  • Connection failure / DNS / TLS: switch to the backup channel immediately.
  • Timeout (output has not started yet): switch to the backup channel, at most 1–2 times.
  • HTTP 429: cool down briefly, then switch channels, and lower that channel's weight at the same time.
  • HTTP 5xx: count it in the failure window; consecutive failures trigger the circuit breaker.
  • 401 / 403: credential or permission problem; disable that credential and alert, do not retry.
  • Insufficient balance: pause that channel and alert.
  • 400 parameter error: after normalization, return it directly to the customer; do not retry, do not bill.
  • SSE interrupted after partial output: return upstream_stream_error; by default do not retry.

Why an interrupted SSE stream cannot simply be retried

Once a streaming response has started outputting tokens to the client, it means the upstream has already produced output (and will bill for it). If the gateway quietly switches to another channel and starts over, the user sees duplicated content, and you pay for both outputs.

The correct approach is to treat this half-finished stream as a partial success: return an error code telling the client 'upstream interrupted', and let the business side decide whether to show the existing content plus a retry prompt, or start over entirely.

Circuit breaker: the layer more important than retries

Retries handle a single request; the circuit breaker judges the overall state over a period of time. When a channel's consecutive failures reach a threshold, mark it 'open', stop sending traffic to it for a while, then let a small amount of probe traffic through (half-open), and after several successful probes fully restore it (closed).

Without a circuit breaker, retries will crush an already-dead channel even harder and cause the customer's average latency to spike.

Retry idempotency and billing

  • Non-streaming requests: after retries, bill only the usage of the final successful attempt.
  • Streaming requests: the attempt that already output tokens really incurs upstream cost and must be recorded; you cannot wipe the ledger clean just because of a retry.
  • Assign a request_id to each user request; all retries share it, so you can fully replay later 'why this channel was chosen'.
  • Refunds/adjustments must go through ledger entries; you cannot directly change the balance.

What Admesh already does

The strategy above is the default behavior in the Admesh gateway: automatic switching among multiple upstream channels for the same public_model, every routing decision is persisted, and customers can see request_id, the model actually used, and channel health in their usage records; error codes are returned in a unified format (error.type / error.code / request_id).

It is worth noting: routing only happens within the channel pool of the same public_model; it will not switch you to another vendor's model just because you requested anthropic/xxx—cross-model substitution directly changes output quality, and that is a decision we cannot make for you.

Admesh live rates (excerpt)

Prices come from the live Admesh configuration, in USD per million tokens. See the model catalog for the full list.

Admesh.ai Model Price List: Prices and Context Windows for 8 Enabled Models
Model Name and IDContext WindowEnter price / 1MOutput Price / 1MCache Hits / 1M
Claude Fable 5anthropic/claude-fable-5
1M$3.25 / 1M$16.25 / 1M$0.325 / 1M
Claude Fable 5.1anthropic/claude-fable-5-1
1M$3.25 / 1M$16.25 / 1M$0.08125 / 1M
Claude Haiku 4.5anthropic/claude-haiku-4-5-20251001
200K$0.325 / 1M$1.625 / 1M$0.0325 / 1M
Claude Haiku 5.5anthropic/claude-haiku-5-5
1M$0.0325 / 1M$0.1625 / 1M$0.00325 / 1M
Claude Opus 4.5anthropic/claude-opus-4-5-20251101
200K$1.625 / 1M$8.125 / 1M$0.1625 / 1M
Claude Opus 4.6anthropic/claude-opus-4-6
1M$1.625 / 1M$8.125 / 1M$0.1625 / 1M
Claude Opus 4.7anthropic/claude-opus-4-7
1M$1.625 / 1M$8.125 / 1M$0.1625 / 1M
Claude Opus 4.8anthropic/claude-opus-4-8
1M$1.625 / 1M$8.125 / 1M$0.1625 / 1M

The above are the standard unit prices for public channels; billing is based on actual token usage. View the complete model catalog

View the error code reference →

FAQ

On a 429, should I sleep or switch models?
Neither is ideal. The correct approach is to switch to another channel of the same model, or queue and wait according to the retry time in the response headers. Switching models directly changes output quality; blindly sleeping slows down overall throughput.
What is a suitable timeout value?
Look at it in two parts: the connection timeout should be very short (a few seconds), because a failure at the connection stage can be switched immediately; the overall timeout depends on the task, and long-text generation may take tens of seconds. Do not use one value to govern both.
Will retries cause duplicate charges?
Not on Admesh: non-streaming requests are settled based on the usage of the final successful attempt; for streaming requests, the part that has already produced output is billed truthfully, and you will not be charged twice because of a channel switch. All settlement is based on ledger entries, which you can check one by one on the usage page.

References

Call these models through one gateway

Admesh provides a unified OpenAI-compatible API: one Base URL and one key for Claude, GPT, DeepSeek and more. New accounts get trial credit that never expires.