Error reference
Every code the gateway returns, what causes it, and whether retrying can help.
The envelope
Every failure, on every endpoint, comes back in OpenAI’s shape. Clients built against OpenAI already parse it — inventing our own would mean every SDK’s error handling silently stopped working the moment someone pointed it at us, which is the easiest possible way to lose a migration.
{
"error": {
"message": "Insufficient credit: balance $0.004120, this request could cost up to $0.061400. Top up at /dashboard/credits.",
"type": "billing_error",
"code": "insufficient_credit",
"request_id": "req_…"
}
}type is one of invalid_request_error, authentication_error, permission_error, not_found_error, rate_limit_error, billing_error, idempotency_error or api_error — the coarse class, for a switch statement. code is the precise reason, and it is the one to branch on. request_id also travels on the X-Multigrid-Request-Id header, on failures as well as successes.
Every code
| Status | Code | Retry | Meaning |
|---|---|---|---|
| 400 | invalid_request | No | Malformed body, an unusable value in one of our own fields, or a 4xx passed back from a provider. The message names what is wrong. |
| 401 | unauthenticated | No | No Authorization header, or it is not a bearer token. |
| 401 | invalid_api_key | No | The token is not a Multigrid key, or no key matches it. |
| 401 | key_revoked | No | The key existed and has been disabled. |
| 402 | insufficient_credit | No | The balance cannot cover this request’s worst case. Nothing was sent upstream. |
| 402 | key_limit_exceeded | No | This key’s own lifetime spend cap. |
| 402 | monthly_limit_exceeded | No | The account’s monthly ceiling, which you set. Not a payment failure. |
| 402 | no_free_route | No | A :free model id found no zero-cost route. Add your own provider key, or drop the suffix. |
| 402 | byok_key_rejected | No | The provider rejected your own key, so the request was not sent. Deliberately not failed over: moving it onto a paid route would quietly start billing you for traffic you had arranged to pay the provider for directly, and you would find out on an invoice. Re-enter the key, or remove it to pay with credit. |
| 403 | wrong_key_scope | No | An inference key on a management endpoint, or a provisioning key on an inference one. type is permission_error: no amount of retrying turns one kind of key into the other, and it used to go out as api_error, which every SDK retry wrapper reads as “our fault, back off and try again” — forever. |
| 403 | account_suspended | No | The account is suspended. Every key on it fails identically. |
| 404 | model_not_found | No | No model in the catalogue has that id. GET /models is the list. |
| 404 | not_found | No | A key, request or batch id that does not exist on your account. Another customer’s id reads the same way, which is all it should tell you. |
| 409 | request_in_flight | Yes | Another request with the same Idempotency-Key is still upstream. Retry with backoff to collect its result. |
| 413 | payload_too_large | No | An attachment, or the request as a whole, is over the size cap. The message states both figures in MB. |
| 422 | guardrail_blocked | No | One of your own guardrails refused it. The message names the rule and says whether it was charged. |
| 429 | rate_limited | Yes | Your key’s per-minute limit, or an upstream one we could not route around. Retry-After is authoritative. |
| 500 | internal_error | Yes | An unexpected failure on our side. The request was not billed. |
| 502 | upstream_error | Yes | Every provider we could try failed. The attempts are on the request row. |
| 503 | no_provider_available | No | The model exists but nothing on this deployment can serve it — no adapter for the provider, or no key for it. |
| 503 | plugin_unavailable | No | A plugin you asked for cannot run here. Refused rather than skipped: an answer without the web search you requested is the model’s recollection dressed as research, and you could not tell. |
| 504 | upstream_timeout | Yes | No provider responded inside the deadline — 60 seconds non-streaming, 300 streaming. |
OAuth is the one endpoint that does not use this envelope. The token endpoint answers in the RFC 6749 shape so that any OAuth client library parses it; those codes are listed with the OAuth flow.
Which errors cost money
Almost none. Everything refused before the balance pre-flight — 401, 403, 404, 409, 413, 429, and an input-stage guardrail block — happens before any provider is contacted and cannot appear on an invoice. So does a 402: that is the pre-flight.
- An output-stage
guardrail_blockedis charged. The response was really generated and we were really billed for it. The message says so. - A stream that fails mid-answer is charged for the tokens produced before the break, including when your own client hangs up.
- A web search that ran is charged even if the model then failed. The backend was called and billed us before the model started.
Anything charged has a matching ledger entry and a cost you can look up by request id.
Errors during a stream
Once a stream has started there is no status code left to set — the 200 is already on the wire. The failure arrives as an SSE event carrying the same envelope:
event: error
data: {"error":{"message":"Groq: upstream connection reset","type":"api_error","code":"upstream_error","request_id":"req_…"}}Every token produced before the break is still delivered, and the terminal usage chunk still follows.
data: lines and ignore named SSE events, so event: error reaches them and is discarded. What your code sees is a stream that ends early: a truncated answer, no exception, no error. It is worth saying plainly because the failure looks like the model simply stopping, and a summariser that returns half a summary without complaining is the sort of bug that reaches production.If a mid-stream failure has to be detectable in your code, read the raw lines rather than using the SDK’s iterator, or check
finish_reason on the terminal chunk — a stream that broke has none. GET /generation with the request id is the authoritative answer after the fact.Something here disagrees with what the API actually did? That is a bug in this page, and worth reporting.
Report it