Skip to navigation

Place order

A success is an offer, not an acceptance: it says the command reached the stream.

URL — No public environment serves this socket yet.

Op submitOrder

The owner’s verdict arrives on the order channel, correlated by the clientOrderId you chose — orderUpdate status: QUEUED, or orderRejected. The ack carries no orderId and cannot: none exists until the owner mints one.

Money is a decimal string, exact at the instrument’s own scale. price is rendered at the instrument’s priceScale and qty at its qtyScale, and a value that does not fit that scale exactly is REFUSED, never rounded: submitting a quantity you did not write is worse than refusing the one you did. Trailing zeros are fine — “0.0350” and “0.035” are the same number at scale 4.

Two aliases apiece, and the same rule for both. Name the account by account (its display name, org-scoped) or by accountId, and the instrument by instrument (its platform symbol) or by instrumentId — at least one of each pair, and both stated must agree. Sending both is fine and is what an interface holding the id it resolved and the name a trader typed will do; they are checked against each other, and only a disagreement refuses (MALFORMED), because acting on either would act on a guess about which half you meant. The HTTP lane states the identical rule for the identical body.

Prefer the ids where you hold them. A symbol is reference data’s to change and the id is not, so a rename between the read and the submit turns a symbol into UNKNOWN_INSTRUMENT while the id still resolves.

Sizing and precision come from reference data, not from this wire. price is exact at the instrument’s priceScale and qty at its qtyScale, and the owner further refuses TICK_SIZE_VIOLATION, QTY_STEP_VIOLATION, QTY_BOUND_BREACH and MIN_NOTIONAL_BREACH against the instrument’s tick, step, bounds and minimum notional. Those values live on the refdata instrument row — note a scale is not a tick: priceScale is decimal places, and a venue’s tick can be coarser than the scale, so the scales alone cannot tell you whether a price is placeable.

A reference-data API that serves the instrument row (the v2 GraphQL instruments endpoint’s successor) is planned and not yet available. Until it ships there is no way to pre-validate sizing from a published surface, and a mis-sized order costs a round trip: the refusal names the observed value against the limit it breached (observedValue/limitValue with a unit), which is what to show a trader meanwhile. This document will name that endpoint once it exists.

Request parameters

ParameterTypeRequiredDescription
opstringYessubmitOrder
reqIdstringYesEchoed byte-exact on the answer. Printable ASCII, escape-free; anything else answers MALFORMED.
paramsobjectYes
> clientOrderIdstringYesYour idempotency key and your correlation handle, 1-36 characters. Every answer about an order before it has an orderId carries it back — the submitOrder ack does not, because none has been minted, so this is the only handle you hold until the owner admits it. The bound is the platform’s own (OrdersView.CLIENT_ORDER_ID_MAX_LENGTH), not this edge’s, and a contract test holds the two equal. It is 36 rather than 64 because a canonical hyphenated UUID fits in 36 and the owner refuses longer as INVALID_FIELD; an id over it is refused here first, as INVALID_CLIENT_ORDER_ID, so the refusal names the field rather than arriving from the owner a round trip later. What the key deduplicates against, exactly. It is scoped to (organization, clientOrderId) and held for as long as the platform retains the order — the retention window, not merely while the order is live. Re-sending a submit under a key you already used restates the existing order’s outcome and never places a second order, which is what makes it safe to retry. CLIENT_ORDER_ID_CONFLICT does not mean “you reused your own key”: it means another user of your organization holds that key, and your submit was not applied. Recovering an outcome you never saw. If the socket drops after a submitOrder ack and you cannot tell whether the order was admitted — it is absent from the order image, which reads the same for “refused” and “not folded here yet” — re-send the identical submit under the same clientOrderId. That is the recovery procedure, not a risk: you get the original order’s outcome if it was admitted, and a fresh attempt if it was not. Minting a new key instead is what risks two orders. Separately, a refusal answer to a write guarantees that nothing reached the stream — the command was never published — so re-sending is always safe.
> accountstringNoThe account’s display name. May be sent beside accountId; the two must then resolve to the same account.
> accountIdintegerNoThe account’s id. May be sent beside account; the two must then resolve to the same account.
> instrumentstringNoThe instrument’s platform symbol. May be sent beside instrumentId; the two must then resolve to the same instrument.
> instrumentIdstringNoAn instrument, by the platform’s own id — an int64 as a decimal string, because a JSON number cannot hold one. One id space across this wire and the HTTP lane, and the same one both order doors take: the id you read here is the id you send, on submitOrder and on POST /orders alike. It is the platform’s own reference-data key and nothing else’s. It is not a venue symbol, and it is not an id from any other system — in particular it does not equal an immix-api v2 Instrument.id, and there is no arithmetic that maps between them. A client holding v2 ids resolves through the symbol, once, at cutover: instrument beside this field carries the platform symbol (OKX@BTC/USDT), which is what both systems can be joined on. The id is the stable address and the symbol is the convenience: a symbol is reference data’s to change and this id is not.
> sidestringYesOne of BUY, SELL.
> orderTypestringYesOne of LIMIT, MARKET.
> timeInForcestringYesOne of GTC, IOC, FOK.
> pricestringNoRequired for LIMIT, and refused on MARKET — a MARKET order states no price. A decimal string, exact at the instrument’s priceScale.
> qtystringYesA decimal string, exact at the instrument’s qtyScale.
> postOnlybooleanNo
> reduceOnlybooleanNofalse v1 refuses reduceOnly outright — there is no positions domain to reduce against — so the only value this version accepts is false, and orderRow and venueCapabilityRow both say the same. It stays on the wire as a field rather than being removed, so it can widen additively when a venue earns it.

