code and a stable string key.
message is human-readable and may change; always branch on key.
Code blocks
Codes are organized into 100-block ranges by domain, with sub-groups every 10 codes inside a domain.| Range | Domain |
|---|---|
| 1000–1099 | General / Infrastructure |
| 1100–1199 | Auth |
| 1200–1299 | 2FA |
| 1300–1399 | Account access |
| 1400–1499 | User Settings |
| 1500–1599 | Wallet |
| 1600–1699 | Deposits (gateway: gatepay, onramp) |
| 1700–1799 | Withdrawals (reserved) |
| 1800–1899 | Crypto Deposits / Withdrawals |
| 1900–1999 | Merchant Management |
| 2000–2099 | Marketplace |
HTTP status mapping
| Status | When |
|---|---|
400 | Generic bad request (BAD_REQUEST). |
401 | Missing or invalid auth, including expired tokens. |
403 | Authenticated but not permitted (suspended, IP not allowed, calling a dashboard-only route). |
404 | Resource doesn’t exist. |
409 | Resource conflict (idempotency replay, duplicate email, etc.). |
410 | Gone (a one-shot 2FA code was already used). |
422 | Validation or business-rule failure. |
429 | Rate limited. |
500 | Internal error. |
502 | Upstream gateway error (Discord OAuth, marketplace partner, crypto RPC). |
503 | Service unavailable (no marketplace workers healthy, FX unavailable, maintenance). |
504 | Marketplace worker did not respond in time. |
General / Infrastructure (1001–1007)
Cross-cutting errors that any endpoint can return.| Code | Key | HTTP | Meaning |
|---|---|---|---|
| 1001 | VALIDATION_FAILED | 422 | Request body / query failed Zod schema validation. The error includes meta.details. |
| 1002 | BAD_REQUEST | 400 | Generic malformed request (e.g. unknown query combo). |
| 1003 | NOT_FOUND | 404 | Generic resource not found. |
| 1004 | CONFLICT | 409 | Generic uniqueness violation. |
| 1005 | RATE_LIMITED | 429 | Honor Retry-After. See Rate limits. |
| 1006 | INTERNAL | 500 | Catch-all server error. Retry. |
| 1007 | MAINTENANCE_MODE | 503 | Service down for maintenance. |
Auth (1100–1107)
| Code | Key | HTTP | Meaning |
|---|---|---|---|
| 1100 | UNAUTHORIZED | 401 | No credentials. |
| 1101 | INVALID_CREDENTIALS | 401 | (Dashboard JWT) bad email/password. |
| 1102 | INVALID_TOKEN | 401 | (Dashboard JWT) bad signature, expired, or wrong purpose. |
| 1103 | MISSING_BEARER | 401 | (Dashboard) no Authorization: Bearer header. |
| 1104 | MISSING_API_KEY | 401 | No api-key header. |
| 1105 | INVALID_API_KEY | 401 | Key not found. |
| 1106 | API_KEY_REVOKED | 401 | Key was revoked in dashboard. |
| 1107 | INVALID_REFRESH_TOKEN | 401 | (Dashboard) refresh token bad/expired. |
2FA (1200–1208)
| Code | Key | HTTP | Meaning |
|---|---|---|---|
| 1200 | TWOFA_REQUIRED | 401 | Login requires 2FA challenge completion. |
| 1201 | INVALID_2FA_CODE | 422 | Bad TOTP / email / recovery code. |
| 1202 | TWOFA_ALREADY_ENABLED | 409 | Cannot start TOTP setup; already enabled. |
| 1203 | TWOFA_NOT_ENABLED | 422 | Operation requires 2FA, which isn’t on. |
| 1204 | TWOFA_NOT_STARTED | 422 | TOTP setup not in progress. |
| 1205 | TWOFA_USE_TOTP | 422 | Email channel offered but account uses TOTP. |
| 1206 | TWOFA_USE_EMAIL | 422 | TOTP channel offered but account uses email. |
| 1207 | TWOFA_CODE_USED | 410 | Recovery code already consumed. |
| 1208 | TWOFA_PROVIDE_CODE | 422 | Provide a TOTP or recovery code. |
Account access (1300–1311)
| Code | Key | HTTP | Meaning |
|---|---|---|---|
| 1300 | FORBIDDEN | 403 | Authenticated but not permitted. Most common: API-key calling a dashboard-only route, or On-Behalf-Of set on /merchant/*. |
| 1301 | ACCOUNT_SUSPENDED | 403 | Merchant or sub-user account suspended. |
| 1302 | ACCOUNT_DELETED | 403 | Account is soft-deleted. |
| 1303 | ACCOUNT_LOCKED | 403 | Temporarily locked (e.g. failed login bursts). |
| 1304 | EMAIL_NOT_VERIFIED | 403 | Operation requires a verified email. |
| 1305 | API_KEY_IP_DENIED | 403 | Source IP not on the key’s allowlist. |
| 1306 | USER_NOT_OWNED | 403 | Acting on a user that doesn’t belong to your merchant. |
| 1307 | USER_NOT_FOUND | 404 | Sub-user not owned by this merchant (or doesn’t exist — same response, no enumeration). |
| 1308 | INVALID_INVITE | 422 | Invite token bad/expired. |
| 1309 | INVITE_EMAIL_MISMATCH | 422 | Invite is for a different email. |
| 1310 | INVALID_VERIFICATION | 422 | Email-verification code bad/expired. |
| 1311 | CAPTCHA_FAILED | 403 | CAPTCHA verification failed. |
User Settings (1400–1422)
| Code | Key | HTTP | Meaning |
|---|---|---|---|
| 1400 | WRONG_PASSWORD | 422 | Current password incorrect. |
| 1401 | NO_PASSWORD_SET | 422 | Account has no password (OAuth-only). |
| 1402 | SAME_EMAIL | 422 | New email matches current. |
| 1403 | RESTORE_INVALID | 422 | Invalid/expired restore token. |
| 1404 | RESET_INVALID | 422 | Invalid/expired password reset token. |
| 1405 | ACCOUNT_NOT_DELETED | 422 | Restore called on a non-deleted account. |
| 1410 | TRADEURL_LIMIT | 422 | Maximum number of saved trade URLs reached. |
| 1411 | TRADEURL_ALREADY_ADDED | 409 | Trade URL already saved. |
| 1412 | TRADEURL_NOT_FOUND | 404 | Trade URL ID doesn’t exist. |
| 1413 | TRADEURL_INVALID_TOKEN | 422 | Trade URL invalid, expired, or inventory private. |
| 1414 | TRADEURL_ESCROW | 422 | Account has trade hold (escrow) enabled. |
| 1420 | DISCORD_ALREADY_LINKED | 409 | Account already linked to a Discord. |
| 1421 | DISCORD_NOT_LINKED | 422 | Unlink called on an unlinked account. |
| 1422 | DISCORD_OAUTH_FAILED | 502 | Discord OAuth handshake failed. |
Wallet (1500–1522)
| Code | Key | HTTP | Meaning |
|---|---|---|---|
| 1500 | INSUFFICIENT_BALANCE | 422 | Wallet balance < required. |
| 1501 | WALLET_SUSPENDED | 403 | Wallet is suspended (e.g. ledger drift). |
| 1502 | WALLET_NOT_FOUND | 404 | Wallet doesn’t exist for that user/type. |
| 1503 | INVALID_CURRENCY | 422 | Currency value outside the supported set. |
| 1504 | INVALID_AMOUNT | 422 | Amount ≤ 0 or wrong format. |
| 1510 | IDEMPOTENCY_CONFLICT | 409 | Same Idempotency-Key, different request body. |
| 1511 | IDEMPOTENCY_KEY_REQUIRED | 422 | Idempotency-Key header missing on /fund. |
| 1520 | TRANSFER_CURRENCY_MISMATCH | 422 | Source and destination wallets in different currencies. |
| 1521 | MERCHANT_MUST_BE_USD | 422 | Merchant accounts must use USD. |
| 1522 | FX_RATE_UNAVAILABLE | 503 | FX rate unavailable — retry shortly. |
Deposits — gateway (1600–1608)
gatepay and onramp deposits.
| Code | Key | HTTP | Meaning |
|---|---|---|---|
| 1600 | DEPOSIT_NOT_FOUND | 404 | Unknown deposit ID. |
| 1601 | DEPOSIT_NOT_CANCELLABLE | 422 | Only initiated deposits can be cancelled. |
| 1602 | DEPOSIT_NOT_RESUMABLE | 422 | Deposit reached a terminal state. |
| 1603 | INVALID_QUOTE | 422 | Quote token bad or expired. Re-quote. |
| 1604 | INVALID_CHAIN | 422 | Unsupported chain for this currency. |
| 1605 | INVALID_DEPOSIT_CURRENCY | 422 | Unsupported deposit currency. |
| 1606 | DEPOSIT_MIN_AMOUNT | 422 | Below provider minimum. |
| 1607 | INVALID_SIGNATURE | 401 | Gateway callback signature failed. |
| 1608 | GATEWAY_ERROR | 502 | Payment gateway returned an error. |
Crypto deposits / withdrawals (1800–1811)
Self-hosted EVM deposit/withdrawal/transfer.| Code | Key | HTTP | Meaning |
|---|---|---|---|
| 1800 | CRYPTO_NOT_CONFIGURED | 503 | Crypto gateway not configured. |
| 1801 | CRYPTO_CHAIN_NOT_SUPPORTED | 422 | Chain not enabled. |
| 1802 | CRYPTO_CHAIN_PAUSED | 503 | Admin paused the chain. |
| 1803 | CRYPTO_TOKEN_NOT_SUPPORTED | 422 | Token not enabled on this chain. |
| 1804 | CRYPTO_ADDRESS_NOT_FOUND | 404 | No deposit address generated for user yet. |
| 1805 | CRYPTO_MIN_AMOUNT | 422 | Below minimum. |
| 1806 | CRYPTO_INVALID_DESTINATION | 422 | Destination address invalid. |
| 1807 | CRYPTO_WITHDRAWAL_NOT_FOUND | 404 | Withdrawal not found. |
| 1808 | CRYPTO_WITHDRAWAL_INVALID_STATE | 422 | Withdrawal not in a valid state for this action. |
| 1809 | CRYPTO_WEBHOOK_INVALID | 401 | Webhook signature bad. |
| 1810 | CRYPTO_SELF_TRANSFER | 422 | Cannot transfer to your own wallet. |
| 1811 | CRYPTO_RECIPIENT_WALLET_MISSING | 422 | Recipient has no wallet for this currency. |
Partner crypto payout custody (1820–1827)
| Code | Key | HTTP | Notes |
|---|---|---|---|
| 1820 | CRYPTO_PAYOUT_NOT_ENABLED | 403 | Merchant does not have cryptoPayoutEnabled=true. Admin-controlled toggle. |
| 1821 | CRYPTO_PAYOUT_FEE_EXCEEDS_MAX | 422 | Live fee at submission exceeds the merchant-supplied maxFeeUsdCents. No state mutated. |
| 1822 | CRYPTO_PAYOUT_INSUFFICIENT_BALANCE | 422 | Per-(chain, token) sidecar balance is below amountCents + fee. Each chain × token is an independent bucket — no cross-chain liquidity. |
| 1823 | CRYPTO_PAYOUT_NO_CALLBACK_URL | 422 | No active CallbackUrl registered for the merchant. Approval callbacks need somewhere to land before any withdrawal can proceed. |
| 1824 | CRYPTO_PAYOUT_DUPLICATE_EXTERNAL_ID | 409 | Only on a genuine concurrent duplicate racing the first create. A normal retry of the same externalId returns the original withdrawal (idempotent replay), not this error. |
| 1825 | CRYPTO_PAYOUT_INVALID_SUBUSER | 422 | forSubUser doesn’t resolve to a sub-user owned by the calling merchant. The value can be the sub-user’s UUID or your externalId, but only within your own children. |
| 1826 | CRYPTO_PAYOUT_FEE_UNAVAILABLE | 503 | The gas/price oracle is temporarily down on a fee chain. Fail-closed — retry shortly. |
| 1827 | CRYPTO_PAYOUT_NO_SIGNING_KEY | 422 | A webhook signing secret must be provisioned before requesting payouts, so the approval call can be signed. |
Merchant management (1900–1950)
| Code | Key | HTTP | Meaning |
|---|---|---|---|
| 1900 | INVALID_ADMIN_OP | 422 | Invalid admin/merchant operation. |
| 1910 | APIKEY_LIMIT_REACHED | 422 | Maximum active API keys reached. |
| 1911 | APIKEY_ALREADY_REVOKED | 422 | API key already revoked. |
| 1920 | SECRET_ALREADY_EXISTS | 409 | Active webhook secret already exists. |
| 1921 | SECRET_NOT_FOUND | 404 | Webhook secret not found. |
| 1922 | SECRET_ALREADY_REVOKED | 422 | Webhook secret already revoked. |
| 1930 | WEBHOOK_TEST_FAILED | 422 | Test ping didn’t return 2xx. |
| 1931 | WEBHOOK_NOT_FOUND | 404 | No active callback URL configured (or revoked / disabled). |
| 1932 | WEBHOOK_DELIVERY_NOT_FOUND | 404 | Delivery ID not yours / doesn’t exist. |
| 1933 | WEBHOOK_URL_INVALID | 422 | URL fails SSRF / scheme checks. |
| 1940 | EMAIL_TAKEN | 409 | Sub-user creation: email already registered. |
| 1941 | STEAM_ID_TAKEN | 409 | Sub-user creation: Steam account already registered. |
| 1942 | EXTERNAL_ID_TAKEN | 409 | Sub-user creation: externalId already taken by a different sub-user. |
| 1943 | SUB_USER_HAS_BALANCE | 422 | Can’t delete: balance is non-zero. |
| 1950 | INVALID_FEE_BPS | 422 | feeBps out of range (0–5000). |
Marketplace (2000–2041)
| Code | Key | HTTP | Meaning |
|---|---|---|---|
| 2000 | MARKET_UNAVAILABLE | 503 | No marketplace workers healthy. Retry once after 30s. |
| 2001 | MARKET_TIMEOUT | 504 | Marketplace worker did not respond in time. |
| 2002 | WORKER_LOW_BALANCE | 503 | Marketplace worker has insufficient balance to settle. |
| 2010 | ORDER_NOT_FOUND | 404 | Trade ID or external ID doesn’t exist in your scope. |
| 2011 | LISTING_NOT_FOUND | 404 | Listing sold / removed since you fetched it. |
| 2012 | LISTING_DETAIL_FAILED | 502 | Failed to fetch listing details from upstream. |
| 2020 | PRICE_MISMATCH | 409 | Listing price drifted above your maxPrice. |
| 2021 | PRICE_BELOW_MINIMUM | 422 | Quick-buy maxPrice below current minimum listing price. |
| 2022 | TRADEURL_REQUIRED | 422 | Sub-user has no primary Steam trade URL set. |
| 2030 | PURCHASE_FAILED | 502 | Marketplace partner rejected the purchase. |
| 2031 | PURCHASE_RETRY_TIMEOUT | 504 | Purchase retried but never settled. |
| 2032 | CALLBACK_INVALID | 401 | Marketplace callback signature failed. |
| 2040 | TRADE_NOT_CANCELLABLE | 409 | Trade not in a cancellable state. |
| 2041 | TRADE_CANCEL_TOO_SOON | 422 | Order must be at least 30 minutes old to cancel. |
Validation error shape
Whenkey === "VALIDATION_FAILED", the error includes per-field details: