Skip to content
Get an API key

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 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
}),
);
});
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].

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.
  • subscribeAll needs a venue-wide firehose upstream; venues without one answer NOT_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.

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.

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.

  • 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