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

CodeNameWhen it occurs
200OKRequest succeeded
400Bad RequestInvalid parameters, message limit exceeded, payment field validation (amount, network, asset, pay_to mismatch)
401UnauthorizedMissing or invalid admin token / API key on enterprise endpoints
402Payment RequiredNo payment-signature header, or payment verification failed
403ForbiddenAuthenticated but lacking permission (e.g. member role calling admin-only org endpoints)
404Not FoundUnknown endpoint, or model ID not in the registry (type: model_not_found)
429Too Many RequestsRate limit exceeded
500Internal Server ErrorGateway error (bug or misconfiguration), or payment settlement failed (type: settlement_failed)
502Bad GatewayUpstream provider returned an error (type: provider_error)
503Service UnavailableNo 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_to mismatch) — 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.