API Reference / Real-time data
A WebSocket stream with per-topic subscriptions. Public topics need no auth; private topics carry the session token in the subscribe frame. The stream pushes deltas only; authoritative state comes from REST.
| Endpoint | ws://127.0.0.1:8090/api/v1/stream |
|---|---|
| Authentication | Public topics need none; private topics carry the session Bearer token in the subscribe frame's token field (read-only check, the session is not consumed). |
| Handshake token | The trading-line WS uses a short-lived handshake token: POST /api/v1/ws/handshake-token |
| Browsers | The same-origin proxy forwards REST only; WebSocket must connect to the stream host directly or through an upgrade-aware reverse proxy. |
{ "op": "subscribe", "topic": "px.crypto.BTC" }
{ "op": "subscribe", "topic": "private.0x3f1a…9c2e.px", "token": "<session bearer token>" }
{ "op": "unsubscribe", "topic": "px.crypto.BTC" }{ "op": "subscribed", "topic": "px.crypto.BTC", "needsSnapshot": true, "currentSequence": 184 }
{ "op": "message", "topic": "px.crypto.BTC", "sequence": 185, "serverTime": 1758240000000,
"type": "tick", "data": { "symbol": "BTC", "priceText": "118425.50", "tsMs": 1758240000000 } }
{ "op": "error", "topic": "private.0x…px", "message": "token address does not match topic" }| topic | Auth | data | Notes |
|---|---|---|---|
px.crypto.{coin} | Public | { "symbol": "BTC", "priceText": "118425.50", "tsMs": 1758240000000 } | Spot price (Pyth feed). All strings; priceText matches the REST history endpoint. |
px.updown.{series} | Public | { "series": "btc-5m", "marketId": "px-btc-5m-…", "roundStartMs": …, "roundEndMs": …, "resolution": null } | Round boundary events, type = rotate | resolved. On receipt re-fetch GET /px/updown; the payload carries no prices. |
private.{address}.px | Bearer session token in the subscribe frame | { "kind": "orders" | "positions" | "fills" } | Account change notifications. The token's address must match the topic; a mismatch rejects that subscription only, the connection stays up. Details are always re-fetched over REST. |
sequence is monotonic and gap-free within one connection and may reset across connections. Use it to detect dropped frames within a connection, not for cross-connection reconciliation.subscribed frame carries needsSnapshot: true and currentSequence. Fetch a snapshot over REST, then consume deltas starting at currentSequence + 1.const ws = new WebSocket("ws://127.0.0.1:8090/api/v1/stream");
let expected: number | undefined;
ws.onopen = () => {
ws.send(JSON.stringify({ op: "subscribe", topic: "px.crypto.BTC" }));
};
ws.onmessage = async (event) => {
const frame = JSON.parse(event.data);
if (frame.op === "subscribed") {
expected = frame.currentSequence + 1;
await loadSnapshotOverRest(); // 先拉快照,再消费增量
return;
}
if (frame.op === "message") {
if (expected !== undefined && frame.sequence !== expected) {
// 丢帧:单连接内 sequence 必须连续。重建快照,不要试图补洞。
await loadSnapshotOverRest();
}
expected = frame.sequence + 1;
apply(frame.type, frame.data);
}
};