client_order_id is the contract
You supply client_order_id on every order. It must be unique per order and is
the idempotency key for the whole placement path.
The rule that matters: on any failure where you are unsure, retry with the
same client_order_id. At most one order ever results. Retrying with a new id
after a timeout is how duplicate orders happen.
Order states
Filter values → what you get
GET /v1/portfolio/orders?status= takes a comma-separated list of the names
above. Common patterns:
Terminal rows (
executed / canceled / expired / rejects) can disappear from
the live list once terminal_cap / reject_cap are hit. Resting rows never
evict.
Cancellations arrive as success
Placement responses are real outcomes
The API holds your request until the order is sequenced, so the response describes what actually happened -filled_qty, resting_qty, and a fills
array in stream order. There is no separate “pending” state to poll through.
seq on the response is the sequencer stamp, which is your audit join back to
canonical history.
Balances are not in the response
Reservation and fee accounting are deliberately absent from placement responses- they arrive on a different lane. Get them from
GET /v1/portfolio/balanceor, better, theuserWebSocket channel.
Reading your orders
status takes a comma-separated list. Querying by client_order_id is how you
resolve a timeout.
Cancelling
Cancels charge the cancel bucket (
2 × write rate), not the write bucket.
Places, decrease, and dead-man stay on write. For queue depth before a reprice,
call GET …/queue_position. See
Rate limits.
Batching
POST /v1/portfolio/orders/batch places several orders in one request, and
DELETE /v1/portfolio/orders/batch cancels up to 20 by id. Cost is 1 token
per HTTP request, not per item, so a 20-order batch spends one write (place)
or one cancel (cancel-batch) token. Exceeding 20 items is 400 batch_too_large
and nothing fires.
Direction
Orders areside × outcome × tick in the outcome’s own price space, see
Order direction.