Skip to navigation

Overview

One WebSocket session on which an organization's principals watch their trading state.

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

TopicPage
orderOrders
executionExecutions
accountBalanceAccount balances
accountAccounts
credentialCredentials
venueCapabilityVenue capabilities

Order entry

OpPage
submitOrderPlace order
cancelOrderCancel 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.

ParameterTypeRequiredDescription
opstringYesauth
reqIdstringYesEchoed byte-exact on the answer. Printable ASCII, escape-free; anything else answers MALFORMED.
paramsobjectYes
> tokenstringYesThe 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.

ParameterTypeRequiredDescription
opstringYessubscribe
reqIdstringYesEchoed byte-exact on the answer. Printable ASCII, escape-free; anything else answers MALFORMED.
paramsobjectYes
> topicsarray of stringsYesA 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.

ParameterTypeRequiredDescription
opstringYesunsubscribe
reqIdstringYesEchoed byte-exact on the answer. Printable ASCII, escape-free; anything else answers MALFORMED.
paramsobjectYes
> topicsarray of stringsYesA 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.

ParameterTypeRequiredDescription
opstringYesauth
reqIdstringYesEchoed byte-exact on the answer. Printable ASCII, escape-free; anything else answers MALFORMED.
successbooleanYestrue
sessionobjectYes
> capabilitiesarray of stringsYesScope 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.
> expiresAtNsstringYesWhen 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.

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

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.

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.

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.

ParameterTypeRequiredDescription
eventstringYesheartbeat
msgTypestringYesheartbeat
dataobjectYes
> streamAgeMsintegerYes

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.

ParameterTypeRequiredDescription
eventstringYessessionExpiring
msgTypestringYessessionExpiring
dataobjectYes
> expiresAtNsstringYes
> inMsintegerYes

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.

ParameterTypeRequiredDescription
eventstringYessessionClosing
msgTypestringYessessionClosing
dataobjectYes
> codeintegerYesOne of 4001, 4002, 4003, 4004.
> reasonstringYesOne of SESSION_EXPIRED, AUTH_FAILED, PRINCIPAL_REVOKED, RATE_LIMITED.
> retryablebooleanYes