> 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/authentication/llms.txt. > For AI client integration (Claude Code, Cursor, etc.), connect to the MCP server at https://docs.immix.xyz/_mcp/server. # Authentication **`POST /v1/ws-tickets — sent`** ```json title="POST /v1/ws-tickets — sent" { "stream": "trading" } ``` **`POST /v1/ws-tickets — received`** ```json title="POST /v1/ws-tickets — received" { "ticket": "", "expiresAtNs": "", "stream": "trading" } ``` **`The session is open and authenticated — received`** ```json title="The session is open and authenticated — received" { "event": "sessionOpened", "msgType": "sessionOpened", "data": { "capabilities": [ "users:manage" ], "expiresAtNs": "" } } ``` **`Re-authenticate the session — sent`** ```json title="Re-authenticate the session — sent" { "op": "auth", "reqId": "", "params": { "token": "" } } ``` **`Authentication succeeded — received`** ```json title="Authentication succeeded — received" { "op": "auth", "reqId": "", "success": true, "session": { "capabilities": [ "users:manage" ], "expiresAtNs": "" } } ``` **`A request was refused — received`** ```json title="A request was refused — received" { "op": "auth", "reqId": "", "success": false, "code": "NOT_PRIMED", "message": "", "retryable": true } ``` **`This session's credential lapses soon — received`** ```json title="This session's credential lapses soon — received" { "event": "sessionExpiring", "msgType": "sessionExpiring", "data": { "expiresAtNs": "", "inMs": 0 } } ``` 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. ## Credentials ### `ticket` **Sent as** query parameter `ticket` The browser's credential: a short-lived, single-use ticket from trading-gateway's `POST /v1/ws-tickets`, on the handshake URL. One of the two schemes, never both. ### `bearer` **Sent as** header `Authorization: Bearer …` (JWT) Every other client's credential: the access token as `Authorization: Bearer …` on the handshake, stating an expiry. One of the two schemes, never both. ## Create a WebSocket ticket **REST API** `POST /v1/ws-tickets` Exchanges your bearer token for a ticket that opens the trading WebSocket once: pass it as `?ticket=` on the WebSocket URL before `expiresAtNs`. It carries your identity and never outlives your token; never cache or log it. No `Idempotency-Key`: every call mints a new ticket. [The guide](/guides/rest-api-guide#websocket-tickets). ### Request parameters | Parameter | Type | Required | Description | | --------- | ------ | -------- | -------------------------------------------------------- | | `stream` | string | Yes | The WebSocket stream the ticket opens. One of `trading`. | ### Response parameters | Parameter | Type | Required | Description | | ------------- | ------ | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | `ticket` | string | Yes | Opaque and URL-safe as served: put it in the WebSocket URL as `?ticket=` unchanged, and never parse it. It opens one session, once. | | `expiresAtNs` | string | Yes | When the ticket stops opening sessions, in epoch nanoseconds: the ticket's lifetime, or your token's expiry if that comes first. Open the WebSocket before then, or create another ticket. | | `stream` | string | Yes | The stream the ticket opens. One of `trading`. | ### Refusals | Status | Description | | ------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | 400 | Refused at the door — nothing reached the platform and the key was not journaled: a missing or malformed `Idempotency-Key` or `If-Match`, malformed JSON, a body `userId`, a body restating the path or `If-Match` differently, a money string off its `pattern` (an exponent, a plus sign, a bare point, surrounding whitespace) or past its `maxLength`, or one no value here can state (over 18 fractional digits, or past a 64-bit integer at its own scale), or any other value outside the schema. | | 401 | No bearer token, or one that does not verify. | | 403 | Authenticated, but not admitted. `FORBIDDEN_PRINCIPAL`: a token with no `org_id`, a non-ASCII `org_id` or `sub`, a disabled user, or a local binding with no user. `AWAITING_ADMISSION`: your user is not yet admitted — every route but `GET /me` answers this until an administrator admits you. `ORG_NOT_ACTIVE`: your organisation is suspended, pending activation, retired or unregistered. The record owners' 403s (`CAPABILITY_DENIED`, `SELF_APPROVAL`, `NOT_PROPOSER`, …) are refusals on the platform's stream: they carry the position header, and a retried key replays them with `Idempotent-Replay`. | | 429 | Shed at once: `INBOX_FULL` or `IN_FLIGHT_FULL` (this member's bounds; nothing queued, the key not journaled), the intake's or the ticket route's `RATE_LIMITED`, or the orders owner's `RATE_CEILING` (the organisation's per-minute ceiling — a refusal on the stream, replayed like any, so its `retryable` is `false` and the order goes again under a fresh key). `Retry-After` says when. | | 503 | This member cannot mint tickets now: `NOT_PRIMED` — it is still catching up; retry shortly — or `TICKETS_UNAVAILABLE` — it holds no ticket signing key, so a retry here changes nothing: a browser cannot open the trading WebSocket through this member until it serves tickets, and any other client sends its bearer token on the WebSocket handshake instead. | ## The session is open and authenticated The first frame of every session, sent before the client says anything — unless access was revoked between the handshake and this frame, when the first frame is `sessionClosing` (`PRINCIPAL_REVOKED`, 4003, not retryable) instead. The session was authenticated on its handshake, so there is no `auth` to ack, and this carries what the ack carries — the grant, never the grantee. Subscribe straight away. To renew the credential before `expiresAtNs`, re-auth over the open socket with `op: auth`, for the same principal; its answer is `authAck`. | Parameter | Type | Required | Description | | ---------------- | ---------------- | -------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `event` | string | Yes | `sessionOpened` | | `msgType` | string | Yes | `sessionOpened` | | `data` | object | Yes | What this session may do and until when — the grant, never the grantee: no identifier rides it. Read at admission and fixed for the session. | | > `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). | ## Re-authenticate the session **Op** `auth` The in-band re-auth: a session is authenticated on its handshake (a `ticket` or a bearer token), and this op renews its credential over the open socket — answer the `sessionExpiring` hint with it, or schedule it off `expiresAtNs`. It is never the way in: from 1.4.0 a handshake without a credential is refused, so no session ever owes it. 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. ### Request parameters | 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. | ### Response parameters 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 | What this session may do and until when — the grant, never the grantee: no identifier rides it. Read at admission and fixed for the session. | | > `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). | ### 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. `UNAUTHENTICATED` is **not emitted from 1.4.0**: every session is authenticated on its handshake, so no op can arrive before authentication. It stays in the set, which is append-only. `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. | ## 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 | |