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
- Seed: fetch the events you care about into a map keyed by
eventID. - Poll: every 2–5 minutes fetch them again (one league at a time, or by
eventIDs). - Diff: compare
byBookmaker[*].decimal,availableandstatuswith what you have; act on changes.
Available "feeds"
| Feed | Equivalent query | Required parameters |
|---|---|---|
events:live | /events?live=true | None |
events:upcoming | /events?leagueID=…&oddsAvailable=true | leagueID |
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 minutesimport 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
endUpdate 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=trueor query byeventIDs. - Prices look frozen — check
info.stale.