Response parameters

Offered, not accepted. The command reached the stream; nothing about admission is known yet and this frame says nothing about it. Wait for orderUpdate status: QUEUED or orderRejected on the order channel, matching on the clientOrderId you sent. There is no orderId here because none has been minted.

ParameterTypeRequiredDescription
opstringYessubmitOrder
reqIdstringYesEchoed byte-exact on the answer. Printable ASCII, escape-free; anything else answers MALFORMED.
successbooleanYestrue

Refusal parameters

ParameterTypeRequiredDescription
opstringYesThe op being answered, or “error” when it could not be read.
reqIdstring, nullableYes
successbooleanYesfalse
codestringYesA closed enum, but tolerate an unknown value: codes are appended as the surface grows, and retryable tells you what to do whatever the code says. The door’s codes — INVALID_*, UNKNOWN_INSTRUMENT, UNKNOWN_ACCOUNT, UNKNOWN_ORDER, NOT_SERVING, OWNER_ABSENT, BACKPRESSURE — arrived in 1.1.0 beside the ops that write, and are the whole of what that version added here. A client generated against 1.0.0 meets them the first time it sends a write. OWNER_ABSENT is reserved and not emitted by this version: when the orders owner is not established, a write refuses NOT_SERVING today. It is published so that a client’s handling of it is written once rather than added later, but do not expect to observe it before a release says otherwise. One of NOT_PRIMED, UNAUTHENTICATED, SESSION_EXPIRED, RATE_LIMITED, MALFORMED, UNKNOWN_OP, INVALID_TOKEN, FORBIDDEN_PRINCIPAL, ORG_NOT_ACTIVE, AWAITING_ADMISSION, FORBIDDEN_CAPABILITY, UNKNOWN_TOPIC, TOPIC_NOT_ACTIVE, TOO_MANY_TOPICS, INVALID_FIELD, INVALID_PRICE, INVALID_QTY, INVALID_CLIENT_ORDER_ID, UNKNOWN_INSTRUMENT, UNKNOWN_ACCOUNT, UNKNOWN_ORDER, NOT_SERVING, OWNER_ABSENT, BACKPRESSURE.
messagestringYesDisplay text. May change between versions; never parse it.
retryablebooleanYes
failedTopicIndexintegerNoPresent on a topic refusal: the zero-based index into params.topics AS SENT of the entry that caused it.