> 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/overview/llms.txt. > For AI client integration (Claude Code, Cursor, etc.), connect to the MCP server at https://docs.immix.xyz/_mcp/server. # Overview **`Authenticate the session — sent`** ```json title="Authenticate the session — sent" { "op": "auth", "reqId": "", "params": { "token": "" } } ``` **`Join topics — sent`** ```json title="Join topics — sent" { "op": "subscribe", "reqId": "", "params": { "topics": [ "order" ] } } ``` **`Leave topics — sent`** ```json title="Leave topics — sent" { "op": "unsubscribe", "reqId": "", "params": { "topics": [ "order" ] } } ``` **`Authentication succeeded — received`** ```json title="Authentication succeeded — received" { "op": "auth", "reqId": "", "success": true, "session": { "capabilities": [ "users:manage" ], "expiresAtNs": "" } } ``` **`Subscribe or unsubscribe succeeded — received`** ```json title="Subscribe or unsubscribe succeeded — received" { "op": "subscribe", "reqId": "", "success": true } ``` **`A request was refused — received`** ```json title="A request was refused — received" { "op": "", "reqId": "", "success": false, "code": "NOT_PRIMED", "message": "", "retryable": true, "failedTopicIndex": 0 } ``` **`Session liveness and stream freshness — received`** ```json title="Session liveness and stream freshness — received" { "event": "heartbeat", "msgType": "heartbeat", "data": { "streamAgeMs": 0 } } ``` **`This session's credential lapses soon — received`** ```json title="This session's credential lapses soon — received" { "event": "sessionExpiring", "msgType": "sessionExpiring", "data": { "expiresAtNs": "", "inMs": 0 } } ``` **`Why this session is about to be closed — received`** ```json title="Why this session is about to be closed — received" { "event": "sessionClosing", "msgType": "sessionClosing", "data": { "code": 4001, "reason": "SESSION_EXPIRED", "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. Authenticate with `op: auth`, then subscribe to any of six channels; each subscription is acknowledged and then pushed a complete image as numbered snapshot parts, after which every change to a row arrives as a delta on the same channel. Every channel is the session's organization's whole set. There is no account, symbol or organization parameter on `subscribe` — the topic vocabulary is a closed set of bare channel names, and a client filters what it wants. That is deliberate: a predicate with no parameter has no surface to be widened on. The client's rule for every channel is **upsert by key into a map that `part: 1` cleared**. Snapshot parts and deltas are the same shape and are applied the same way; the image is complete at `last: true`. A repeat `subscribe` for a held topic re-pushes the image, which is how a client forces a resync — there is no separate op for it. Writes — `submitOrder` and `cancelOrder` — are **served from 1.1.0**. A success is an OFFER, not an acceptance: it says the command reached the stream and nothing more. The outcome arrives on the `order` channel, correlated by the `clientOrderId` you chose — `orderUpdate` with `status: QUEUED` when the owner admits it, `orderRejected` when it refuses. Do not treat the ack as a placed order. **Telling two images apart.** A repeat `subscribe` re-pushes the image, so a client can start image N+1 while N is still arriving. Every snapshot part carries a `snapshotId`, the same across one image and different across the next: **apply only the parts whose id matches the one you are currently building, and discard the rest.** That is what keeps a straggling part of the old image out of the map the new one just cleared. The value is opaque — equality only. **Ordering, and what a client may rely on.** Everything a session receives is produced by one thread, and the guarantees follow from that. A `subscribe` ack precedes the first part of every topic it named. A topic's parts are contiguous — no delta of that topic arrives between them — and every delta that follows carries a `seq` at or past the part's. Topics named in one `subscribe` are imaged in the order named, each complete before the next begins. Across topics there is no ordering guarantee and none is needed: each is a map of its own. A session whose send queue overflows is disconnected rather than throttled; reconnect and resubscribe, since snapshots are local reads. A **write's ack precedes its outcome**, from the same one thread: the `submitOrder` or `cancelOrder` answer is written before any `order` frame that answers it, so a client never sees the outcome of a command it has not yet seen acknowledged. **What a subscription delivers is the ORGANIZATION's, not the session's.** The `order` channel carries every order of your organization — every principal's, and orders placed on the HTTP lane as well as this one. A client that toasts refusals must filter to what it sent (match on the `clientOrderId` values it minted, or on `userId`), or it will report another trader's refusal as its own. And a subscription is what makes an outcome reachable: a session that writes without holding `order` is acked and then **never told what happened** — the ack says the command reached the stream and nothing more. Subscribe to `order` before you write. **The inbound budget.** 50 frames per second refuses and 500 per second closes, each over a tumbling one-second window, per session. Over the first, the frame is answered `RATE_LIMITED` and **applied to nothing** — the refusal carries the op it answers, so an in-flight request is still resolved. At or past the second, the session is answered and then closed with code `4004`, whatever the op: a flood is transport hygiene. Both numbers are per-deployment configuration and may move; the shape of the answer will not. A `heartbeat` is pushed every 5 seconds by default, and its `streamAgeMs` is how long ago this member last applied a platform fact — pick a dead-socket timeout from a few multiples of that interval, never from `streamAgeMs` itself, which reads high on a quiet stream. **On `additionalProperties: false`.** Every payload below declares it, and that is a statement about the producer, not an instruction to the consumer: it is how this gateway's own tests refuse to serve a field the document does not carry. **Ignore properties you do not know.** Fields are added in minor versions — the changelog names each — and a client that refuses an unknown property will break on the first one. **Money.** Every money value is a decimal string rendered from the scale the value itself states — never through floating point, and never at a scale taken from reference data. Parse it as a decimal and compare numerically. Do not read meaning into the digit count: the same amount recorded at different precisions serves different strings, and "2.5" and "2.500000" are the same number. The HTTP lane serves the identical string for the same value. ## Channels | Topic | Page | | ----------------- | --------------------------------------------------------------- | | `order` | [Orders](/api-reference/trading/orders) | | `execution` | [Executions](/api-reference/trading/executions) | | `accountBalance` | [Account balances](/api-reference/trading/account-balances) | | `account` | [Accounts](/api-reference/trading/accounts) | | `credential` | [Credentials](/api-reference/trading/credentials) | | `venueCapability` | [Venue capabilities](/api-reference/trading/venue-capabilities) | ## Order entry | Op | Page | | ------------- | --------------------------------------------------- | | `submitOrder` | [Place order](/api-reference/trading/place-order) | | `cancelOrder` | [Cancel order](/api-reference/trading/cancel-order) | ## Authenticate the session The first op of every session: every other op before it answers UNAUTHENTICATED. The answer carries the GRANT, not the grantee — what this session may do and until when, with no identifier in it, because the client presented the token and already knows who it is. A session may re-auth with a fresh credential for the SAME principal, which renews its lease. A credential naming a different principal is refused FORBIDDEN\_PRINCIPAL and the session keeps the one it was opened with: the identity is immutable for the life of the socket. | Parameter | Type | Required | Description | | --------- | ------ | -------- | ----------------------------------------------------------------------------------------------- | | `op` | string | Yes | `auth` | | `reqId` | string | Yes | Echoed byte-exact on the answer. Printable ASCII, escape-free; anything else answers MALFORMED. | | `params` | object | Yes | | | > `token` | string | Yes | The bearer token, verbatim. | ## Join topics Validated whole, then answered, then armed — in that order. A frame naming several topics where one is bad arms NONE of them and names the offender in `failedTopicIndex`: a client that had to work out which half of its request took effect could not retry safely. Membership is a set, so a repeat subscribe for a held topic succeeds AND re-pushes the image. That is the documented resync. | Parameter | Type | Required | Description | | ---------- | ---------------- | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | `op` | string | Yes | `subscribe` | | `reqId` | string | Yes | Echoed byte-exact on the answer. Printable ASCII, escape-free; anything else answers MALFORMED. | | `params` | object | Yes | | | > `topics` | array of strings | Yes | A bare channel name — the whole closed vocabulary. The six served names carry data; the four reserved ones are real names whose domains have not landed and answer TOPIC\_NOT\_ACTIVE, which is a different thing from a typo (UNKNOWN\_TOPIC) and calls for a different response: wait for a release, rather than correct yourself. | ## Leave topics State, not history: leaving a topic never held succeeds, because the outcome asked for — not holding it — is already true. | Parameter | Type | Required | Description | | ---------- | ---------------- | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | `op` | string | Yes | `unsubscribe` | | `reqId` | string | Yes | Echoed byte-exact on the answer. Printable ASCII, escape-free; anything else answers MALFORMED. | | `params` | object | Yes | | | > `topics` | array of strings | Yes | A bare channel name — the whole closed vocabulary. The six served names carry data; the four reserved ones are real names whose domains have not landed and answer TOPIC\_NOT\_ACTIVE, which is a different thing from a typo (UNKNOWN\_TOPIC) and calls for a different response: wait for a release, rather than correct yourself. | ## Authentication succeeded The grant, never the grantee. `capabilities` are the register row's bits — not token claims, so they reflect what an administrator has granted rather than what was true when the token was issued — read **at admission** and fixed for the session. An administrator clearing one does not change an open session's grant: re-auth over the socket to pick up the new set. `expiresAtNs` is when the session closes; schedule a re-auth off it rather than decoding the token. | Parameter | Type | Required | Description | | ---------------- | ---------------- | -------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `op` | string | Yes | `auth` | | `reqId` | string | Yes | Echoed byte-exact on the answer. Printable ASCII, escape-free; anything else answers MALFORMED. | | `success` | boolean | Yes | `true` | | `session` | object | Yes | | | > `capabilities` | array of strings | Yes | Scope strings in choice order — the same vocabulary as the HTTP lane's GET /me, published here as an enum rather than promised in prose. Shape your affordances from these, never from a token claim. | | > `expiresAtNs` | string | Yes | When this session's credential lapses and the session is closed. "0" for a roster binding, which never expires (local and test only). | ## Subscribe or unsubscribe succeeded For a subscribe, the snapshots follow on the same lane, in the order the topics were named. The ack always precedes them. | Parameter | Type | Required | Description | | --------- | ------- | -------- | ----------------------------------------------------------------------------------------------- | | `op` | string | Yes | One of `subscribe`, `unsubscribe`. | | `reqId` | string | Yes | Echoed byte-exact on the answer. Printable ASCII, escape-free; anything else answers MALFORMED. | | `success` | boolean | Yes | `true` | ## A request was refused Answered on the op it answers, so a client routes refusals exactly as it routes successes. The one exception is a frame whose `op` could not be read at all, which answers with `op: "error"` — the only op-less shape on this wire. `retryable` says whether the IDENTICAL frame may be re-sent after a back-off. It is on the wire rather than left to prose because prose about which codes are retryable is how a client ends up retrying something that will never succeed. | 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. | ## Session liveness and stream freshness `streamAgeMs` is the stream-freshness floor, which is what lets a client tell a quiet market from a stalled member. | Parameter | Type | Required | Description | | --------------- | ------- | -------- | ----------- | | `event` | string | Yes | `heartbeat` | | `msgType` | string | Yes | `heartbeat` | | `data` | object | Yes | | | > `streamAgeMs` | integer | Yes | | ## This session's credential lapses soon Sent once per credential, one lead time before the lapse. Re-auth over the open socket with a fresh credential for the same principal and keep your subscriptions; renewing re-arms the hint. It exists because a client that schedules its own refresh may simply not be running when its timer fires — a throttled background tab — where an inbound frame is something the browser will deliver. | Parameter | Type | Required | Description | | --------------- | ------- | -------- | ----------------- | | `event` | string | Yes | `sessionExpiring` | | `msgType` | string | Yes | `sessionExpiring` | | `data` | object | Yes | | | > `expiresAtNs` | string | Yes | | | > `inMs` | integer | Yes | | ## Why this session is about to be closed Sent immediately before an edge-initiated close, so a client can tell a credential lapse from a network drop and act differently. `retryable` says whether reconnecting can succeed at all; `reason` says what to do about it. SESSION\_EXPIRED wants a fresh credential, RATE\_LIMITED wants a back-off and the same one, and a `retryable: false` close means the register is the obstacle — reconnecting is refused again however long you wait and whatever you present. The `code` values are RFC 6455's private-use range, and the SAME code travels on the WebSocket close frame that follows. Read either: this message if you are parsing frames and want `retryable` with it, or the close event's `code` if you are not — a browser surfaces that even when the last message was never read. | Parameter | Type | Required | Description | | ------------- | ------- | -------- | ----------------------------------------------------------------------------- | | `event` | string | Yes | `sessionClosing` | | `msgType` | string | Yes | `sessionClosing` | | `data` | object | Yes | | | > `code` | integer | Yes | One of `4001`, `4002`, `4003`, `4004`. | | > `reason` | string | Yes | One of `SESSION_EXPIRED`, `AUTH_FAILED`, `PRINCIPAL_REVOKED`, `RATE_LIMITED`. | | > `retryable` | boolean | Yes | | > One WebSocket session on which an organization's principals watch their trading state.