Errors
Every failure carries a machine-readable code and a human sentence.
Branch on code, never on the message
The sentence is written for a person and may be reworded at any time. The code is part of the contract and will not change within v1.
The distinction that matters most is DENIAL versus COULD NOT CHECK
AUTH_UNAVAILABLE and LOOKUP_FAILED are 503s meaning we failed, not you are refused. Treating them as denial is how a working integration appears broken during a blip.
Every code v1 can return
All 22 of them, extracted from the API's own errorCodes.js — this table is generated, so a code cannot exist without appearing here.
| code | status | retry | meaning and what to do |
|---|---|---|---|
API_KEY_EXPIRED | 401 | no | The key is past its expiry. Request a new one. |
API_KEY_INACTIVE | 401 | no | The key exists but is disabled. Talk to us; it was revoked or paused. |
API_KEY_INVALID | 401 | no | The key did not match a live record. Check the key. Terminal — do not retry. |
API_KEY_REQUIRED | 401 | no | No key, and no recognised origin. Send X-API-Key, or ask us to register your origin. |
AUTH_UNAVAILABLE | 503 | yes | We could not CHECK your key — not a denial. 🚨 Retry. Treating this as a refusal locks out a legitimate partner during a blip. |
CHAIN_DISABLED | 400 | no | That chain is retired or paused. Terminal. Fall back to capabilities.mintUrl. |
CHAIN_NOT_SUPPORTED_BY_SURFACE | 400 | no | v1 mints Solana only. Use the mint page for other chains. |
CLIENT_BROADCAST_NOT_ALLOWED | 400 | no | You sent a transaction hash — meaning you broadcast it yourself. 🚨 Send the signed BYTES instead. We submit. There is no flag to disable this. |
CODE_REQUIRED | 400 | no | A claim-code check arrived with no code. Send { code } in the body. |
COLLECTION_CLOSED | 409 | no | The drop is closed. Terminal for minting; the read still works. |
COLLECTION_NOT_FOUND | 404 | no | No live drop matches that identifier. Address a drop by its short id or on-chain address — not an internal uuid. |
COLLECTION_NOT_IN_SCOPE | 403 | no | The key was not issued for that drop. A key is scoped to specific collections. Terminal. |
COLLECTION_REQUIRED | 400 | no | No collection was supplied. Include collectionId. |
LOOKUP_FAILED | 503 | yes | We could not load the drop. Retry shortly. Not a statement about whether it exists. |
NFTS_LOCKED | 409 | yes | The items are briefly held by another mint that is still settling. Transient — not a failure. Retry in a moment; tell the collector to try again, never "mint failed". |
NOT_ELIGIBLE | 403 | no | This wallet does not meet the phase requirements. Show the reason from the eligibility endpoint. Terminal for this wallet/phase. |
ORIGIN_NOT_ALLOWED | 403 | no | This origin is not on the key. Usually OUR configuration to fix — send us the exact origin, staging and preview included. |
RATE_LIMITED | 429 | yes | Too many requests. Back off and honour Retry-After. |
SESSION_EXPIRED | 410 | no | The prepared session timed out before the signature came back. Call prepare again. Never reuse a stale session. |
TX_MODIFIED | 400 | no | The signed transaction does not match what we built. 🚨 Never retry blindly — rebuild with prepare. This is the guard that protects your collectors. |
UNSUPPORTED_BY_SURFACE | 409 | no | The drop needs a mechanic v1 cannot render faithfully. Read capabilities.requiresFeatures and link to capabilities.mintUrl rather than rendering a wrong panel. |
WALLET_MISMATCH | 400 | no | The signing wallet is not the wallet the session was prepared for. Prepare and sign with the same wallet. |
The mint-time ones worth reading twice
NFTS_LOCKEDis not a failure. The item is briefly held by another mint that is still settling. Telling the collector "mint failed" is wrong — tell them to try again in a moment.TX_MODIFIEDmeans the bytes we received are not the bytes we built. Rebuild; never resubmit. This is the check that makes it safe for us to be the one broadcasting.CLIENT_BROADCAST_NOT_ALLOWEDcannot be turned off. See the broadcast rule.
