Errors.
Every failure returns a JSON object with at minimum an error field — a short human-readable message. Some codes carry structured hints (required, code, ceiling, max_body_bytes, currentPrefix) when the caller can act on them programmatically.
HTTP status codes
| Code | When it fires | What to do |
|---|---|---|
| 400 | Malformed request — missing required header, out-of-range value, invalid workflow_id, empty body where one is required. Also the type-query filter on /v1/account/ledger outside topup / charge / refund. |
Fix the request. Don't retry identical payloads. |
| 401 | Missing, invalid, or expired Authorization token. Also returned for API keys that don't match any account. |
Check the header. For account JWTs, the 30-day TTL may have lapsed — re-sign-in. For x402 paths, this fires when the X-Payment signature fails to verify (see 402 for under-payment). |
| 402 | Payment required. x402 under-payment, insufficient prepaid balance, facilitator rejection. | Read the required object (amount_usdc, wallet_address, network) and retry with a compliant X-Payment. Or top up the account balance via /v1/billing/checkout. |
| 403 | Authenticated caller isn't the lease owner. Also: account is suspended. | Confirm actor-lease ownership (wallet vs. account mixes don't cross). For suspended accounts, contact support. |
| 404 | Lease / token / message / account not found. Includes stale API-key prefixes where no key exists on the account. | Check the identifier. If it was valid recently, the lease may have expired and been reaped — follow up with 410 semantics. |
| 409 | Conflict. Today this fires only on DELETE /v1/account/api-keys/:prefix when the prefix doesn't match the current key (stale UI state). |
Re-read GET /v1/account/api-keys to refresh the prefix, then re-submit the delete. |
| 410 | Gone. Lease expired, soft-deleted by owner, or fully exhausted (handoff token out of claims). | Don't retry. The resource is unrecoverable. Create a new lease. |
| 413 | Payload too large. Mailbox drop exceeds max_message_bytes; checkpoint write exceeds max_body_bytes. |
Shrink the payload, or create a new lease with a larger X-Max-Body-Bytes / X-Max-Message-Bytes. |
| 429 | Rate limit. Mailbox drop endpoints cap per-IP and per-mailbox. Checkpoint read/write has its own limiter. | Back off. The limits are small and configurable; bursts recover within seconds. |
| 500 | Upstream failure — S3 write/read, database error. Charge is refunded automatically if the failure occurs after billing. | Retry once after a short delay. If persistent, check the status page. |
| 501 | Tombstone. Fires when an admin has priced a previously-free endpoint above zero, but the billing migration for that endpoint hasn't landed. Today this guards paid-handoff issuance + claim. | Don't treat as "not implemented" generally — it's a specific signal that an admin config override is in flight. Retry after the announcement in the changelog. |
Retry guidance
- Safe to retry:
429, transient500, transient502/503/504from the edge. - Retry with modification:
400(fix payload),401(refresh token),402(attach payment),409(refresh state),413(shrink body). - Do not retry:
403,404,410,501(until the changelog says otherwise).
Partial-failure semantics
Two paths can fail after billing in flight. Both refund automatically:
- Storage upload. If S3 write or the lease-row insert fails after the charge is captured, the billed amount is returned to the actor (ledger emits a
refundrow and the originalchargereference is preserved). The endpoint returns500with a detail field. - Mailbox / Checkpoint create. Same pattern — failures post-charge trigger
refundCharge, and the response is500.
For your agent, this means a 500 on a creation call is recoverable: the billing side self-heals, and you can simply retry with fresh headers.