Error Responses
The /trade error model — read the response body, not the HTTP status.
POST /trade is synchronous and returns the same JSON trade-response shape whatever the HTTP status (200, 400, 429, 503, 504). Branch on the body, not the status line. A business rejection (below minimum, bad precision, insufficient balance) is data, not a transport error.
The envelope carries submission_status and, on accepted, a response envelope with the per-order outcome. A failed order does not always produce a top-level error — for order-ish actions the failure code lives in an {"error":"<code>"} leaf inside response while submission_status stays accepted. See Execution-level failures.
(One body never has this shape: a request over the 256 KiB /trade limit is refused before the handler runs and comes back as plain text. Guard your JSON parse.)
Only the transport layer raises — a non-trade-response 4xx/5xx body, or a wire failure before any response arrived. A decodable trade response is always returned to you as-is.
submission_status
The call blocks for the on-chain outcome and returns it directly.
There are exactly three values.
submission_status
When
Do next
accepted
The transaction landed and executed — for an order that covers rested, filled, or a benign IOC/FOK/self-trade/no-liquidity cancel; for a non-order action it committed. No error.
Done. The /trade response does not carry the fill state — read orderStatus to see whether an order rested or filled.
rejected
The write was refused — request-shaping, gateway (rate limit / suspension / expiry), node admission — or it failed at execution. error.code says why; tx_hash is present once canonical bytes exist.
If RateLimited, back off error.retry_after_ms and resend the same signed action. Otherwise fix the cause and submit a fresh action.
timeout
The outcome wasn't observed within the 3-second budget, or the submission couldn't be routed to a node.
Depends on error.code — the Handoff* family (503) never reached a node, so resubmit rather than lose the write; everything else may still land, so reconcile by cloid and never resubmit under a new nonce.
timeout is not rejected — the transaction may still commit in a later block, and resubmitting under a new nonce is the one move that can double-fill you. Reconcile by cloid via orderStatus / txStatusByCloid.
A timeout has three shapes, and only one of them is safe to resubmit:
error.code
HTTP
Reached a node?
Do next
(none) — the wait budget elapsed
200
Yes, it is executing
Reconcile by cloid
HandoffBufferFull:*
503
No — refused before any submission was attempted
Resubmit; nothing was delivered
HandoffTimeout / HandoffMultipleActive
503
No — no writable node accepted it
Resubmit; reconcile first if a duplicate would be costly
node_unreachable: …
504
Unknown — the connection broke mid-submission
Reconcile by cloid
Where a code comes from
Every error.code comes from one of four layers, and the spelling tells you which:
Layer
Style
Examples
HTTP
submission_status
Request-shaping
lowercase snake_case
invalid_json, invalid_quantity_precision, missing_cloid
400
rejected (no tx_hash)
Gateway
CamelCase / prefixed
RateLimited, PlaceOrderSuspended, ExpiredTx, HandoffTimeout, node_unreachable: …
429 / 503 / 504 / 200
rejected or timeout
Node admission
CamelCase, verbatim from the node
MinTradeSpotNtl, DuplicateCloid, InsufficientSpotBalance, AccountFrozen
200
rejected
Execution — order-ish
lowercase (the variant name)
tick, insufficientspotbalance, lotsize
200
accepted, code in the response leaf
Execution — envelope
CamelCase display form
BadNonce, BadSignature, ExpiredTx
200
rejected
One condition can surface at two layers with different spellings — an under-minimum order is usually caught at admission as MinTradeSpotNtl, but the same failure at execution reads mintradespotntl. Match on the code you actually receive.
Errors you'll actually hit
The wire carries error.code, not display copy — the Message column is illustrative UI text you'd render from the code. This is the curated subset you meet in practice; the full catalog is at the bottom of this page.
RateLimited
Over quota on either limiter, never admitted. HTTP 429, carries error.retry_after_ms. tx_hash tells them apart: the per-IP budget (1 req/s) is enforced before the body is parsed, so the reply has no tx_hash; the per-signer rate (1000 req/s) is enforced after canonicalization, so it does.
"Too many requests — retrying shortly."
Back off retry_after_ms, then resend the same signed action. The only safe resend.
PlaceOrderSuspended
The write path is degraded, so order placement is suspended: order, modify, and any batch that contains a non-cancel item are refused. Only cancel / cancelAll — and a batch whose every item is cancel / cancelAll — still go through; an empty batch is also refused. HTTP 503, error.retry_after_ms: 1000.
"Placing orders is paused — try again shortly."
Back off and retry; keep cancelling if you need to reduce exposure.
HandoffTimeout / HandoffBufferFull:{request_count|bytes|signer} / HandoffMultipleActive (timeout)
The submission couldn't be routed to a single writable node (leadership handoff / backpressure). submission_status: "timeout", HTTP 503, error.retry_after_ms: 1000. No node accepted it.
"Reconnecting — retrying your order."
Back off retry_after_ms and resubmit, rather than losing the write for the whole handoff. Reconcile by cloid first if a duplicate would be costly — see submission_status.
node_unreachable: … (timeout)
Node admission couldn't be reached; submission_status: "timeout", HTTP 504. The action may still land.
"Order submitted — confirming status."
Reconcile by cloid; never resubmit under a new nonce.
MinTradeSpotNtl
Order, modify replacement, or batch item is below the market's quote-asset minimum notional. Market orders use their protection price.
"Order must have a minimum value of 10 USDC."
Size up so price × quantity clears the minimum.
invalid_price_precision / invalid_quantity_precision
price / quantity has more fractional digits than the market's price_decimals / base_quantity_decimals.
"Price has too many decimal places for this market."
Snap to market precision before signing; send strings, never floats.
DuplicateCloid
The cloid is already open for this owner and market, or the tx repeats a (market_id, cloid).
"An order with this ID already exists."
Use a fresh cloid per order. When reconciling a timeout, look the existing one up — don't resend.
ExpiredTx
The signed expires_after_ms had already passed when execution reached the tx.
"Order expired before it was placed."
Widen expires_after_ms, re-sign, resubmit.
InsufficientSpotBalance
Balance-mode precheck found too little available balance for the order reserve.
"Insufficient balance."
Fund the account's quote asset — deposit from your main wallet in the web app.
DirectSignerIsActiveAgent
An active API-wallet key signed in direct-owner mode. An agent key may never sign as an owner.
"This API wallet can't trade as an owner."
Sign in agent mode — pass the owner accountAddress alongside the agent key.
AgentEpochMismatch
The signed agent_epoch doesn't match the live agent-slot epoch.
"Session expired — reconnecting."
Re-resolve agent_epoch from userAgents and resubmit. If it persists, the API wallet was revoked or re-approved — create a new one in the web app.
AccountFrozen
The account was frozen by an operator; while frozen, only cancel / cancelAll are admitted — new orders and modifies are rejected.
"Your account is frozen — trading is paused."
Stop placing orders until the freeze is lifted; cancels still go through.
Batch
A batch is one /trade call under one envelope nonce, so the envelope gets one submission_status. That status reflects the batch's admission and overall outcome — it does not report each item. Items execute in array order, and each item may individually succeed or fail inside the batch execution result. On accepted, read the per-item outcomes straight out of response.statuses[], in item order — no /info lookup needed. Fall back to one orderStatus per item only when the envelope came back timeout. Don't infer item outcomes from the envelope status.
Execution-level failures
An admitted action still runs against the book and can fail at execution. Because /trade is synchronous, that failure comes back on the /trade response — but where it appears depends on the action, and getting this wrong reads a failed order as a success.
Order-ish actions (
order,cancel,cancelAll,modify,batch) staysubmission_status: "accepted"with no top-levelerror. The code appears only as a leaf inside theresponseenvelope, as{"error":"<code>"}. How deep that leaf sits follows the action:response.status.errorfor anorder,cancel, ormodify;response.statuses[i].errorfor acancelAll;response.statuses[i].status.errorfor abatchitem. This coversinsufficientspotbalance,mintradespotntl,tick,lotsize,missingorder, and the rest.Non-order actions (
withdraw/settle/repay/approveAgent/revokeAgent) do map an execution failure tosubmission_status: "rejected"with a top-levelerror.code.Five envelope-level failures demote any action to
rejectedbecause they invalidate the transaction itself:badnonce,badsignature,expiredtx,malformedtx,featuredisabled. These surface in their CamelCase display form —BadNonce,BadSignature, and so on.
error.code at the top level is never a lowercase execution code for an order. If you are matching on error.code == "tick", you will never hit it — look in the response leaf instead.
tick
the response leaf, with submission_status: "accepted"
A non-integer price exceeded the market's max_price_sig_figs. The transaction landed; the order never entered the book.
Snap the price to the market's price_decimals / max_price_sig_figs before signing. The Python SDK checks this locally (LocalValidationError) and never sends it; see Decimals & units.
insufficientspotbalance / mintradespotntl / lotsize / missingorder
the response leaf, with submission_status: "accepted"
The order failed at execution for the stated reason.
Same handling as the CamelCase admission form of the condition — the difference is only which layer caught it.
BadNonce / BadSignature / ExpiredTx / MalformedTx / FeatureDisabled
top-level error.code, with submission_status: "rejected"
The transaction envelope itself was invalid, so nothing executed.
Re-sign correctly and submit a fresh action.
Full /trade error-code reference
Every error.code /trade can return. Request-shaping codes are lowercase snake_case (HTTP 400, no tx_hash); the gateway operational codes at the end of the first table carry their own HTTP status. Node-admission codes are CamelCase, returned verbatim.
Request-shaping and gateway errors:
invalid_json
The request body was not valid JSON.
must provide action + nonce and a signature or signatures
Required envelope fields were omitted.
must provide exactly one of signature or signatures
Both signature and signatures were present, or neither.
signatures_not_allowed_for_action
signatures (multisig) was sent for an action whose type does not accept a multisig proof.
invalid_signatures_len
The signatures array was empty or exceeded 32 entries.
signatures_required_for_action
A single signature was sent for a multisig-only action (e.g. deposit/ACCOUNTING, or an admin action under an active admin multisig policy).
insufficient_signatures
Fewer signatures than the required admin multisig threshold.
legacy_signature_not_accepted
A legacy (auth_scheme absent or "legacy") signature was sent for an EIP-712 cutover action (withdraw / settle / repay / approveAgent / revokeAgent, or an operator deposit/admin*). These require auth_scheme:"eip712".
eip712_not_allowed_for_action
auth_scheme:"eip712" was sent for a non-target action; only legacy is accepted for those.
eip712_agent_epoch_not_allowed
An auth_scheme:"eip712" request carried agent_epoch, which EIP-712 forbids.
query_view_unavailable
The query view was not yet available when the write path needed market/asset metadata. Transient — retry shortly.
unknown_market
The request referenced a market that is not in the current query view's market metadata.
unknown_asset
The request referenced an asset that is not in the current query view's asset metadata.
invalid_market_id
A market id was a valid u64 JSON value but exceeded the protocol u32 range.
invalid_asset_id
An asset id was a valid u64 JSON value but exceeded the protocol u32 range.
invalid_dst_address
withdraw.dst_address was not a 20-byte hex address.
invalid_dst_chain_id
withdraw.dst_chain_id exceeded the protocol u32 range. A zero chain id passes this check and is rejected later at admission as InvalidWithdraw (HTTP 200).
missing_cloid
An action that requires a client id omitted cloid.
invalid_cloid
A cloid was not a 16-byte hex value.
invalid_side
side was not bid, ask, buy, or sell.
invalid_order_type
order_type was not limit or market.
invalid_tif
tif was not gtc, ioc, fok, or alo.
missing_oid_or_cloid
A cancel or modify target omitted both oid and cloid. Does not apply to cancelAll, which carries no oid/cloid.
invalid_price
price was numerically too large to parse as decimal conversion input. Malformed decimal strings are rejected before this response shape.
invalid_price_precision
price had more fractional digits than the market's price_decimals.
invalid_price_overflow
Decimal-to-atom conversion for price overflowed u64.
invalid_quantity
quantity was numerically too large to parse as decimal conversion input. Malformed decimal strings are rejected before this response shape.
invalid_quantity_precision
quantity had more fractional digits than the market's base_quantity_decimals.
invalid_quantity_overflow
Decimal-to-atom conversion for quantity overflowed u64.
invalid_signature_hex
signature was not hex or did not decode to exactly 65 bytes.
encode_error: <TxCodecError>
The write path could not assemble canonical signed tx bytes, for example because a batch length exceeded codec limits.
empty_tx_bytes
Defensive guard: canonical byte assembly produced an empty byte vector. This should not occur for normal JSON requests.
decode_error: <TxDecodeError>
The write path assembled bytes but could not decode them or recover the authorization (single signature, or a multisig proof — empty/too-many/duplicate/unsorted recovered signers). For public JSON this is the usual shape for a malformed or unrecoverable signature.
RateLimited
Over quota on either limiter: the per-IP budget (1 req/s per endpoint, enforced before parsing — no tx_hash) or the per-signer rate (1000/s over a 1-second window, enforced after canonicalization — carries tx_hash). HTTP 429; includes retry_after_ms.
TooManyPending
Too many synchronous /trade waits are already in flight on this instance. HTTP 503, submission_status: "rejected", retry_after_ms: 50 — transient, retry immediately. Distinct from the same-named node-admission code below.
PlaceOrderSuspended
Order placement is suspended while the write path is degraded. Admitted: cancel, cancelAll, and a batch whose every item is cancel / cancelAll. Refused: order, modify, any batch that mixes in a non-cancel item, and an empty batch. HTTP 503, retry_after_ms: 1000.
ExpiredTx
The envelope's expires_after_ms was already past at the gateway clock; fast-failed before the node hop. HTTP 200, submission_status: "rejected".
HandoffTimeout / HandoffBufferFull:{request_count|bytes|signer} / HandoffMultipleActive
The submission could not be routed to a single writable node (leadership handoff / backpressure). HTTP 503, submission_status: "timeout", retry_after_ms: 1000.
node_unreachable: <tonic error>
The submit path could not complete node admission. HTTP 504, submission_status: "timeout".
Node admission pass-through errors:
QueryLagBackpressure
The node's query view is missing or more than four blocks behind execution; retry the same signed request after projection catches up.
DuplicateTxHash
The same transaction hash is already pending in ingress.
DuplicateAuthorityNonce
The same authority/nonce pair is already pending in ingress (authority is the recovered signer for single-sig, or the policy authority for multisig).
MalformedTx
The node could not decode canonical transaction bytes. Public JSON normally fails earlier if bytes cannot be built.
BadSignature
The node could not recover a signer from the canonical transaction signature. Public JSON normally fails earlier during signer recovery.
AuthorityHintMismatch
The decoded authority does not match the submit-path authority_hint (recovered signer for single-sig, derived policy authority for multisig). The hint did not match the canonical transaction.
WrongChainId
The signed payload's chain id did not match the node's configured chain id.
TooManyPending
Global pending capacity or per-owner pending capacity was reached at the node. (The API also emits this code itself, at HTTP 503 with retry_after_ms: 50, when its own synchronous-write concurrency is saturated — see the gateway table above.)
InvalidIngressConfig
The node ingress configuration was invalid.
DirectSignerIsActiveAgent
A signer currently registered as an active agent attempted direct-owner mode.
OwnerDoesNotExist
Direct-owner admission resolved to an owner account that does not exist.
UnknownAgent
Agent-mode submission used a signer that is not an active agent.
AgentEpochMismatch
Agent-mode submission used an epoch that does not match the active agent slot epoch.
AgentActionNotAllowed
Agent-mode submission attempted an action kind not allowed for agent signatures.
OracleUnavailable
A non-cancel SpotCreditAccount action was submitted while the oracle status was unavailable.
SpotCreditAccountFrozen
A non-cancel action was submitted for a frozen SpotCreditAccount, including settle by a frozen margin signer.
AccountNotFunded
Balance-mode precheck found no balance row for the asset required by the order reserve.
InsufficientSpotBalance
Balance-mode precheck found clearly insufficient available balance for an order reserve or repay debit.
MinTradeSpotNtl
An order, modify replacement, or batch order/replacement was below the current quote asset minimum notional. Market orders use their submitted protection price for this precheck.
InsufficientSpotCredit
Spot-credit precheck showed the single-order risk leg or settle post-position value would take available credit below zero.
DuplicateCloid
The submitted order or modify replacement cloid is already open for the same owner and market, or the tx contains duplicate (market_id, cloid) intents.
MarketNotFound
The latest acceptable QueryView has no referenced market.
OracleMarkPriceMissing
A SpotCreditAccount order path or settle post-position check needs a fresh mark price that is absent or stale.
AccountNotFound
Settle/repay admission found a required account missing after owner admission resolution.
BalanceOverflow
Admission proved a settle credit would overflow the destination balance.
ActionNotAllowedForSpotCreditAccount
Withdraw admission found the signer is a SpotCreditAccount where only balance-mode accounts are allowed.
InvalidWithdraw
Withdraw payload is invalid, for example zero amount, zero destination chain, or zero destination address.
WithdrawUnknownChainToken
Withdraw destination chain/asset is not configured, or the asset is missing.
WithdrawUnknownUser
Withdraw admission could not resolve the signer owner account.
WithdrawAmountBelowMinimum
Withdraw amount is below the configured minimum for (dst_chain_id, asset_id).
WithdrawAmountNotAboveFee
Withdraw amount is not greater than the asset's configured withdraw fee.
WithdrawDuplicateNonce
Withdraw business nonce is already committed or currently pending admission.
WithdrawInsufficientBalance
Withdraw admission proved the signer has insufficient available balance.
InvalidSettle
Settle payload, account shape, or margin position is invalid.
InvalidRepay
Repay payload, account shape, or target position is invalid.
AssetNotFound
A settle/repay asset reference was absent from the current committed state.
Overflow
Admission precheck arithmetic or id allocation overflowed.
AccountFrozen
The account was frozen by an operator (adminFreezeAccount); while frozen, only cancel / cancelAll are admitted.
V3SignatureSuperseded
The signature used the superseded v3 EIP-712 scheme for a withdraw / settle / repay; only the v4 scheme is accepted at submit — re-sign with v4.
Node-admission codes are returned verbatim (CamelCase). A failure at execution comes back synchronously with a lowercase execution code (e.g. tick) — see Execution-level failures above.
See also
Transaction SigningAPI AccessLast updated