Error codes
Every error response carries a code next to its message. The only exception
is the token endpoint, which keeps the OAuth shape (see Errors).
Code
Branch on code, not on message. A code is upper snake case and does not
change once published. A message is written for people, and its wording can
change at any time. Some codes add more fields to the body, such as
INSUFFICIENT_BALANCE; each one's row below says so.
Some codes appear with more than one status, for example USER_NOT_FOUND as a
400 on construct calls and a 404 on user reads. Treat the code as the
answer, and use the status only to decide whether to retry (see
Retries).
New codes can be added at any time. Handle a code you do not recognise by its HTTP status.
General
These can come from any endpoint. When an error has no more specific code, it gets the general code for its status.
| Code | Status | Meaning |
|---|---|---|
BAD_REQUEST | 400 | The request was refused, and no more specific code applies |
VALIDATION_FAILED | 400 | One or more fields failed validation. message joins every failure; the extra errors array lists them one per entry |
INVALID_PARAMETER | 400 | A path parameter is malformed, for example a userId that is not a UUID |
INVALID_CURSOR | 400 | A pagination cursor was not issued by this API, or was altered |
INVALID_PAGE | 400 | page is below 1 |
INVALID_AMOUNT | 400 | An amount is not a positive number, or cannot be parsed |
AMOUNT_TOO_SMALL | 400 | An amount is below the token's smallest unit |
UNSUPPORTED_CHAIN | 400 | The chain is not supported for this operation |
REQUEST_TOO_DEEP | 400 | The JSON body is nested too deeply |
UNAUTHORIZED | 401 | Authentication failed, and no more specific code applies |
FORBIDDEN | 403 | The caller is not allowed to do this |
NOT_FOUND | 404 | No such route or resource |
CONFLICT | 409 | The request conflicts with existing data |
PAYLOAD_TOO_LARGE | 413 | The request body is too large |
TOO_MANY_REQUESTS | 429 | Rate limited. Back off and retry |
INTERNAL_SERVER_ERROR | 500 | Something failed on our side. Retry with backoff |
NOT_IMPLEMENTED | 501 | The operation exists but is not available yet |
SERVICE_UNAVAILABLE | 503 | A dependency is unavailable. Retry with backoff |
Authentication
Returned on every /api/v1/s2s/… route except the token endpoint. Each 401
also carries a WWW-Authenticate: Bearer challenge.
| Code | Status | Meaning |
|---|---|---|
ACCESS_TOKEN_MISSING | 401 | No Authorization: Bearer header was sent |
ACCESS_TOKEN_INVALID | 401 | The token is malformed, expired, or was not issued by us. Fetch a new one |
CREDENTIAL_UNKNOWN | 401 | The credential that issued the token no longer exists |
CREDENTIAL_REJECTED | 401 | The credential is revoked or expired, or your organisation is suspended. Fetching a new token will not help |
SOURCE_IP_NOT_ALLOWED | 401 | The request came from an address that is not on the credential's IP allowlist |
S2S_NOT_CONFIGURED | 401 | The partner API is not enabled on this environment |
SCOPE_MISSING | 403 | The token is valid but lacks a scope this route needs. message names the scope |
Users
| Code | Status | Meaning |
|---|---|---|
USER_NOT_FOUND | 400, 404 | No such user, or the user belongs to another partner (see 404 on a user) |
WALLET_NOT_LINKED | 400 | The user has no wallet address yet. Link one before building a transaction |
WALLET_ALREADY_LINKED | 400 | The user already has a wallet. A linked wallet cannot be replaced |
EMAIL_ALREADY_REGISTERED | 409 | Another user already has this email |
WALLET_ALREADY_REGISTERED | 409 | Another user already has this wallet address |
USER_ALREADY_REGISTERED | 409 | The email or wallet address was registered by a concurrent request |
UNKNOWN_COUNTRY_CODE | 400 | countryCode is not a country code we recognise |
Staking
| Code | Status | Meaning |
|---|---|---|
POOL_NOT_FOUND | 400, 404 | No pool with that slug or ID |
POOL_DISABLED | 403 | The pool is turned off for your partner account |
STAKING_DISABLED | 400 | The pool is not accepting new stakes right now |
POOL_MISCONFIGURED | 400 | The pool cannot build transactions right now. Contact us |
MAX_AMOUNT_NOT_SUPPORTED | 400 | maxAmount: true is not available for unstaking from this pool type |
UNSTAKE_NOT_SUPPORTED | 501 | Unstaking is not available for this pool type yet |
INSUFFICIENT_POOL_LIQUIDITY | 400 | The pool cannot pay out this much right now. message gives the most it can pay |
TRANSACTION_BUILD_FAILED | 400 | The deposit or withdrawal could not be built. Retry, and contact us if it keeps failing |
Stocks
| Code | Status | Meaning |
|---|---|---|
STOCK_NOT_FOUND | 404 | No stock or commodity with that symbol in the catalogue |
STOCK_DISABLED | 403 | The stock is turned off for your partner account |
STOCK_TRADING_DISABLED | 400 | Buying or selling this stock is turned off right now. message says which side |
MARKET_DATA_UNAVAILABLE | 400 | No price or market data is available for the stock right now. Retry later |
MARKET_DATA_NOT_FOUND | 404 | The market data provider has no data for this symbol |
MARKET_DATA_PROVIDER_ERROR | 400, 500 | The market data provider refused the request or failed |
SWAP_PROVIDER_ERROR | 400, 500, 502 | The swap provider could not quote or build the trade. 502: it returned a transaction that failed our checks; retry later |
STOCK_MISCONFIGURED | 500 | The stock is set up incorrectly on our side. Contact us |
Bridge
| Code | Status | Meaning |
|---|---|---|
BRIDGE_ROUTE_NOT_FOUND | 404 | No route between that pair of chains and tokens |
BRIDGE_ROUTE_PAUSED | 400 | The route exists but is paused |
BRIDGE_AMOUNT_BELOW_MINIMUM | 400, 503 | The amount is below the route's minimum, or too low for the provider to relay |
BRIDGE_AMOUNT_ABOVE_MAXIMUM | 400 | The amount is above the route's maximum |
BRIDGE_FEE_TOO_HIGH | 503 | The provider's fee is above the route's ceiling right now. Retry later |
BRIDGE_QUOTE_EXPIRED | 400, 503 | The quote expired, or would expire before it could be signed. Build a new transaction |
BRIDGE_TRANSFER_NOT_FOUND | 404 | No transfer with that ID for this user |
BRIDGE_TRANSFER_NOT_PENDING | 400 | The transfer was already submitted or settled. message gives its status |
BRIDGE_PROVIDER_UNAVAILABLE | 503 | The bridge provider is unreachable or returned something unusable. Retry with backoff |
Transactions
Returned by every construct and send call (stake, unstake, buy, sell and bridge).
| Code | Status | Meaning |
|---|---|---|
INSUFFICIENT_BALANCE | 400 | The wallet cannot afford the batch with the platform fee included. Extra fields: token, required, available, shortfall (see Insufficient balance) |
INTENT_NOT_FOUND | 400 | The intentId has expired, was already submitted, was built for another user, or was built by another product's construct call (send a bridge batch to the bridge send, a stock trade to the stocks send). Build a new one; do not retry |
AUTHORIZATION_INVALID | 400 | The EIP-7702 authorization was not signed by the wallet that sends the batch, or its yParity is invalid, or the send carried an authorization when bundle.authorization was null (or left it out when it was not) |
BATCH_EMPTY | 400 | The batch has no calls |
GAS_SPONSORSHIP_UNAVAILABLE | 400 | Gas for this transaction cannot be sponsored right now |
BUNDLER_ERROR | 400, 503 | 400: the bundler rejected the user operation; message ends with the ERC-4337 code when there is one, e.g. (AA24) for a bad signature. 503: the bundler could not be reached or timed out; retry the same send |
BATCH_SEND_IN_PROGRESS | 409 | Another send for this intentId is still running. Wait for it rather than retrying |
BATCH_REVERT_LIMIT | 429 | Too many of this user's recent transactions reverted on-chain (for example, funds moved out before the batch landed). Try again later |
CHAIN_NOT_CONFIGURED | 400, 503 | The chain is not set up for transactions on this environment |
TOKEN_NOT_CONFIGURED | 400 | A token in the batch has no contract address on the chain |
Live stock prices stream
The stream uses the same convention on its error messages:
| Code | Meaning |
|---|---|
BAD_REQUEST | The message was not a JSON object, was sent as a binary frame, or had a missing or invalid field |
UNKNOWN_CHANNEL | No channel has that name |
UNAVAILABLE | The channel cannot serve right now. Retry later |
INTERNAL_ERROR | Something failed on our side. Retry later |
