Skip to main content
Browsers connect directly to wss://gateway.mainnet.kuru.io/ws on mainnet or wss://gateway.testnet.kuru.io/ws on testnet. The examples below target mainnet. One connection can multiplex market and user subscriptions. Control messages are JSON text frames; data messages are binary frames with an eight-byte subscription ID followed by a payload beginning with the KXMD magic bytes. Every server response carries a feedEpoch. The epoch is fixed for the server process and namespaces every market and user sequence. A sequence number is only meaningful together with the epoch in which it was issued.
The public frontend contract subscribes to finalized (@f) topics.

L2 deltas

Snapshot, replay, and live order-book changes.

Trades

Replayable market fills.

L2 book

Periodic complete order-book snapshots.

BBO

Periodic best bid and offer snapshots.

All mids

Mid prices for every active market.

User orders

Replayable order updates with periodic snapshots.

User balances

Replayable balance updates with periodic snapshots.

User trades

Replayable taker- and active-maker-side executions.

Control requests

Clients send control requests as UTF-8 JSON text frames. Binary frames are reserved for server data. The request object is strict: unknown keys are rejected instead of ignored.
The Gateway’s Rust parser accepts non-negative JSON integers across the full unsigned 64-bit range unless a narrower bound is stated below. JavaScript clients must restrict number-valued controls to Number.MAX_SAFE_INTEGER (9,007,199,254,740,991) because larger JSON numbers cannot be represented exactly. Binary-frame sequence values remain bigint; do not lossy-convert a larger cursor to number when constructing a control request.

Subscribe

The documentation and examples default to the finalized view. Every topic still requires an explicit view suffix: use @f for finalized, @v for voted, or @p for proposed. Proposed and voted subscriptions are intended for diagnostic clients; frontend applications should use finalized streams. The topic determines its scope, cadence, and whether seq is accepted: Replace @f with @v or @p to select another supported view. All other topic-specific field rules remain the same. Market subscriptions require m and reject u. User subscriptions require u and reject m. allMids rejects both selectors. The six L2 formatting fields are accepted together only by l2Book; supplying any of them to another topic returns bad_options. For significant-figure grouping, g=sf requires sf. mt is permitted only when sf=5. For native-tick grouping, g=tick requires a positive tk and rejects sf and mt. The ungrouped mode rejects tk, sf, and mt. seq must be greater than zero and is accepted only by the five replayable streams in the table. Whenever seq is present, feedEpoch is required. A feedEpoch may be supplied without seq to ensure a fresh subscription still belongs to the REST response’s namespace. Any asserted epoch is checked before replay or subscription ownership; a mismatch returns feed_epoch_mismatch. Subscribe to a market stream after applying sequence 1022:
Subscribe to a user stream after applying sequence 2951:
An accepted subscription receives an acknowledgement before bootstrap, replay, or live binary frames:
The id must be unique among active subscriptions on that connection. A duplicate ID or a connection subscription-cap rejection returns bad_options. The ID becomes reusable after a successful unsubscribe. Coalesced durations are positive milliseconds. Recommended frontend values are 50, 100, 250, 500, and 1000. A userOrders or userBalances duration must not exceed the server’s configured full-snapshot interval, which defaults to five seconds.

Unsubscribe

Unsubscribe by the same client-selected ID:
The operation is idempotent. The Gateway acknowledges it even when that ID is not currently active:

Application ping

An application-level ping may include a timestamp:
The Gateway echoes ts when present:
This JSON ping is separate from the native WebSocket Ping frame the Gateway sends every 15 seconds by default. Browsers answer native Ping frames with Pong automatically.

Control responses

All ack, pong, err, resync, and resub responses carry feedEpoch. Control responses are JSON text frames and are distinct from binary market and user data frames.

Acknowledgement and pong

ack confirms either a successful subscription or an idempotent unsubscribe. For a subscription, the acknowledgement is queued before any bootstrap, replay, or live binary data. pong echoes the optional timestamp from an application-level ping.

Errors

Invalid requests receive err with a stable code and a diagnostic message. id is optional: it is absent when parsing or validation fails before the Gateway can recover a request ID.

Market resynchronization

A market replay or live-join gap emits a market resync control. It has no subscription id; the affected stream is identified by topic t and market m. seq is the Gateway’s suggested first available sequence boundary.
Stop applying that market stream, discard its local derived state, and create a fresh subscription. For l2Delta, omit seq so the Gateway supplies a new authoritative L2 snapshot before live deltas.

User resynchronization

A user resync includes the affected subscription id, topic, a stable reason, and optional cursor information:
history_lost applies to userTrades, which has no authoritative snapshot with which to bridge an evicted history range. Refresh the corresponding REST history, then recreate the subscription using the supplied feedEpoch and seq as the last processed cursor when seq is present. latest is the latest sequence indexed for that exact user stream and may be absent. userOrders and userBalances first try to recover a retention gap transparently: the Gateway emits an authoritative snapshot, replays from that baseline, and continues the existing subscription without a control response. Only when it cannot establish a safe baseline does it emit:
On baseline_unavailable, discard the affected user state and resubscribe without seq; the Gateway will establish a new authoritative snapshot.

