Errors
Onlist returns errors in the OpenAI- and OpenRouter-compatible envelope. If your client
already handles OpenAI errors, it handles Onlist errors. The one rule worth internalizing:
branch on the string code, not on the HTTP status, and never on a numeric value inside
code.
The error envelope
Every error response is a JSON object with a single top-level error field:
{
"error": {
"message": "insufficient credits for estimated request cost",
"type": "onlist_error",
"code": "insufficient_balance",
"param": "",
"metadata": null
}
}
| Field | Type | Notes |
|---|---|---|
message | string | Human-readable. May carry a (request id: <id>) suffix for tracing. On a relayed upstream 4xx it starts with Provider <name> returned: . Do not parse it. |
type | string | Error family label: a string, not a status. See type families. |
code | string | Machine-readable slug. Branch on this. |
param | string | null | OpenAI-compat. Usually empty on Onlist pre-relay errors. |
metadata | object | null | Present only on upstream-relayed provider errors. provider_name names the fulfilling provider; on an upstream 5xx, raw carries the upstream envelope verbatim. |
code is always a machine-readable string such as insufficient_balance or
rate_limited, never the numeric HTTP status (402, 429). The HTTP status and the
code slug are two separate signals. Switch on error.code in your client; treat the HTTP
status as a coarse category.
type families
type tells you where the error came from, not what went wrong:
onlist_error: an Onlist pre-relay error. The request never reached a provider: it was rejected during validation, authentication, billing, or routing. All the slugs in the reference table below carry this type.- the upstream type, echoed: the request reached a provider's upstream provider and the
provider returned an error. Onlist relays that error with its fields intact (only
messagechanges: prefixed on a 4xx, replaced on a 5xx, see below), sotypeis whatever the upstream sent (e.g.invalid_request_error), defaulting toupstream_errorwhen the upstream gives nothing usable. See upstream passthrough.
param is an OpenAI-compatibility field. It is usually empty for Onlist pre-relay errors
and may be populated on a relayed upstream error. metadata appears only on upstream
errors and carries provider_name, plus raw on a 5xx.
Error code reference
The following slugs are produced by Onlist before relaying (all type: onlist_error). The
HTTP status is the coarse category; the code is the precise reason.
| HTTP | code | Meaning |
|---|---|---|
| 400 | invalid_request | Malformed request, or a missing/invalid model or messages. |
| 400 | provider_sub_slug_removed | A / appeared in a provider slug. Sub-slug addressing is removed: use a bare slug. |
| 401 | (unauthorized) | Missing/invalid bearer token, or a stale platform test token. |
| 402 | insufficient_balance | Remaining credits are below the estimated request cost. |
| 402 | quota_exhausted | The per-token quota is exhausted. |
| 403 | model_not_allowed | The API key only allows selected models and this model is not one of them. The message lists the allowed models; change them under “Only allow selected models” on the key. |
| 404 | provider_not_found | The pinned provider slug does not exist. |
| 404 | no_listing_for_model | No active listing exists for the requested model. |
| 422 | author_mismatch | The model's author segment disagrees with the registered author. |
| 422 | no_listing_for_model_at_provider | The pinned provider has no listing for this model. |
| 422 | invalid_data_collection | provider.data_collection was neither allow nor deny. |
| 429 | rate_limited | Per-token rolling-window cap hit. Carries Retry-After. |
| 503 | no_provider_in_max_price | provider.max_price filtered out the entire pool. |
| 503 | no_provider_after_allow_deny | allow/ignore preferences excluded every provider. |
| 503 | no_zdr_provider_available | provider.zdr: true and no eligible listing declares zero data retention. |
| 503 | no_provider_without_data_collection | provider.data_collection: "deny" and no eligible listing declares that prompts are withheld from training. |
| 503 | no_provider_available | All matching listings have disabled channels. Carries Retry-After on cooldown. |
| 500 | internal_error | Internal routing, billing, or database failure. |
A 401 response uses the standard envelope but does not commit to a stable code slug:
treat any 401 as an authentication failure and check your token. See
Authentication.
These three statuses split the routing failures cleanly. 404 means the model or pinned provider does not exist at all. 422 means the request shape is valid but no listing matches (author mismatch, or a pinned provider that does not carry the model). 503 means listings exist but none is currently eligible (filtered by your price/allow/deny constraints, or every channel is disabled or cooling down).
A few examples
402 insufficient_balance:
{
"error": {
"message": "insufficient credits for estimated request cost",
"type": "onlist_error",
"code": "insufficient_balance",
"param": ""
}
}
400 sub-slug removed:
{
"error": {
"message": "provider sub-slug addressing is removed; use a bare provider slug",
"type": "onlist_error",
"code": "provider_sub_slug_removed",
"param": ""
}
}
503 no_provider_in_max_price:
{
"error": {
"message": "no listing within the requested provider.max_price",
"type": "onlist_error",
"code": "no_provider_in_max_price",
"param": ""
}
}
Upstream passthrough
When the request reaches a provider's upstream provider and that provider returns an error,
Onlist does not wrap it. The HTTP status mirrors the upstream status; type, code and
param are whatever the upstream returned; and metadata.provider_name identifies the
fulfilling provider. What happens to message depends on the status class:
- 4xx keeps the upstream's text in full, prefixed with
Provider <name> returned:. It is the upstream's explanation of your request (a bad parameter, a context window overflow, a rate limit) and it is what you need to fix it. The prefix is for anyone who only seesmessage, such as a CLI or a chat UI: without it, a provider's own "insufficient quota" reads like a message about your Onlist credits. - 5xx gets
messagereplaced by the fixed stringProvider returned error, and the upstream's original envelope is placed verbatim inmetadata.raw. A 5xx body is a gateway or infrastructure error page in whatever language and format that provider's stack uses; nothing in it is for your code to act on, so you get a stable message to log and the original to inspect. The status,typeandcodeare still the upstream's.
One exception: on a streaming request, once heartbeat comments (: ONLIST PROCESSING)
have started, the 200 status line is already committed and cannot change. A terminal
upstream failure after that point is delivered as an in-stream error frame instead of
an HTTP error status, and no Retry-After header is possible. The frame shapes are
documented in Streaming.
The presence of metadata.provider_name is your signal that an error came from upstream
rather than from Onlist's own pre-relay checks.
{
"error": {
"message": "Provider alice-shop returned: This model's maximum context length is 128000 tokens.",
"type": "invalid_request_error",
"code": "context_length_exceeded",
"param": "messages",
"metadata": { "provider_name": "alice-shop" }
}
}
The same provider returning a 502 from its gateway arrives with the generic message and
the original body under raw:
{
"error": {
"message": "Provider returned error",
"type": "upstream_error",
"code": "bad_response",
"param": "",
"metadata": {
"provider_name": "alice-shop",
"raw": "{\"error\":{\"message\":\"upstream gateway timeout\",\"type\":\"upstream_error\",\"code\":\"bad_response\"}}"
}
}
}
If type is onlist_error, the request was rejected by Onlist before relay: match on the
slugs in the table above. Otherwise the error came from upstream: metadata.provider_name
is set and code/type follow the upstream provider's own conventions, which Onlist does
not normalize (only message changes: prefixed on a 4xx, replaced on a 5xx).
Which provider rejected it
Onlist changes two things at most: it adds metadata.provider_name under
error.metadata, and it marks message: on a 4xx by putting Provider <name> returned: in
front of the upstream text, on a 5xx by swapping it for the generic string while keeping the
original body in metadata.raw. The upstream status and every other error field are left
alone. Onlist is a multi-provider marketplace, so a 400 may come from the platform's
pre-relay checks, from a provider's own gateway, or from the upstream behind that provider,
and your next move depends on which: retry elsewhere, or exclude this provider.
The attribution sits at error.metadata.provider_name on all three protocol families (OpenAI,
Anthropic, Gemini), whatever else their error objects contain. Non-streaming responses also
carry X-Onlist-Provider and X-Onlist-Attempts saying the same thing; see
Limits & headers.
The field, and with it the 4xx message prefix, is absent when:
- the upstream body is not a standard JSON error envelope — an HTML error page from a reverse proxy, plain text, a JSON array. Those are relayed verbatim with no structural rewriting;
- the upstream already set
metadata.provider_nameitself. Upstream attribution wins.metadata.rawfollows the same two rules: it is only added on a standard envelope, and an upstream that already set it wins; - the error is Onlist's own (
type: "onlist_error"): authentication, insufficient credits, a constraint that emptied the candidate pool. There is no provider to attribute; - it is the terminal failure frame of a
/v1/responsesstream after heartbeats began. That protocol's failure event is shaped asresponse.error, which has no matching metadata slot; a non-standard key there breaks strict clients' event parsing.
Streaming responses never carry X-Onlist-Provider in their HTTP headers — those are
committed before Onlist knows which provider finishes — so on a stream, read the attribution
from the error envelope.
Empty upstream responses
A provider can return a well-formed 200 and then produce nothing at all: no content, no
usage. Onlist treats that as a failure rather than a successful empty answer. The call is
not charged, the provider goes on cooldown, and you get a terminal error carrying
code: "empty_response". On a streaming request it arrives as an in-stream error frame
(see Streaming):
{
"error": {
"message": "upstream returned an empty response (no output produced); you were not charged",
"type": "upstream_error",
"code": "empty_response"
}
}
The same empty_response slug shows up as other.error_code on that request's row in
Usage & cost, so a call you saw fail in real time is still
explainable afterwards.
The Anthropic /v1/messages variant
The Anthropic-native endpoint /v1/messages uses Anthropic's own error shape, not the
OpenAI envelope. It has only type and message inside error. There is no code or
param:
{
"type": "error",
"error": {
"type": "overloaded_error",
"message": "Overloaded"
}
}
The one exception is the provider attribution described above: on a failed relay, error
also carries metadata.provider_name. Anthropic SDKs ignore fields they do not recognize,
so the extra key does not affect parsing.
If you point an Anthropic SDK at Onlist, branch on error.type for this endpoint. All other
endpoints (/v1/chat/completions, /v1/completions, /v1/responses, /v1/embeddings,
and the rest of the OpenAI surface) use the standard envelope with the string code.
Retrying
A 429 (rate_limited) and a 503 on channel cooldown both carry a Retry-After
header giving the number of seconds to wait before retrying. Honor it: back off for at least
that long rather than retrying immediately.
X-RateLimit-* headers
Onlist does not emit X-RateLimit-* headers. Retry-After (on 429 and 503) is the only
rate-limit and cooldown signal. See Limits & headers for the full
header set and the rate-limit and cooldown behavior.
Other statuses are not automatically retryable. A 402 means you need to buy more credits, a 400
or 422 needs a fix to the request, and a 404 means the model or provider is not
available. A 500 (internal_error) is safe to retry after a short backoff.
Next steps
- Limits & headers: Retry-After, X-Onlist-Route-Id, X-Onlist-Warnings, and the rate-limit and cooldown behavior.
- Authentication: Bearer tokens, the normalized x-api-key / x-goog-api-key headers, and 401 handling.
- Provider routing: The provider object (allow, ignore, max_price) and the failures that surface as 503.
- OpenAPI spec: Full endpoint reference with request and response schemas (YAML download).