Skip to content
Docs menu

API reference

Errors

Errors use standard HTTP status codes. Every error body carries a human-readable `error` sentence and a stable `code` you can branch on — branch on `code`, never on the wording of `error`, which may be reworded at any time.

json
{
  "error": "Insufficient credits: this call costs 24, balance is 7.8.",
  "code": "insufficient_credits",
  "need": 24,
  "have": 7.8,
  "topup_url": "https://you.bot/dashboard/billing"
}

On chat models, image input is refused rather than ignored: attaching an image to a chat model whose reference has no `images` parameter comes back 400 with `image_input_not_supported` and is not charged — we do not answer a question about a picture the model never received. On media models (image, video, audio), an `images` key is not part of the schema and is dropped like any other unknown field; send media through the input fields that model documents.

`need` and `have` are credits and appear only on a shortfall; `topup_url` appears only where topping up is the remedy, and it is a signed-in page, so send a person to it rather than a script. A 402 whose code is `spend_cap_daily` or `spend_cap_monthly` is your own key's limit, and `pricing_unavailable` is a fault on our side — neither is fixed by topping up, so do not retry those.

**We tell the model who it is — unless you send your own system instruction.** When you omit `system`, we add one that names the model you called and instructs it to answer with that name if asked, rather than deflecting or naming another. This was measured: without that clause a model answers with a generic description of itself, and sometimes invents a version that does not exist.

If you send your own `system`, it REPLACES ours entirely — the model then knows only what your instruction tells it. Include your own identity line if you want it to answer "which model are you?" correctly. This is the most common reason a caller sees an assistant deny being the model they requested.

What a model says about OTHER versions is still not evidence either way: asked about a version that does not exist, a model will often confirm it exists and even give a release date, then contradict itself later in the same conversation. The record is the `model` field returned on every call — read from the model's own reply — together with your usage log, where each row also shows which API key it arrived on. If either ever shows a model you did not request, send us the task id and we will trace it.

codeWhen
insufficient_credits402 — the account balance will not cover the call.
spend_cap_daily / spend_cap_monthly402 — this API key's own spend limit was reached.
model_not_allowed403 — this API key is restricted to a list of models and the requested model is not on it. Nothing ran and nothing was charged.
pricing_unavailable402 — the model is misconfigured on our side. Nothing ran and nothing was charged.
invalid_request400 — parameters the model will not accept.
unauthorized401 — missing or invalid API key.
forbidden403 — IP not allowed for this key, or the account/model is unavailable.
rate_limited429 — retry after the Retry-After header.
search_not_performed422 — `web_search` was requested but the reply came back without an answer. Nothing was charged; retry without `web_search`.
duplicate_request409 — an identical submit arrived moments ago. Nothing was charged; retry is safe.
payload_too_large413 — the prompt, an attachment, or the account's upload quota is over the limit.
not_found404 — the task or file was not found, or it has expired.
timeout504 — the upstream took too long. Nothing was delivered; retry is safe.
upstream_error / unavailable / internal_error502 / 503 / 500 — a media task that produced no result is refunded.
image_input_not_supported400 — this model takes text only. A model that accepts images lists an `images` parameter in its own reference; one that does not, refuses here rather than answering about a picture it never received.
too_many_images400 — more images than that model's `images` parameter allows in one message, or more than 8 across the whole request.
image_too_large400 — one attachment is over the per-image size limit.
image_format_unsupported400 — not JPEG, PNG, GIF or WebP. Read from the file's own bytes, so a data: URL that names the wrong type is refused on its content, not its label.
image_unreadable400 — the image, or its pixel size, could not be read. Send a data: URL, an https URL we can fetch, or an upload from this account.
capability_not_supported400 — a capability was requested that the model does not offer (for example `web_search: true` on a model whose API reference lists no `web_search` parameter). Refused with nothing charged, rather than answered as if the capability had run. Web search is available only where the model's own reference lists it.
image_not_received422 — the model answered that it did not receive the attached image. A supplier route can strip an image block, and a confident answer about a picture the model never saw is worse than an error, so the turn is refused and NOT charged. Retry.
empty_reply422 — the model returned no answer text (reasoning or a tool call only, or an empty response). Nothing was charged; retry.
unknown_image_field400 — images were sent under a key the model has no parameter for (`input_urls`, `image_urls`, `imageUrl`, …). Chat models that accept images take them as `images`; the request is refused rather than answered without them, and nothing is charged.
image_placement_unsupported400 — an image is attached to a message we do not send to the model (a system message, an assistant message, or a turn older than the last 20). Attach images to a user message in the current turn; they are refused rather than silently dropped.
prompt_and_messages400 — both `prompt` and `messages` were sent. Pick one; we do not choose for you, because the other one would disappear without a word.
messages_unsupported400 — `messages` was sent to a model that is not a chat model. Media models take a single `prompt`.
messages_not_array400 — `messages` is not an array of `{ "role", "content" }` objects. A conversation flattened into one string is not a conversation; send real turns.
messages_empty400 — `messages` is an empty array. Send at least one message, or use `prompt`.
too_many_messages400 — more than 20 messages in one request. Trim the oldest turns yourself, so you know exactly what the model sees; we do not drop them for you.
message_not_object400 — one entry of `messages` is not an object. The error names its index.
message_role_unknown400 — a message `role` is missing or is not `user`, `assistant` or `system`. An unrecognised role is refused rather than treated as the user speaking.
message_content_type400 — a message `content` is neither a string nor an array of content blocks.
message_empty400 — a message carries neither text nor images. Leave it out instead; an empty turn would otherwise vanish on the way upstream.
Chat messages and a caller-supplied `system` prompt are capped at 400,000 characters each; over that the call is refused with 413 and nothing is charged. Anything under it is sent in full — if it exceeds the model's own context window, the model's own error comes back to you.
StatusMeaning
400Invalid or missing parameters for the model.
401Missing or invalid API key.
402Insufficient credits — top up in Billing.
403Request IP not allowed for this key, or the account/model is unavailable.
404Task or file not found, or expired.
409Duplicate submit inside a short window — nothing was charged.
413Payload too large — prompt, attachment or upload quota.
422The upstream answered, but the answer was not usable (no text, an image it did not receive, or search results instead of an answer). Nothing was charged; the body names which. A 4xx because the body must reach you — a 5xx body can be replaced by the CDN in front of us.
429Rate limited — slow down and retry after the Retry-After header.
504Upstream timed out — nothing was delivered; retry is safe.
5xxUpstream or gateway error — a media task that produced no result is refunded.