# Real-Time Data (Polling) - Streaming API
URL: https://sockodds.com/docs/guides/realtime-streaming-api/

# Real-Time Data (Polling) - Streaming API [#real-time-data-polling---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 [#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" [#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 [#quick-start-example]

**JavaScript/Node.js** · **Python** · **Ruby**

```javascript
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
```

```python
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)
```

```ruby
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 [#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 [#connection-management]

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

## Troubleshooting [#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](https://sockodds.com/docs/faq/) · [Email](mailto:api@sockodds.com) · [Contact](https://sockodds.com/contact-us/)
