API Reference
Error Reference
HTTP status codes, error shapes, and how to handle common failures
Error format
Most errors use the OpenAI-style envelope with type and message:
{
"error": {
"type": "bad_request",
"message": "description of what went wrong"
}
}Exception: 402 without a payment header. When the client has not supplied a payment-signature header, the gateway emits the x402-spec PaymentRequired object at the top level of the response body — not wrapped in error.message. Parse the body as JSON directly:
const body = await response.json();
if (response.status === 402 && body.x402_version) {
// Cost-quote 402 — body IS the PaymentRequired object
const cost = body.cost_breakdown;
const accepts = body.accepts;
}Other 402 cases — invalid signature, replay, expired transaction, amount mismatch — use the OpenAI envelope with type: "invalid_payment" and a plain-text message. Distinguish the two shapes by checking for the top-level x402_version field.
HTTP status codes
| Code | Name | When it occurs |
|---|---|---|
200 | OK | Request succeeded |
400 | Bad Request | Invalid parameters, message limit exceeded, payment field validation (amount, network, asset, pay_to mismatch) |
401 | Unauthorized | Missing or invalid admin token / API key on enterprise endpoints |
402 | Payment Required | No payment-signature header, or payment verification failed |
403 | Forbidden | Authenticated but lacking permission (e.g. member role calling admin-only org endpoints) |
404 | Not Found | Unknown endpoint, or model ID not in the registry (type: model_not_found) |
429 | Too Many Requests | Rate limit exceeded |
500 | Internal Server Error | Gateway error (bug or misconfiguration), or payment settlement failed (type: settlement_failed) |
502 | Bad Gateway | Upstream provider returned an error (type: provider_error) |
503 | Service Unavailable | No provider could serve a paid request (type: upstream_unavailable — payment not charged), or a required dependency is unavailable |
Common errors and fixes
402 — No payment
{
"x402_version": 2,
"resource": { "url": "/v1/chat/completions", "method": "POST" },
"accepts": [ { "scheme": "exact", "amount": "...", "pay_to": "...", "...": "..." } ],
"cost_breakdown": { "total": "...", "currency": "USDC", "fee_percent": 5, "...": "..." },
"error": "Payment required"
}Cause: No payment-signature header was included.
Fix: The body is the PaymentRequired object (top-level fields, no OpenAI envelope). Read accepts[0].amount and accepts[0].pay_to, build and sign a Solana USDC-SPL transaction, wrap the signed bytes in a PaymentPayload, base64-encode the JSON, and resend with the payment-signature header.
See x402 Protocol for the full flow.
402 — Payment verification failed
{
"error": {
"type": "invalid_payment",
"message": "Payment verification failed. Check your transaction and retry."
}
}Cause: The payment-signature header was present but the transaction could not be verified on Solana. The gateway intentionally returns a generic message — server-side detail (RPC errors, signer mismatch) is in the gateway logs, not the response.
Common reasons:
- Transaction not yet confirmed — retry after a few seconds
- Wrong recipient address (
pay_tomismatch) — separate error:"Payment recipient does not match. Use the pay_to advertised in the 402 response."(returned as 400 Bad Request) - Wrong asset (not USDC-SPL mint) — separate 400:
"Payment asset is unsupported. Use the asset advertised in the 402 response." - Wrong network (not Solana mainnet) — separate 400:
"Payment network is unsupported. Use the network advertised in the 402 response." - Settlement not confirmed:
"Payment transaction could not be confirmed. Please retry."
402 — Replay attack detected
{ "error": { "message": "transaction has already been used; each payment signature may only be submitted once" } }Cause: You submitted the same signed transaction twice.
Fix: Build a new transaction with a fresh blockhash and sign it again. Never reuse a transaction signature.
404 — Model not found
{
"error": {
"type": "model_not_found",
"message": "model not found: my-custom-model"
}
}Status code: 404 Not Found (the model-not-found path returns 404, not 400 — see GatewayError::ModelNotFound in crates/gateway/src/error.rs).
Cause: The model field contains an ID that isn't in the registry.
Fix: Use a valid model ID from GET /v1/models, a recognized alias (sonnet, gpt5, etc.), or a routing profile (auto, eco, premium, free).
400 — Too many messages
{ "error": { "message": "too many messages: 300 exceeds maximum of 256" } }Cause: The messages array exceeds 256 items.
Fix: Truncate or summarize older messages.
400 — Payment amount insufficient
{ "error": { "message": "payment amount insufficient: paid 100 but cost is 2625 atomic USDC" } }Cause: The amount in your PaymentPayload.accepted is less than the gateway's computed cost.
Fix: Use the amount value from the 402 response exactly. Do not reduce it.
400 — Request blocked by content policy
{ "error": { "message": "Request blocked by content policy" } }Cause: The prompt guard detected injection, jailbreak patterns, or other policy violations.
Fix: Review the request content. Prompt injection patterns (e.g., "ignore previous instructions") are blocked.
503 — No provider available (paid request)
{
"error": {
"type": "upstream_unavailable",
"message": "No provider could serve your request right now and your payment was NOT charged. Please retry shortly."
}
}Cause: The gateway accepted your payment but no upstream provider could produce a response.
You are not charged for an undelivered completion:
- exact scheme — the USDC transfer is deferred until after delivery, so nothing settled on-chain.
- escrow scheme — the deposit settled pre-call, but the gateway never claims it for a failed request; the message is
"No provider could serve your request right now; no claim was made against your escrow deposit and it refunds at expiry. Please retry shortly."
Fix: Retry the request. If the issue persists, the provider may be experiencing an outage.
429 — Rate limited
{
"error": {
"type": "rate_limit_exceeded",
"message": "Too many requests. Please slow down."
}
}Cause: Too many requests from this client (payer wallet, falling back to source IP) within the rate-limit window.
Fix: Back off and retry. The default ceiling is 60 requests per 60-second fixed window per identified client (10 for the shared "unknown" bucket when neither a wallet nor a source IP can be derived) — use the X-RateLimit-Reset header or the Retry-After header to time retries rather than a fixed backoff. See Rate Limits.