Track prices over the odds stream
Open the odds stream, read a snapshot and print the best price on each side.
The odds stream is part of the Pro plan. Other plans get a 403 problem. See pricing.
What it does
The program opens a WebSocket to the odds stream for one league's winner markets. The stream sends a snapshot straight away, then checks again every 15 seconds and sends a new snapshot only when something changed. When nothing changed, it sends a heartbeat instead.
For each game in the first snapshot, the program prints the best current price on each side and the key of the bookmaker offering it, then closes the connection. Prices arrive grouped by market, and each outcome lists every bookmaker's price best first, so the best price is the first one. To keep tracking, leave the connection open and handle every snapshot the same way.
The stream is for pre-match prices refreshed every 15 seconds. It is not a low-latency in-play feed.
The program
Node 22 has a built-in WebSocket client that can send the Authorization header, so no package is needed. Python's standard library has no WebSocket client, so there is no Python version of this example.
const url = "wss://api.sockodds.com/v1/stream/odds?league=rlwc&markets=winner&limit=5";
const socket = new WebSocket(url, {
headers: { Authorization: `Bearer ${process.env.SOCKODDS_API_KEY}` },
});
function printBest(event) {
// Each outcome's prices are sorted best first, so prices[0] is the best price on that side.
const market = event.markets.find((m) => m.key === "winner" && m.period === "full");
const outcomes = new Map((market?.outcomes ?? []).map((outcome) => [outcome.side, outcome]));
console.log(event.name);
for (const participant of event.participants) {
const outcome = outcomes.get(participant.role);
const best = outcome?.prices[0];
console.log(best ? ` ${outcome.name}: ${best.decimal_odds.toFixed(2)} at ${best.bookmaker}` : ` ${participant.identity.name}: no current price`);
}
}
socket.addEventListener("message", (message) => {
const frame = JSON.parse(message.data);
if (frame.type === "heartbeat") return; // nothing changed since the last snapshot
if (frame.type === "error") throw new Error(frame.problem.detail);
// A league snapshot is the odds route's body: a list of events in data.
for (const event of frame.data) printBest(event);
socket.close(); // remove this line to keep receiving snapshots
});
socket.addEventListener("close", (event) => {
if (event.code !== 1000 && event.code !== 1005) console.error(`stream closed: ${event.code}`);
});Output from a local copy of the API with invented test prices
Australia v New Zealand
New Zealand: 2.23 at playup
Australia: 1.81 at playupKeeping a stream running
- Snapshots, not changes. Each snapshot is the full current body for your filters, the same as the odds route returns. Replace what you hold with it rather than merging.
- Objects. A snapshot counts towards your allowance like an odds request. A heartbeat is free. Every refresh still counts towards your per minute request limit, so narrow with
markets=,bookmakers=andlimit=(up to 20 events for a league). - Lifetime. A stream lasts at most 3,600 seconds. When it closes with code
1000, open a new one. You can hold up to 2 streams per key. - Read fast. If your client falls behind, the server closes the stream with
1013. If a snapshot is too large it closes with1009: narrow the filters. - Read only. Any message you send closes the stream.
- Freshness. A quote older than ten minutes is withheld, as on every current route, and each price still carries
observed_at.
See streaming for every close code and limit, and the API reference for the parameters. The output above comes from a local copy of the API with invented test prices.