Error Codes
Error shapes, status codes, and handling behavior.
Most errors follow a consistent shape:
{ "error": "ERROR_CODE", "message": "Human readable description" }POST /v1/topup is an exception: some of its error responses (invalid payment encoding, mismatched payment, undecodable wallet address, and a 503 when the payment facilitator itself is unreachable) return { "error": "<human-readable message>" } with no separate message field and no machine-readable code. For this endpoint, match on HTTP status rather than parsing error as a code enum.
Error reference
| HTTP | Code | When |
|---|---|---|
400 | VALIDATION_ERROR | Missing/invalid params, unknown model, amount too low |
401 | UNAUTHORIZED | Missing/invalid API key or SIWE signature |
402 | INSUFFICIENT_BALANCE | Not enough credit for the estimated cost |
404 | NOT_FOUND | Resource not found |
429 | RATE_LIMITED | Per-wallet rate limit exceeded |
500 | INTERNAL_ERROR | Server error |
502 | UPSTREAM_ERROR | LLM provider failed or timed out |
Detailed examples
Insufficient Balance (402)
Returned when balance can't cover the reserved cost estimate.
{
"error": "INSUFFICIENT_BALANCE",
"message": "Insufficient balance. Top up at POST /v1/topup"
}Rate Limited (429)
Returned when per-wallet sliding window limits are exceeded.
{
"error": "RATE_LIMITED",
"message": "Rate limit exceeded"
}Validation Error (400)
{
"error": "VALIDATION_ERROR",
"message": "model and messages are required"
}Upstream Error (502)
Returned when the LLM provider fails on a non-streaming request. The balance lock is released (no charge).
{
"error": "UPSTREAM_ERROR",
"message": "Provider returned an error"
}For stream: true requests, the SSE headers and HTTP 200 status are sent before the upstream call starts, so a mid-stream provider failure cannot become an HTTP 502. Instead it arrives as an SSE event over the already-open 200 connection:
data: {"error":{"message":"...","type":"upstream_error"}}
data: [DONE]Streaming requests never see the 502 shape above
Check for an error key in each parsed SSE data: payload — don't rely on the HTTP status code or the {error: "UPSTREAM_ERROR", ...} shape for streaming failures. The balance lock is still released in this case.
Error handling behavior
| Scenario | Behavior |
|---|---|
| Insufficient balance | HTTP 402 with top-up instructions and current balance |
| Provider down | Release lock, return 502, no balance deducted |
| Invalid model | Return 400, release lock, no balance deducted |
| Rate limited | Return 429, no balance deducted |
| Invalid API key | Return 401 |
| Expired/invalid SIWE | Return 401 |
| Top-up below minimum | Return 400 before issuing 402 |