Real-Time Data — Streaming API

No WebSocketSockOdds does not stream. The source refreshes every ~2 minutes, and /v2/stream/events answers 501 Not Implemented so a client written for SportsGameOdds fails loudly instead of waiting on a socket that never opens. This page shows the polling-and-diff pattern that replaces it, in the same shape the SGO streaming examples use, so the rest of your code stays the same.

How it works

  1. Seed: fetch the events you care about into a map keyed by eventID.
  2. Poll: every 2–5 minutes fetch them again (one league at a time, or by eventIDs).
  3. Diff: compare byBookmaker[*].decimal, available and status with what you have; act on changes.

Available "feeds"

FeedEquivalent queryRequired parameters
events:live/events?live=trueNone
events:upcoming/events?leagueID=…&oddsAvailable=trueleagueID
events:byid/events?eventIDs=…eventID

Quick start example

const API_BASE_URL = "https://api.sockodds.com/v2";
const API_KEY = process.env.SOCKODDS_KEY;
const EVENTS = new Map();

async function fetchEvents(params) {
  const url = new URL(API_BASE_URL + "/events"); url.search = new URLSearchParams(params);
  return (await (await fetch(url, { headers: { "x-api-key": API_KEY } })).json()).data;
}

function diff(prev, next) {
  const changes = [];
  for (const [oddID, odd] of Object.entries(next.odds)) for (const [book, q] of Object.entries(odd.byBookmaker)) {
    const was = prev?.odds?.[oddID]?.byBookmaker?.[book];
    if (!was || was.decimal !== q.decimal || was.available !== q.available) changes.push({ oddID, book, from: was?.decimal, to: q.decimal, available: q.available });
  }
  return changes;
}

async function tick() {
  for (const e of await fetchEvents({ leagueID: "AFL", oddsAvailable: "true", limit: "100" })) {
    const changes = diff(EVENTS.get(e.eventID), e);
    if (changes.length) console.log(e.eventID, changes);
    EVENTS.set(e.eventID, e);
  }
}
await tick(); setInterval(tick, 150_000); // 2.5 minutes
import os, time, requests
API = "https://api.sockodds.com/v2"; KEY = os.environ["SOCKODDS_KEY"]; EVENTS = {}

def fetch_events(**params):
    return requests.get(f"{API}/events", params=params, headers={"x-api-key": KEY}).json()["data"]

def diff(prev, nxt):
    out = []
    for odd_id, odd in nxt["odds"].items():
        for book, q in odd["byBookmaker"].items():
            was = (prev or {}).get("odds", {}).get(odd_id, {}).get("byBookmaker", {}).get(book)
            if not was or was["decimal"] != q["decimal"] or was["available"] != q["available"]:
                out.append((odd_id, book, was and was["decimal"], q["decimal"], q["available"]))
    return out

while True:
    for e in fetch_events(leagueID="AFL", oddsAvailable="true", limit=100):
        changes = diff(EVENTS.get(e["eventID"]), e)
        if changes: print(e["eventID"], changes)
        EVENTS[e["eventID"]] = e
    time.sleep(150)
require "net/http"; require "json"
API = "https://api.sockodds.com/v2"; KEY = ENV["SOCKODDS_KEY"]; events = {}

def fetch_events(params)
  uri = URI("#{API}/events"); uri.query = URI.encode_www_form(params)
  req = Net::HTTP::Get.new(uri); req["x-api-key"] = KEY
  JSON.parse(Net::HTTP.start(uri.hostname, uri.port, use_ssl: true) { |h| h.request(req) }.body)["data"]
end

loop do
  fetch_events(leagueID: "AFL", oddsAvailable: true, limit: 100).each do |e|
    prev = events[e["eventID"]]
    e["odds"].each do |odd_id, odd|
      odd["byBookmaker"].each do |book, q|
        was = prev&.dig("odds", odd_id, "byBookmaker", book)
        puts "#{e["eventID"]} #{odd_id} #{book} #{was&.dig("decimal")} -> #{q["decimal"]}" if was.nil? || was["decimal"] != q["decimal"]
      end
    end
    events[e["eventID"]] = e
  end
  sleep 150
end

Update detection

Because every bookmaker entry carries lastUpdatedAt, you can also skip the diff and simply act on entries whose timestamp is newer than your last poll.

Connection management

There is nothing to keep open. Handle 429 by honouring Retry-After, and 5xx with one retry after a short delay.

Troubleshooting

  • No changes detected — the source refreshes every ~2 minutes; polling faster shows nothing new.
  • An event vanished from the listing — it kicked off more than 24 hours ago; add includeFinished=true or query by eventIDs.
  • Prices look frozen — check info.stale.
Need help?FAQ · Email · Contact