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": "wallet balance insufficient 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. 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 (e.g. { "provider_name": "alice-shop" }). |
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 verbatim, so
typeis 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 passthrough fields like provider_name.
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 | Wallet balance is below the estimated request cost. |
| 402 | quota_exhausted | The per-token quota is exhausted. |
| 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. |
| 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_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": "wallet balance insufficient 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 or rewrite it. The error is relayed verbatim:
- the HTTP status mirrors the upstream status;
message,type,code, andparamare whatever the upstream returned;metadatacarries passthrough fields, andmetadata.provider_nameidentifies the fulfilling provider.
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": "This model's maximum context length is 128000 tokens.",
"type": "invalid_request_error",
"code": "context_length_exceeded",
"param": "messages",
"metadata": { "provider_name": "alice-shop" }
}
}
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.
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,
param, or metadata:
{
"error": {
"type": "overloaded_error",
"message": "Overloaded"
}
}
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 needs a wallet top-up, 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).