Skip to content

Reference

Errors

Failures use the OpenAI error envelope, so SDK error handling works unchanged. Branch on error.code: it is stable, while messages may be reworded.

Error envelope (permalink)

Every error the gateway itself produces has this shape.

JSON402 response body
{
  "error": {
    "message": "Token budget exhausted. Contact your provider to renew.",
    "type": "insufficient_quota",
    "code": "token_budget_exhausted",
    "param": null
  }
}
  • message is human-readable and safe to log or show an operator. It never contains credentials.
  • type is the broad family: invalid_request_error, permission_error, insufficient_quota or api_error.
  • code is the specific reason. This is the field your code should switch on.
  • param is reserved for field-level errors and is null on everything the gateway raises.

Gateway errors (permalink)

These nine are the complete set the gateway raises on its own. Anything else you see came from the model provider and passed straight through.

Complete gateway error reference
StatusCodeTypeMeaningWhat to do
401missing_api_keyinvalid_request_errorThe request carried no credential at all.Send Authorization: Bearer with your key, or the x-api-key header.
401invalid_api_keyinvalid_request_errorUnknown key, or a valid key sent to the other billing surface.Check the key was copied whole, then confirm you are on the right base URL.
403key_suspendedpermission_errorThe key exists but has been paused by whoever issued it.Ask them to resume it. The key itself stays valid and does not need replacing.
403key_expiredpermission_errorThe key is past its expiry date.Request an extension or a new key. It will not recover on its own.
403model_not_allowedpermission_errorThe key is valid but is not scoped to the model in the request body.Use a model on your allowlist, or ask for the allowlist to be widened.
402insufficient_creditinsufficient_quotaA credit key whose USD balance has reached zero.Ask for a top-up. Confirm the balance in the usage checker first.
402token_budget_exhaustedinsufficient_quotaA token key that has consumed its whole prepaid budget.Ask for the budget to be renewed.
404unknown_endpointinvalid_request_errorNothing followed the base URL, or the path did not resolve to a real endpoint.Append a real path such as /chat/completions. Watch for a doubled or missing /v1.
502upstream_unavailableapi_errorThe gateway could not reach the model provider at all. Nothing was billed.Retry with exponential backoff. Repeated 502s mean an outage, not a bad request.

Order of checks (permalink)

Knowing the order tells you what a status rules out. The gateway stops at the first failing check and never reaches the ones below it.

  1. Path. A request with nothing after the base URL, or a path that does not resolve, gets 404 unknown_endpoint. This happens before authentication, so a 404 says nothing about your key.
  2. Credential present. No Authorization: Bearer and no x-api-key gives 401 missing_api_key.
  3. Key known. An unrecognised key gives 401 invalid_api_key.
  4. Key usable. Suspended gives 403 key_suspended, past its expiry gives 403 key_expired.
  5. Right surface. A credit key on the token base URL, or the reverse, gives 401 invalid_api_key again.
  6. Funds. An empty balance gives 402 insufficient_credit, a spent budget gives 402 token_budget_exhausted.
  7. Model allowed. A model outside the key’s allowlist gives 403 model_not_allowed. Only now is the request body read.
  8. Forwarded. If the provider cannot be reached at all, 502 upstream_unavailable. Otherwise the provider’s own status is what you get.

Upstream failures (permalink)

A request that passes every check is forwarded to the model provider, and the provider’s status and response body come back to you unchanged. So a 400 about an unsupported parameter, a 429 about rate limits, or a 529 about capacity originates there rather than here, and the message reads in the provider’s own words.

Response headers are filtered rather than mirrored. content-type and x-request-id are preserved, streamed responses also carry cache-control: no-cache, no-transform, and everything else is dropped. Do not build logic on a header you have not seen documented here.

Pass-through statuses
StatusWhere it comes from
400, 404, 422The provider rejected the request body: a bad parameter, an unknown model id, an oversized context, or a missing max_tokens on an Anthropic Messages call.
429The provider is rate limiting. The gateway does not add a rate limit of its own, so this always comes from upstream. Back off with jitter.
5xx other than 502The provider failed or timed out. Retry with backoff, and report it if it persists.

A 402 can also arrive this way, when the upstream account itself is short of funds. The distinction matters: if the usage checker shows your key still has balance or budget but calls keep returning 402, the problem is upstream and only your provider can clear it.

What to retry (permalink)

Authentication, permission and billing failures are decisions, not accidents. Retrying them wastes time and makes rate limits worse. Only 429 and 5xx deserve another attempt.

JavaScriptReading the error
const response = await fetch(url, init);

if (!response.ok) {
  const body = await response.json().catch(() => null);
  const code = body?.error?.code ?? "unknown_error";

  // Only 429 and 5xx are worth trying again.
  const retryable = response.status === 429 || response.status >= 500;

  throw Object.assign(new Error(body?.error?.message ?? response.statusText), {
    status: response.status,
    code,
    retryable,
  });
}

Then retry only what is retryable, with exponential backoff and jitter.

JavaScriptBackoff wrapper
async function withRetry(send, attempts = 4) {
  for (let attempt = 0; ; attempt++) {
    try {
      return await send();
    } catch (err) {
      if (!err.retryable || attempt >= attempts - 1) throw err;
      const wait = 500 * 2 ** attempt + Math.random() * 250;  // jittered backoff
      await new Promise((resolve) => setTimeout(resolve, wait));
    }
  }
}
  • Cap the attempts. Four tries over roughly ten seconds is plenty for a transient upstream fault.
  • Add jitter. Without it, every client that failed at the same moment retries at the same moment.
  • Stop on 402 immediately, or you will hammer a key that has nothing left to spend.

What gets billed (permalink)

Failures cost nothing, but they are not all recorded the same way.

How failures are recorded
OutcomeBilledIn the usage checker
Refused by the gatewayNoNo. A 401, 402, 403 or 404 stops before the ledger, so it never appears as a request.
Forwarded, provider erroredNoYes, recorded against your key as an error at zero cost.
Provider unreachable (502)NoYes, recorded as an error at zero cost.
SucceededYesYes, with model, token counts and cost.

Successful metadata calls are the one exception in the other direction. A GET /models reports no token usage, so it is neither billed nor listed. More detail on Checking usage.

Reporting a problem (permalink)

When something needs a human, include:

  • The x-request-id response header, when the provider supplied one.
  • The HTTP status and the whole error object.
  • The key prefix only, meaning the first characters such as kl_live_a1b2c3d4. Never send the whole key.
  • The model id, whether you were streaming, and roughly when it happened.