Stream a live order book
Subscribe over WebSocket, handle full-state frames, and survive the non-fatal errors.
REST gives you a book at a moment. The WebSocket gives you the book as it changes, and it does so with one upstream venue subscription shared across every client watching that market — so scaling consumers does not burn a venue's rate limits.
The protocol reference is Streaming. This page is the working loop.
Connect and subscribe
Connect to /v1/stream on the WebSocket origin shown in your dashboard, authenticating with the
same pk_live_… key as REST.
const ws = new WebSocket(`${WS_ORIGIN}/v1/stream`, {
headers: { Authorization: `Bearer ${process.env.PREDICTEFY_API_KEY}` },
});
ws.on('open', () => {
ws.send(
JSON.stringify({
op: 'subscribe',
channel: 'orderbook',
venue: 'polymarket',
marketId: OUTCOME_ID, // venue-native — see below
}),
);
});
Handle the frames
ws.on('message', (raw) => {
const frame = JSON.parse(raw);
switch (frame.type) {
case 'subscribed':
case 'unsubscribed':
return;
// Both carry the FULL book, never deltas. Replace state; do not merge.
case 'snapshot':
case 'update':
book.set(frame.marketId, frame.data);
return;
case 'trade':
onTrade(frame.venue, frame.marketId, frame.data);
return;
case 'error':
// Non-fatal. The socket stays open — do NOT reconnect on these.
console.warn(`${frame.code}: ${frame.message}`, frame.venue, frame.marketId);
return;
}
});
Two properties decide the shape of this loop:
snapshot and update both carry the full book. snapshot is the first frame after
subscribing and after backpressure coalescing; update marks live ticks. Neither is a delta, so
replace your local state rather than merging into it. Merging works until the first coalesced
snapshot, then drifts.
Protocol errors are non-fatal. BAD_MESSAGE, NOT_SUPPORTED, NOT_SUBSCRIBED and
SUBSCRIPTION_LIMIT arrive as type: "error" frames and the socket stays open. Treating
them as disconnects produces a reconnect loop against a working connection.
Book frames carry bids and asks as { price, size }, with prices as probabilities in
[0, 1].
What you cannot subscribe to
NOT_SUPPORTED is a real answer about the world, not a failure:
- Venues without a public stream answer it rather than serving a synthetic one.
- Trades stream only where the venue has a native fills channel. Order-book support does not imply trade support.
subscribeAllneeds a venue-wide firehose upstream; venues without one answerNOT_SUPPORTED.
Do not fall back to another venue when you get this. The honest answer is that this venue does not offer that stream — see Capability-honest data.
Subscription limits
Active logical subscriptions are capped by plan: 2 on Free, 20 on Builder, 100 on Pro, 500 on Scale. A lower service safety cap can also apply.
Exceeding either returns a non-fatal SUBSCRIPTION_LIMIT — the socket survives and the
subscription simply does not exist. Track your own count; a subscribe that "succeeded" because
the socket stayed open is not the same as a subscribe that took effect. Wait for the
subscribed ack.
Falling back to REST
For a point-in-time book, or for any venue without a stream, fetchOrderBook takes an
outcomeId and a limit of up to 1000 depth levels. It also reads the archive: at for the
nearest stored snapshot to a time, since/until for a range, and side to return only bids
or only asks.
Related
- Streaming — the full protocol: auth, close codes, feed tickers
- Identifiers — venue-native versus unified ids
- Venue coverage — which venues have a real book at all