Every response uses the same shape, so you handle errors the same way across the API.
// success
{ "success": true, "data": { … }, "pagination": { "limit": 50, "offset": 0, "has_more": false } }
// error
{ "success": false, "message": "human readable", "error": "machine_code", "details": { … } } Branch on the HTTP status and the machine-readable error code. message is for humans and may change. details appears on some
4xx errors and carries machine-readable context your code can act on — for a
sell-back rejection, which token blocked the batch and why. pagination is only on
list endpoints. 5xx responses never leak internal detail: unexpected
server-side failures surface as error: "internal_error" (500), and the two
transient conditions you should retry surface as 503 with overloaded or timeout and a Retry-After header. A request that fails with
any 4xx or 5xx is not billed: the credit charged up front is refunded when
the response finishes.
| Status | error codes | Meaning |
|---|---|---|
| 400 | invalid_pagination | limit or offset carries no integer at all (?limit=abc), or an id array is over its 500-entry cap. Anything with a leading integer is read the way parseInt would and clamped into range — on every list endpoint of every API |
| 400 | phone_required | Shipping quote to a non-US destination without a destination phone number. Carriers require one for international labels; send it in any national or international format and we normalise it |
| 400 | invalid_tier·missing_idempotency_key·invalid_amount·identifier_too_long | Malformed request: unknown tier, missing Idempotency-Key header, bad amount, identifier over 255 chars |
| 400 | missing_shipping_address·invalid_address·token_required·invalid_tx_hash·invalid_status·unsupported_item | Bad redemption request (invalid_address also covers a bad PUT /payout-wallet body; invalid_status also a bad ?status= filter) |
| 400 | invalid_price_query | GET /mystery/price needs exactly one of card_id / token_id |
| 403 | Insufficient permissions | Key lacks the required scope (or is not a partner-scoped key) |
| 404 | not_found·token_not_found·payout_wallet_not_set | Purchase / webhook / redemption / card / token / payout wallet does not exist (or is not yours) |
| 404 | value_unknown | GET /mystery/price: card exists but no market price is available |
| 409 | idempotency_mismatch·not_fulfilled·webhook_limit | Idempotency-Key reused with a different tier_id / purchase not revealed yet / more than 20 webhooks |
| 409 | redemption_exists·not_burnable·not_owner·no_holder·no_items·invalid_status·invalid_token_id | Redemption conflict: token already redeeming, not redeemable, moved wallets, or wrong lifecycle state |
| 409 | value_unknown·insufficient_liquidity | Buyback: the card’s group has no oracle price, or the pool cannot cover the payout right now |
| 409 | window_expired·wrong_holder·not_eligible | Buyback pre-flight: the card’s buyback window has closed, the wallet is not the card’s original recipient, or another token in the batch is ineligible (the on-chain call is all-or-nothing) |
| 400 | invalid_token_ids·too_many_tokens·missing_tx_hash·tx_reverted·tx_unverified | Sell-back request: no valid token ids, more than 50 in one batch, or a transaction that reverted / cannot be found (submit after it is mined) |
| 403 | not_tier_partner·seller_mismatch | Wrong model for the endpoint, or the on-chain seller is not one of your end users |
| 404 | not_found | Also returned for a path that does not exist under /api/v1 — check the method and URL before retrying |
| 429 | rate_limited | Per-key or per-IP budget exhausted. Read Retry-After and X-RateLimit-Reset; do not retry sooner |
| 503 | overloaded·timeout | Retryable. overloaded: the endpoint’s concurrency gate is full (Retry-After: 1). timeout: a database query hit its time budget (Retry-After: 2). Back off and retry; both carry retryable: true |
Each endpoint page in the API Reference lists the exact codes it returns and when. See Prepare purchase or Prepare redemption.