Track prices over the odds stream

Open the odds stream, read a snapshot and print the best price on each side.

Pro plan

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 playup

Keeping 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= and limit= (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 with 1009: 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.

Need help?FAQ · Email support

Search across the SockOdds documentation and catalogue.