Skip to main content
Orders are signed client-side. The client builds an EIP-712 Order struct, signs it with the trading wallet, and submits the signed values. The server recovers the signer, recomputes the order hash, enforces the market’s trading rules, and hands the order to the matching engine. The API signs nothing on a trader’s behalf, and no endpoint can amend a signed order, because the flags and prices are inside the signature.

Before signing

Read GET /assets for the target market. The signature covers marketId while the request body carries assetId, so both are required:
marketId is nullable. A market that has never published a snapshot reports null, and no valid order can be signed for it, because the signature covers that field. tickSize, lotSize, and maxLeverage are also nullable, and here null means the market publishes no such constraint rather than that the market is unusable. Where a value is present, prices must be a multiple of tickSize, sizes a multiple of lotSize, and leverage within maxLeverage; violations are rejected as TICK_SIZE, LOT_SIZE, or MAX_LEVERAGE. Read the fields per market rather than assuming a constraint exists. A universal 0.000001 precision floor applies regardless.

The Order struct

The domain, from GET /status, using exchange.chainId and exchange.clearingHouseAddress:
Fields, in this order:
Exactly one of postOnly or reduceOnly must be true.Settlement validates both sides of a match and rejects an order whose flags are equal, so an order with neither set can rest on the book but can never fill. Submitting one is rejected as INVALID_FLAGS. Because the flags are inside the signed struct, the server cannot correct them.
The four monetary fields are signed as 1e6 fixed-point integers but submitted as decimal strings. Scale for the signature, then send the human-readable form.

Submitting

The submitted fields must agree with the signed values, or the recovered hash will not match and the order is rejected as INVALID_SIGNATURE. Response:
Idempotency is by order hash: resubmitting an identical signed order returns the original with idempotent: true. There is no client order id, but the order hash can be computed locally before submitting, so the identifier is known in advance.

Order types

A limit order executes at its price or better, and rests on the book until it fills, expires, or is cancelled. A market order still carries a limitPrice, which acts as a slippage bound, because it is submitted as an aggressive limit order and so cannot execute at an unbounded price. The matching engine rejects a market order into an empty book, so limit orders are the safer choice for quoting rather than taking.

Batching

POST /orders/batch takes up to 20 orders and POST /orders/batch-cancel up to 50 hashes. Both return per-item results rather than failing as a unit:
Always check each entry, because a partial success is normal.

Accepted is not filled

A 200 means the engine took the order. It can still reject it afterwards, and that surfaces on the order rather than on the submit response. Poll GET /account/orders/{orderHash} or watch the orders stream. A rejected or cancelled order carries rejectCode, a small integer from the engine, and rejectReason, its decoded description. Both are null while the order is live.
rejectCode is decoded against two different maps, selected by the order’s status. The same integer means different things in each: code 2 is a rate limit on a rejected order but an expiry on a cancelled one. Read status first, then decode. An unrecognised code carries a null rejectReason.
For a rejected order, branch on rejectCode and treat rejectReason as display text. These are the codes a trading client meets most often; the engine’s map is larger: For a cancelled order the codes are unrelated to the table above:

Cancelling

Dead man’s switch

POST /orders/cancel-all-after arms a countdown. If it lapses, every open order for the key is cancelled, so a bot that crashes or loses connectivity does not leave exposure resting on the book.
Refresh it every 15 to 20 seconds while the strategy is running. Send { "timeout": 0 } to disarm.
Treat this as mandatory for any unattended strategy. It is the only mechanism that withdraws resting orders when the client process itself is what failed.

When trading is halted

While trading is paused, order submission returns 503 TRADING_DISABLED and the system:status stream fires. Cancels remain accepted, deliberately, so exposure can always be pulled during a halt. Error handling should let a halt stop submissions without also blocking cancels. GET /assets also reports a tickerState per market: AUCTION means orders accrue without continuous matching, CONTINUOUS is normal matching, DELISTED markets stay listed so old positions still resolve, and UNKNOWN means the book has never published a snapshot.
tickerState comes from the last published order-book snapshot, so it is only as fresh as that market’s snapshot cadence, and a market that stops publishing keeps reporting its last phase. Treat it as the last observed phase, not a fact about this instant, and never use it as the sole gate on submission, because the engine’s own rejection is authoritative.