Authentication
| Code | HTTP | Meaning |
|---|---|---|
auth_required | 401 | X-Api-Key header missing on an authenticated endpoint |
api_key_bad_format | 401 | X-Api-Key header didn’t match ps_<env>_<keyId>_<secret> shape |
api_key_unknown_key | 401 | keyId not found in the registry (typo, wrong environment, or revoked long ago and garbage-collected) |
api_key_bad_secret | 401 | Stored hash didn’t match — wrong secret |
api_key_revoked | 401 | Key was explicitly revoked via admin |
api_key_expired | 401 | Key’s expiresAt has passed |
api_key_suspended | 401 | Partner was suspended — all their keys stop working until reactivation |
api_key_ip_denied | 401 | Caller IP not in the key’s ipAllowlist |
api_key_no_associated_wallet | 401 | single_wallet partner has no associatedWallet attached — admin must attach one before authenticated endpoints work |
api_key_user_wallet_required | 401 | multi_wallet partner: X-User-Wallet header missing on an authenticated request |
api_key_user_wallet_invalid | 401 | multi_wallet partner: X-User-Wallet header value isn’t a 0x + 40-hex address |
api_key_scope_missing | 403 | Key lacks the scope required by this endpoint; response body includes requiredScope + have[] |
wallet_banned | 403 | The request’s effective wallet (associated or X-User-Wallet) is on the banned list |
Trading
| Code | HTTP | Meaning |
|---|---|---|
invalid_amounts | 400 | price or quantity ≤ 0 or bad format |
invalid_tif | 400 | MARKET+GTC rejected |
bad_signature | 400 | EIP-712 recover failed |
fee_too_high | 400 | feeRateBps > MAX_FEE_RATE_BIPS (1000) |
expired | 400 | Order expiration in the past |
invalid_outcome | 400 | Outcome index out of range |
insufficient_funds | 200 (envelope) | Balance overdraft |
market_not_open | 409 | Market status ≠ OPEN |
idempotency_race | 500 | Internal race on (wallet, clientOrderId) |
matcher_error | 200 (envelope) | Matcher returned a business error |
exchange_unavailable | 503 | Exchange-service / matcher unreachable |
Orders
| Code | HTTP | Meaning |
|---|---|---|
order_not_found | 404 | Order ID doesn’t exist for this wallet |
not_cancellable | 409 | Order already terminal |
forbidden | 403 | Order belongs to a different wallet |
Withdrawals
| Code | HTTP | Meaning |
|---|---|---|
invalid_amount | 400 | Amount ≤ 0 |
bad_signature | 400 | User EIP-712 signature invalid |
destination_not_cleared | 200 (envelope) | New destination below EDD threshold |
new_destination_edd | 200 (envelope) | New destination above EDD → MLRO review |
wallet_banned | 200 (envelope) | Destination on banned-wallets list |
aml_blocked | 200 (envelope) | Destination failed AML screen |
invalid_state | 409 | Tried to cancel after SUBMITTED |
not_found | 404 | Withdrawal ID doesn’t exist |
Faucet (testnet)
| Code | HTTP | Meaning |
|---|---|---|
faucet_disabled | 403 | Faucet not enabled |
faucet_daily_cap | 429 | Daily cap exceeded |
invalid_amount | 400 | Amount out of range |
Rate limits
| Code | HTTP | Meaning |
|---|---|---|
rate_limited | 429 | Bucket exhausted; retry after retryAfterSec |
Service-level
| Code | HTTP | Meaning |
|---|---|---|
exchange_unavailable | 503 | Upstream service temporarily unavailable — retry with backoff |
exchange_transport_error | 503 | Connection failed |
exchange_malformed_response | 503 | Non-JSON response |
not_implemented | 501 | Feature disabled in this env |
Handling strategy
- 4xx codes — fix the client.
- 200 with
code— business logic reject; surface to user. - 429 — wait
retryAfterSecthen retry. - 5xx — exponential backoff with jitter; 3 attempts max for
idempotent calls. Non-idempotent writes use
clientOrderId/noncedeadlinefor safe retries.