WebSocket APIHow-tos
Stream an order book
Subscribe to a market's order book, and show its Yes and No sides as they change.
The predictOrderbook/{marketId} topic sends a market's whole order book each time it changes. The latest book usually arrives right after you subscribe. When it doesn't, fetch it from GET /v1/markets/{id}/orderbook.
Subscribe
Send a subscribe request with the market's ID in the topic:
{ "method": "subscribe", "requestId": 1, "params": ["predictOrderbook/123"] }Each update is the whole book, so replace what you have instead of merging:
{
"type": "M",
"topic": "predictOrderbook/123",
"data": {
"marketId": 123,
"updateTimestampMs": 1736696400000,
"asks": [[0.62, 1500], [0.63, 800]],
"bids": [[0.61, 2000], [0.6, 1200]]
}
}asks and bids are [price, shares] levels, best price first. The order book topic lists every field, including the settlements that are still pending.
The No side
The book is priced in Yes. The No side holds the same orders seen from the other side: a Yes bid at 0.61 is a No ask at 0.39. So the No book is the Yes book with its sides swapped, and each price replaced by its complement at the market's decimalPrecision. Prices and order book explains why.
The whole example
This reads the market's precision from the REST API, subscribes to its order book, and prints both books on every update. It also echoes the server's heartbeats, which keeps the connection open.
// Show a market's order book for both outcomes, Yes and No, as it changes.
// The API sends the Yes book. The No book holds the same orders seen from the
// other side: a Yes bid at 0.40 is a No ask at 0.60. So the sides swap, and each
// price becomes its complement.
// Runs in browsers and in Node.js 22 or later, which have WebSocket built in.
// Get an API key from the developer console: https://developers.predict.fun
const apiKey = "YOUR_API_KEY";
// a market ID, like the ones the Market data example prints.
const marketId = "MARKET_ID";
type Level = [price: number, shares: number];
// Prices have the market's decimal precision, for example 2 for steps of 0.01.
const response = await fetch(`https://api.predict.fun/v1/markets/${marketId}`, {
headers: { "x-api-key": apiKey },
});
if (!response.ok) throw new Error(`Predict API error: ${response.status}`);
const { decimalPrecision } = (await response.json()).data;
// The No price of a Yes price. Rounding at the market's precision makes the two
// add up to exactly 1.
function complement(price: number): number {
const factor = 10 ** decimalPrecision;
return (factor - Math.round(price * factor)) / factor;
}
function cents(price: number): string {
return `${(price * 100).toFixed(Math.max(decimalPrecision - 2, 0))}¢`;
}
// Prints the 5 best levels of each side. The asks print highest first, so the
// best ask and the best bid meet at the spread.
function printBook(outcome: string, asks: Level[], bids: Level[]) {
const row = (side: string, [price, shares]: Level) =>
` ${side} ${cents(price).padStart(6)} ${shares.toFixed(2).padStart(10)}`;
console.log(`${outcome} price shares`);
for (const level of asks.slice(0, 5).reverse()) {
console.log(row("ask", level));
}
if (asks.length > 0 && bids.length > 0) {
console.log(` spread ${cents(asks[0][0] - bids[0][0])}`);
}
for (const level of bids.slice(0, 5)) {
console.log(row("bid", level));
}
}
// Browsers can't set headers on a WebSocket, so the key goes in the URL.
const ws = new WebSocket(`wss://ws.predict.fun/ws?apiKey=${apiKey}`);
const topic = `predictOrderbook/${marketId}`;
ws.addEventListener("open", () => {
ws.send(
JSON.stringify({ method: "subscribe", requestId: 1, params: [topic] }),
);
});
ws.addEventListener("message", (event) => {
const message = JSON.parse(event.data);
if (message.type === "R" && !message.success) {
console.error(`Subscribe failed: ${message.error.message}`);
} else if (message.topic === "heartbeat") {
// Echo each heartbeat, or the server closes the connection.
ws.send(JSON.stringify({ method: "heartbeat", data: message.data }));
} else if (message.topic === topic) {
const { asks, bids }: { asks: Level[]; bids: Level[] } = message.data;
const toNo = ([price, shares]: Level): Level => [complement(price), shares];
console.clear();
printBook("Yes", asks, bids);
// No asks come from the Yes bids, and No bids from the Yes asks.
printBook("No ", bids.map(toNo), asks.map(toNo));
}
});
ws.addEventListener("close", (event) => {
console.log(`Connection closed (${event.code})`);
});To keep the stream running through disconnects, add the reconnection from Stay connected.