Public Channels
Public channels are unauthenticated and keyed by numeric marketId. Book
levels are [price, qty] string pairs in micro-units ("1000000" = 1.0), so
they survive JavaScript number limits.
The direct institutional endpoints expose the identical frame contract at
wss://md.sentico-labs.xyz:9002/api/v1/ws/public and port 9003. They require
SC-HMAC-V2 upgrade headers from an active machine credential with read scope;
an anonymous upgrade receives HTTP 401. Sign GET, the exact path and query,
and an empty body as described in Authentication.
Ports 9002 and 9003 terminate in separate listener processes and can be used as
independent network paths. They do not change the event timestamp semantics or
sequence contract described below.
Channels
| Channel | Scope | Payload |
|---|---|---|
marketSnapshot | Market | The full public snapshot: book (yes/no for outcome markets, spot for spot markets), mids, last prices, 24 h volume and reference prices, recent trades, topOfBookChecksum |
l2Book | Market | { marketId, seq, prevSeq, stateVersion, topOfBookChecksum, book } — the same book object, without the surrounding statistics |
trades | Market | { marketId, seq, prevSeq, stateVersion, topOfBookChecksum, trades[] } — public trade tape without account fields |
bbo | Market | { marketId, seq, prevSeq, stateVersion, topOfBookChecksum, spot: { bid, ask } } for spot markets, yes/no best levels for outcome markets |
activeAssetCtx | Market | Per-market context (mids, last, 24 h reference); the compact companion to marketSnapshot |
Private fields are stripped before public frames leave the server; the
topOfBookChecksum lets you verify that your local top of book matches the
server after applying a frame.
Subscribe
{ "id": "sub-1", "method": "subscribe", "subscription": { "type": "l2Book", "marketId": 3, "depth": 20 } }
{ "id": "sub-2", "method": "subscribe", "subscription": { "type": "trades", "marketId": 3, "limit": 50 } }
{ "id": "sub-3", "method": "subscribe", "subscription": { "type": "bbo", "marketId": 14 } }
The server first completes the durable replay or snapshot bootstrap. It then
sends an acknowledgement that echoes the subscription and states
snapshotSeq and liveFromSeq, followed by snapshot_end; only then does
live delivery begin. A new typed-V2 stream can bootstrap at snapshotSeq: 0;
in that case liveFromSeq is 1 and the first delta carries prev_seq: 0.
Orderbook pattern
- Subscribe to
l2Bookfor the market. - Apply the snapshot the server sends after the ack (or fetch
GET /api/v1/public/markets/{marketId}/orderbook?depth=N&book=SPOT|YES|NOand drop frames withseq <= snapshot seq). The REST levels are{ "price_micros": 100000, "total_qty": "1000" }objects;total_qtyis the base-asset quantity in micro-units (the same scale as the trade tape'sqty), always a multiple of the market'slotSizeMicro. There is no/bookroute. - Apply deltas only while
prev_seqequals the lastseqyou applied. - Compare
topOfBookChecksumafter each frame; on a mismatch or a gap, resubscribe withfromSeqset to the last good sequence, and if the server answersresume_required, rebuild from REST.
Placement status on the recoverable market feed
The snapshot-and-delta endpoints at /api/v1/feed/public,
/api/v1/feed/public/a, and /api/v1/feed/public/b send a global status
frame right after the connection's session frame and whenever order
placement readiness changes:
{
"type": "status",
"sessionState": "cancel_only",
"placementReady": false,
"cancelOnlyAvailable": true,
"reason": "execution persistence degraded (1 unrecovered job(s))",
"tsMs": 1786259008273
}
This is connection state rather than sequenced market data. It has no seq,
prev_seq, marketId, or channel, is not replayed, and must not take part
in gap detection. Stop new placement when placementReady is false;
cancelOnlyAvailable says whether cancellation still works.
GET /api/v1/status remains available as a fallback after reconnects.
What the top-level ts measures
On /api/v1/ws/public, the server sets ts when it constructs the outgoing
JSON envelope in senticore_ws_frame. Engine apply, durable projection and any
SQL replay lookup have already happened by then. The remaining path is JSON
serialization, the frame cache, async task scheduling, the awaited WebSocket
send, the host TCP stack, edge proxy/CDN and the client receive loop.
Frames served from the serialization cache keep the timestamp from the first
encoding of that exact frame. Treat client_receive_time - ts as an
approximate gateway-to-client age, not as engine-to-client latency. The server's
internal apply_to_socket_write timing ends after the awaited socket send and
does not prove that the client received the bytes. These two measurements have
different start and end points and must not be added as if they were adjacent
stages.
Limits
The session frame carries maxSubscriptionsPerConnection and
publicMaxMarketsPerConnection. Exceeding either sends an error frame with
code 1008 and the rejected request id, then closes the connection with
code 4409. Market makers that need many markets should open several
connections. The redundant /feed/public/a + /b feed uses one market and
one channel per connection and is intended for latency-sensitive consumers.