Errors

Every response uses the same shape, so you handle errors the same way across the API.

Response envelope

// 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.

Error codes

Statuserror codesMeaning
400invalid_paginationlimit 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
400phone_requiredShipping 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
400invalid_tier·missing_idempotency_key·invalid_amount·identifier_too_longMalformed request: unknown tier, missing Idempotency-Key header, bad amount, identifier over 255 chars
400missing_shipping_address·invalid_address·token_required·invalid_tx_hash·invalid_status·unsupported_itemBad redemption request (invalid_address also covers a bad PUT /payout-wallet body; invalid_status also a bad ?status= filter)
400invalid_price_queryGET /mystery/price needs exactly one of card_id / token_id
403Insufficient permissionsKey lacks the required scope (or is not a partner-scoped key)
404not_found·token_not_found·payout_wallet_not_setPurchase / webhook / redemption / card / token / payout wallet does not exist (or is not yours)
404value_unknownGET /mystery/price: card exists but no market price is available
409idempotency_mismatch·not_fulfilled·webhook_limitIdempotency-Key reused with a different tier_id / purchase not revealed yet / more than 20 webhooks
409redemption_exists·not_burnable·not_owner·no_holder·no_items·invalid_status·invalid_token_idRedemption conflict: token already redeeming, not redeemable, moved wallets, or wrong lifecycle state
409value_unknown·insufficient_liquidityBuyback: the card’s group has no oracle price, or the pool cannot cover the payout right now
409window_expired·wrong_holder·not_eligibleBuyback 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)
400invalid_token_ids·too_many_tokens·missing_tx_hash·tx_reverted·tx_unverifiedSell-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)
403not_tier_partner·seller_mismatchWrong model for the endpoint, or the on-chain seller is not one of your end users
404not_foundAlso returned for a path that does not exist under /api/v1 — check the method and URL before retrying
429rate_limitedPer-key or per-IP budget exhausted. Read Retry-After and X-RateLimit-Reset; do not retry sooner
503overloaded·timeoutRetryable. 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.