Architecture
Error Codes
Errors are boring on purpose: a machine-readable code, an optional human detail, and the right HTTP status. Nothing sensitive ever rides an error body.
Response shapes
Product APIs return a flat error object; ops APIs use the envelope with a request id:
jsonproduct API error
{ "error": "amount_over_limit", "detail": "amount exceeds the per-transaction limit ($500)" }jsonops API error
{
"ok": false,
"error": { "code": "reason_required", "message": "A reason (min 8 chars) is required." },
"meta": { "requestId": "req_…", "timestamp": "2026-07-12T00:00:00Z" }
}x402 endpoints are special: a rejected payment returns 402 with an error string and a fresh accepts quote, so an agent can recover in one round-trip.
HTTP status conventions
400— validation failed (zod), malformed payload, bad parameters.401— no valid session;402— payment required / payment rejected.403— authenticated but forbidden (role, over-cap, policy).409— conflict (replayed nonce, duplicate idempotency key with different body).423— account locked;429— rate limited (checkRetry-After).502— upstream (chain RPC, provider) failed;503— feature not configured on this deployment or rail paused.
Machine-readable codes
| Parameter | Type | Description |
|---|---|---|
| amount_over_limitrequired | 403 | Amount exceeds the server-side per-transaction cap for the rail. The detail names the cap. |
| replay_rejectedrequired | 409 / 402 | The EIP-3009 nonce or quote was already used. Sign a fresh authorization — never resign the same nonce. |
| invalid_signaturerequired | 400 / 402 | Signature recovery did not match the payer (ECDSA and ERC-1271 both failed). |
| rail_pausedrequired | 503 | The rail is administratively paused. Retry after the status page clears. |
| authorization_expiredrequired | 400 | validBefore has passed. Sign a fresh authorization. |
| quote_expiredrequired | 402 | The 402 quote TTL elapsed. Re-request the resource for a fresh quote. |
| settlement_pendingoptional | 202 | Submitted on-chain but confirmation not yet observed. Poll pending.statusPath — do NOT sign a fresh authorization until it resolves. |
| service_unavailablerequired | 503 | The feature is not configured on this deployment (e.g. no on-chain relayer). Production never fabricates success. |
| reason_requiredrequired | 400 | Ops control changes require a typed reason for the audit trail. |
| unknown_pageoptional | 400 | Docs feedback referenced a path outside the documentation. |
Retries
Retry
429 after the Retry-After value and 502 with backoff. Never blind-retry 402 replay rejections or 202 pending settlements — both can double-pay; follow the recovery path in the body instead.Did this page help?
Edit this page on GitHub