Errors overview
Every error the router returns is listed, with its status code and a sample body, in the
Error reference. This page covers what is common to all of them.
Response formats
Errors come back in one of three JSON shapes. Check the status code first, then read the
body that goes with it.
Router errors: most errors, including errors forwarded from a provider:
{
"error": {
"message": "Your license is out of budget"
}
}
Policy denials (403) add a machine-readable code and the offending parameter:
{
"error": {
"message": "'temperature' must be between 0 and 1",
"code": "policy_denied",
"param": "temperature"
}
}
Request validation (422, plus 405 for a wrong HTTP method) uses FastAPI’s format,
with detail at the top level instead of error:
{
"detail": [
{
"type": "extra_forbidden",
"loc": ["body", "top_k"],
"msg": "Extra inputs are not permitted",
"input": 40
}
]
}
Two responses fall outside these shapes: GET /v1/health answers {"status": "error"}
when it is unhealthy, and an unexpected internal failure returns 500 with the plain-text
body Internal Server Error.
Status codes
| Status | Meaning | Body | Retry? |
|---|---|---|---|
400 |
The request can’t be served as sent: out of budget, a bad header, no provider available, a guardrail block, or a provider rejection | error |
No. Fix the request or the license first |
401 |
Missing, malformed, unknown, revoked, or expired credentials | error |
No |
403 |
Your license’s policy does not allow the service or a parameter value | error + code + param |
No |
404 |
The provider reported that the model or resource does not exist | error |
No |
405 |
Wrong HTTP method for the endpoint | detail |
No |
422 |
The request body failed validation | detail |
No |
500 |
Budget misconfiguration or an unexpected provider or internal error | error or plain text |
Maybe. Report it if it persists |
503 |
The provider is unavailable, throttled, timed out, or rejected our credentials; or your license’s policy could not be evaluated | error |
Yes, with backoff |
The order checks run in
A request goes through the same checks, in the same order, on every endpoint that calls a
provider. The first one that fails decides the response.
- Authentication. A malformed
Authorizationheader or a key that doesn’t resolve
returns401straight away. - Body validation. The JSON body is validated against the endpoint’s schema
(422). This runs before the check for a missingAuthorizationheader, so a
request with no key and an invalid body gets422, not401. - Missing credentials. No
Authorizationheader returns401. - Quantized headers. A malformed
X-Quantized-ProviderorX-Quantized-Metadata
returns400. - Budget. A license with no budget left returns
400. A misconfigured budget
returns500. - Policy. The license-type policy can deny the service or a parameter value (
403). - Provider selection. No provider able to serve the request returns
400. - Guardrails. An input guardrail can block the request (
400). - Provider call. The provider’s own errors are mapped to
400,404,500, or
503. - Output guardrails. An output guardrail can block the response (
400).
GET /v1/models and GET /v1/license only run steps 1, 3, and 4. GET /v1/health
needs no credentials.
What you are charged for
A request that fails is not charged, whether the router rejects it or the provider
returns an error. The one exception is a response blocked by an output guardrail:
the provider already did the work, so the call is charged even though you receive a
400.
How provider errors are mapped
The router does not pass provider status codes through as-is:
| Provider response | You receive |
|---|---|
400 or 422 (request rejected) |
400 with the provider’s message |
404 (model or resource not found) |
404 with the provider’s message |
401 or 403 (our credentials rejected), 429, any 5xx, timeout, connection failure |
503 Service temporarily unavailable |
| Any other status | 500 |
The 503 is fully masked: the message is always the same, no provider details are
included, and no Retry-After header is forwarded. Back off and retry.
Who validates the model
Quantized does not keep a gate in front of the providers. A model id we cannot resolve
in the catalog is forwarded to the provider exactly as you sent it, and the provider
decides whether it exists. The status code and the message you get back are the
provider’s, not ours.
This matters because the same mistake gets a different answer depending on who serves
the endpoint. On the two single-provider endpoints it is at least consistent:
POST /v1/aws-bedrock/embeddings {"model": "amazon.titan-embed-text-v99:0"}
400 The provided model identifier is invalid.
POST /v1/gemini/embeddings {"model": "gemini-embedding-9999"}
404 models/gemini-embedding-9999 is not found for API version v1beta, or is not
supported for embedContent.
Both are the same class of error. AWS calls it a 400, Google calls it a 404.
Where an endpoint has several eligible providers it is not stable even across repeated
calls, because an unresolved model gives the router nothing to route on. Identical
requests to POST /v1/chat/completions with {"model": "nonexistent-model"} can come
back with different wordings and different status codes, depending on which provider
served each one:
404 nonexistent-model is not a valid model ID
404 The model `nonexistent-model` does not exist or you do not have access to it.
404 model: nonexistent-model
400 The provided model identifier is invalid.
Treat any 4xx naming the model as “this model is not usable”. Do not match on message
text, do not assume 404, and do not expect two identical requests to fail identically.
Call GET /v1/models and match on id. That is the list of ids the
catalog resolves; anything else depends on the provider accepting it.
Handling errors
import time
import httpx
def chat(payload, retries=3):
for attempt in range(retries):
response = httpx.post(
"https://api.quantized.us/v1/chat/completions",
headers={"Authorization": "Bearer sk-quantized-YOUR-KEY"},
json=payload,
timeout=60,
)
if response.status_code == 200:
return response.json()
if response.status_code == 503 and attempt < retries - 1:
time.sleep(2**attempt) # provider unavailable: back off and retry
continue
if response.status_code in (405, 422):
for d in response.json()["detail"]:
print(f"Invalid request: {d['msg']} at {d.get('loc')}")
elif "application/json" in response.headers.get("content-type", ""):
error = response.json()["error"]
if error.get("code") == "policy_denied":
print(f"Not allowed by your license: {error['message']}")
else:
print(f"Error {response.status_code}: {error['message']}")
else:
print(f"Error {response.status_code}: {response.text}")
response.raise_for_status()
Streaming requests report errors that happen after the stream starts inside the stream
itself, with a 200 status. See Errors during a stream.