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.
@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.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:
2951:
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:Application ping
An application-level ping may include a timestamp:ts when present:
Control responses
Allack, 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 receiveerr 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 marketresync 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.
l2Delta, omit seq so the Gateway supplies a new
authoritative L2 snapshot before live deltas.
User resynchronization
A user resync includes the affected subscriptionid, 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:
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.
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: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-endianid,
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.
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.