Skip to main content
Three different things can slow you down, and they arrive as the same HTTP status. Telling them apart is the difference between “retry in a moment” and “retrying will never work”.

The three 429s

The third one is the trap. TENANT_BUDGET_EXCEEDED is a 429, so a client that branches on the status code alone will retry it forever and never succeed. Branch on error.code, not on the status.

Backing off

Both retryable cases set Retry-After in seconds. Honour it — it is the platform telling you what it knows, and it beats a fixed guess:
If Retry-After is absent, exponential backoff with jitter is the right fallback. Retrying immediately, in a loop, across every worker you have, turns a brief upstream wobble into a sustained one.

Money, not speed

Three more codes stop you for billing reasons rather than throughput. None of them are retryable, and none of them are billed: Auto-reload, spending limits and the current balance all live in the console under Billing. Setting a spending limit is what produces TENANT_BUDGET_EXCEEDED — it is a guardrail you asked for, not a failure.

Reducing the pressure

  • Stream. stream: true does not change your token cost, but it frees the connection sooner and gets the first token to your user much earlier.
  • Cap max_tokens. An unbounded generation occupies capacity until the model decides to stop, and you pay for all of it.
  • Retry the right thing. A retry after UPSTREAM_RATE_LIMITED may well land on a different provider for the same model — that is what x-provider-class: fallback on the response is telling you.

Seeing your own usage

Every response carries a usage object with the token counts you were billed on. The console shows the same figures per request, per model and per day, so reconciling what you measured against what you were charged does not require an export.

Next

Errors

All 28 codes, with retryability and billing.

Authentication

What a key is allowed to reach in the first place.