All pages

Errors

Every error uses one envelope: {"error":{"code":"...","message":"...","status":...}}. The code is machine-readable and stable (e.g. INSUFFICIENT_CREDITS), message is for humans and may change, and status mirrors the HTTP status. Branch on code, never on the message text.

Which errors to retry

  • 429. Wait per the Retry-After header, then retry.
  • 500 / 502 / 503 / 504. Retry with backoff; if you sent an Idempotency-Key, the retry is safe.
  • 400 / 401 / 402 / 409. Retrying won't help; fix the request, key, or credit first.
  • 499 means your own client disconnected; nothing was charged.

Error catalogue

StatuscodeDescription
400BAD_REQUEST

The request body or query parameters are invalid. Check the error.code and error.message fields for details. Codes the gateway emits at 400:

  • BAD_REQUEST: generic malformed JSON / missing required field.
  • INVALID_TOOLS: the tools array, plugins field, or model's :online suffix uses an unsupported shape. The response names the offender and links to the supported form. See the dedicated InvalidTools response below for the three triggers and migration hints.
  • UNSUPPORTED_TOOL: a built-in tool (file_search, code_interpreter, computer_use, mcp) whose pricing dimension is not yet wired into the gateway hold estimator.
  • INVALID_AUDIO_FORMAT: input_audio.format value is not in the supported set. The envelope lists every supported codec name. The gateway accepts both short names (wav, webm) and full MIME aliases (audio/wav, audio/webm); anything else fails fast at admission. See API guide §8b.
  • AUDIO_NOT_SUPPORTED_ON_RESPONSES: input_audio content part sent to POST /v1/responses. The Responses API has no audio content type today; resend to POST /v1/chat/completions instead.
400INVALID_TOOLS

Returned when the tools array, plugins field, or model :online suffix uses a shape the gateway does not support. The three triggers documented today:

  • "model":"…:online": legacy variant; use tools:[{"type":"web_search"}].
  • "plugins":[{"id":"web"}]: legacy plugin form; same migration.
  • "tools":[{"type":"<internal>:…"}]: internal namespace; drop the prefix and use the short name.

The error message names the offender and includes a migration hint. The hold has NOT been placed (no charge).

400UNSUPPORTED_IMAGE_FORMAT

The image bytes could not be decoded (HEIC, AVIF, partial bytes, non-image content). The classifier requires JPEG, PNG, WebP, or GIF input. Re-encode the image and resend with a fresh Idempotency-Key. The hold has been released.

401UNAUTHORIZED

Authentication failed. Either the Authorization header is missing, the key format is invalid, or the key doesn't match any active account.

404NOT_FOUND

The requested resource was not found. Either the ID is wrong or it belongs to a different account.

409CONFLICT

Idempotency key conflict: you sent the same Idempotency-Key with a different request body.

429RATE_LIMITED

Too many requests. Back off and retry after a short delay. Check the Retry-After header for guidance.

499CLIENT_CLOSED

Your HTTP client cancelled the request before the gateway finished. No charge: the hold has been released. The most common cause is a 30 s default client timeout against a model whose p95 latency is 45-60 s; bump your client timeout to 120 s.

Retries with the same Idempotency-Key will replay this 499 forever (the prior outcome is "you cancelled"). For an actual re-attempt, mint a fresh Idempotency-Key. See docs/CUSTOMER_ERROR_HANDLING.md §3 for the full contract.

500INTERNAL_ERROR

Unexpected server error. Please retry with exponential backoff. If the issue persists, contact your admin.

502UPSTREAM_ERROR

The upstream AI provider is temporarily unavailable. Retry with exponential backoff (1s, 2s, 4s) using the same Idempotency-Key: if the upstream actually completed the work and only the response failed, the gateway replays the cached success without a second charge. Holds are released automatically on upstream failures.

504UPSTREAM_TIMEOUT

The gateway's 120 s upstream deadline elapsed before the upstream provider produced a response. Retry with the same Idempotency-Key so a delayed-but-eventually-successful upstream call can be replayed instead of re-charged. Consider switching to a faster model or capping max_tokens if this recurs.

FAQ

What does 402 INSUFFICIENT_CREDITS mean?

Your balance can't cover this request's hold. The request never reaches the model and nothing is charged; top up and retry.

Why am I getting 499?

Your HTTP client closed the connection before the response finished (usually a short timeout). Raise the timeout or enable streaming; the hold was released.

Updated 2026-10-13