> For clean Markdown of any page, append .md to the page URL. > For a complete documentation index, see https://docs.immix.xyz/api-reference/trading/place-order/llms.txt. > For AI client integration (Claude Code, Cursor, etc.), connect to the MCP server at https://docs.immix.xyz/_mcp/server. # Place order **`Request — sent`** ```json title="Request — sent" { "op": "submitOrder", "reqId": "", "params": { "clientOrderId": "", "account": "", "instrument": "", "side": "BUY", "orderType": "LIMIT", "timeInForce": "GTC", "price": "", "qty": "" } } ``` **`Response — received`** ```json title="Response — received" { "op": "submitOrder", "reqId": "", "success": true } ``` **`Refusal — received`** ```json title="Refusal — received" { "op": "submitOrder", "reqId": "", "success": false, "code": "NOT_PRIMED", "message": "", "retryable": true } ``` Each frame is its shape, read off the contract: `` stands for a value, and a union shows its first form. **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 | Parameter | Type | Required | Description | | ----------------- | ------- | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | `op` | string | Yes | `submitOrder` | | `reqId` | string | Yes | Echoed byte-exact on the answer. Printable ASCII, escape-free; anything else answers MALFORMED. | | `params` | object | Yes | | | > `clientOrderId` | string | Yes | **Your 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. | | > `account` | string | No | The account's display name. May be sent beside `accountId`; the two must then resolve to the same account. | | > `accountId` | integer | No | The account's id. May be sent beside `account`; the two must then resolve to the same account. | | > `instrument` | string | No | The instrument's platform symbol. May be sent beside `instrumentId`; the two must then resolve to the same instrument. | | > `instrumentId` | string | No | An 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. | | > `side` | string | Yes | One of `BUY`, `SELL`. | | > `orderType` | string | Yes | One of `LIMIT`, `MARKET`. | | > `timeInForce` | string | Yes | One of `GTC`, `IOC`, `FOK`. | | > `price` | string | No | Required for LIMIT, and refused on MARKET — a MARKET order states no price. A decimal string, exact at the instrument's `priceScale`. | | > `qty` | string | Yes | A decimal string, exact at the instrument's `qtyScale`. | | > `postOnly` | boolean | No | | | > `reduceOnly` | boolean | No | `false` 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. | Parameter | Type | Required | Description | | --------- | ------- | -------- | ----------------------------------------------------------------------------------------------- | | `op` | string | Yes | `submitOrder` | | `reqId` | string | Yes | Echoed byte-exact on the answer. Printable ASCII, escape-free; anything else answers MALFORMED. | | `success` | boolean | Yes | `true` | ## Refusal parameters | Parameter | Type | Required | Description | | ------------------ | ---------------- | -------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `op` | string | Yes | The op being answered, or "error" when it could not be read. | | `reqId` | string, nullable | Yes | | | `success` | boolean | Yes | `false` | | `code` | string | Yes | A 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`. | | `message` | string | Yes | Display text. May change between versions; never parse it. | | `retryable` | boolean | Yes | | | `failedTopicIndex` | integer | No | Present on a topic refusal: the zero-based index into `params.topics` AS SENT of the entry that caused it. | > A success is an offer, not an acceptance: it says the command reached the stream.