Resubscription

resub means the publisher-side subscription has already been removed. The WebSocket connection normally remains open and other subscription IDs are unaffected.
The reason is upstream_closed when the upstream publisher or forwarding task ended unexpectedly, or publication_gap when lossless live delivery could no longer prove continuity. Recreate only the identified subscription, normally using a new client-selected ID and the last cursor your client durably applied.

Sequence reconciliation

l2Delta and market trades use per-market marketSeq cursors. User orders, balances, and trades use per-user globalUserSeq cursors. An exact (market, view, topic) or (user, view, topic) stream can be sparse within its sequence domain, so continuity follows the predecessor encoded in each frame, not arithmetic adjacency. Treat the replay cursor as (feedEpoch, marketSeq) or (feedEpoch, globalUserSeq), and store it only after the corresponding payload has been applied. When seq is supplied in a subscription, feedEpoch is required. The server rejects a stale epoch with feed_epoch_mismatch rather than interpreting the sequence in a new namespace. For a market replay requested after sequence R, accept the first returned frame only when marketSeq > R and previousMarketSeq is null or less than or equal to R. After accepting that bridge frame, require each subsequent frame’s previousMarketSeq to equal the last accepted marketSeq exactly. User streams apply the same rule using globalUserSeq and previousGlobalUserSeq. A predecessor may be much lower than the current sequence; never derive it by subtracting one. For market trades, use feedEpoch and marketSeq from the Data Source market-trades response. For user streams, use feedEpoch and globalUserSeq from the corresponding Data Source user response. Gateway REST responses also carry feedEpoch, but the supported L2 bootstrap is the snapshot emitted by l2Delta itself. Start an L2 stream without seq when you need that initial snapshot. When a sequence-linked stream reports an unrecoverable gap, stop applying updates and follow the specific resync or resub instruction rather than guessing over the missing range. User-order and user-balance retention gaps may instead be repaired transparently with an authoritative snapshot. If a binary frame carries a different feedEpoch from the installed client state, discard it and bootstrap again. Never compare sequence numbers across epochs.

Binary frame header

Every binary data frame starts with the same 16-byte header: Kind-specific fields begin at byte 16. Kinds 2 (l2Delta), 3 (market trades), and market-scoped kind 4 (lifecycle) share this replayable market prefix after the common header: For l2Delta and market trades, sourceBlockNumber occupies bytes 60..68, sourceBlockId occupies bytes 68..100, and the record count occupies bytes 100..104. The market lifecycle body begins at byte 60. l2Book, BBO, and all-mids retain 16-byte signed price fields. Delta/trade/order prices use 8-byte signed fields. All prices are native pp integers. L2 delta tuples are 61 bytes; market trade tuples are 43 bytes; user snapshot orders are 104 bytes; active/passive user trades are 159/153 bytes. Wire version remains 1 for the coordinated testnet rollout. Update clients that assumed x18 snapshot units or the intermediate widened event-price layout together with the server.
The reference pages describe the exact objects returned by the shared envelope decoder’s message property. Use decodeExchangeWsEnvelope on socket bytes; decodeExchangeWsFrame accepts only the inner payload. The decoders preserve wire integers as bigint. Every price is in native market price precision (pp); market/order/trade quantities remain native size precision. Token balances retain native token decimals. Do not apply a second price conversion.

Lifecycle frames

Replayable streams may interleave lifecycle frames where the selected view requires them. The decoder returns the following shape:
Finalized market streams contain only promoted market data and do not emit lifecycle frames. Under the current user-retention semantics, finalized user streams may still replay promoted lifecycle controls. Consumers should handle kind: "lifecycle" rather than assuming every user frame is a business-data frame. See the userTrades response changelog for fee/source fields and the coordinated version-1 testnet decoder update.

Binary subscription envelope

Every binary socket message starts with an eight-byte unsigned big-endian id, followed by the existing KXMD payload. The id is the client-selected subscription id within that socket. This applies to all streams and to snapshots, replay and live messages; one prefix identifies the entire message, including all its rows. All existing layout offsets describe the inner payload; add eight for socket offsets. Use decodeExchangeWsEnvelope(data) for synchronous binary decoding or await decodeExchangeWsMessage(data) for binary/Blob input. Both return { id: bigint, message }; route message by id within the socket. The existing decodeExchangeWsFrame and stream-specific frame decoders accept only the inner payload after the prefix has been removed.
See decoder support before selecting an SDK decoder for these frame layouts.
Two subscriptions to the same topic can receive identical inner payloads with different outer IDs. Route by the envelope id, not by topic or frame kind: an l2Delta subscription also receives an l2Book bootstrap frame. The decoded field is named id, not subscriptionId. The inner KXMD header starts at socket byte 8; its payload starts at socket byte 24. JSON control messages and WebSocket ping/pong are unchanged. Wire version stays 1 on testnet, but this is a breaking framing change: deploy the Gateway and updated socket decoder together. Unprefixed messages are not auto-detected.