# Live Sports Odds API: How Fresh Live Prices Really Are

The age of a price on your screen is capture plus delivery plus your own poll interval. Here is how to measure each part.

A live sports odds API returns current bookmaker prices for games that are in progress or about to start, along with the game's status. How "live" those prices are depends on three things: how recently the provider saw the price at the bookmaker, how the prices reach you (polling or a push stream), and how often your own code asks. Add them together and you have the real age of a price on your screen. In Australia there is a fourth factor: online in-play betting is banned, so feeds of Australian books are mostly pre-match prices with live game state attached.

This guide works through each one, then shows how to measure freshness yourself instead of trusting a headline figure.

## What "live" means in an odds API

Providers use "live" for three different things, and it pays to know which one a page is talking about.

| Meaning | What it describes | What to check |
| --- | --- | --- |
| Live events | The game has started and the API says so | A status field such as scheduled, live or final |
| Live prices | How recently the provider saw the bookmaker's price | A timestamp on every price |
| Live delivery | How fast the provider gets data to you | Polling interval or push stream, and its cadence |

A provider can be excellent at one and ordinary at the others. A sub-second WebSocket does not help if the price inside it was captured a minute ago, and a fast capture does not help if you only poll every five minutes.

## How old is a live price?

The age of a price on your screen is roughly:

**time since the provider saw it + processing time + delivery or poll interval**

The first term is usually the biggest. A millisecond latency figure on a pricing page measures the last step only.

### Ask for the cadence, and for a timestamp

Some providers publish how often they read each kind of market, often shorter in-play than pre-match and shorter again for exchanges. Others give one headline figure. Either way, a cadence is a promise about the average case. It doesn't tell you how old the particular price in front of you is. A timestamp on every price does.

Key tiers matter too. Betfair's own [Application Keys documentation](https://betfair-developer-docs.atlassian.net/wiki/spaces/1smk3cen4v3lu3yomq5qye0ni/pages/2687105/Application+Keys) says the delay on its delayed key "can range from 1 to 180 seconds". The same exchange, read through a different key, can be three minutes behind.

SockOdds does not promise a capture interval. Instead, every price carries `observed_at`, the time SockOdds saw that price at the bookmaker, in UTC, and a quote not seen again within ten minutes is withheld from current responses rather than shown as current. Ten minutes is a ceiling on how old a served price can be, not a cadence: most prices will be far younger, and `observed_at` tells you exactly how much.

### A worked example

Take a price that changes at the bookmaker. On SockOdds:

1. The change reaches the API once SockOdds next sees that price, and `observed_at` records when that was.
2. If you poll every 2 minutes and just missed it, you see it up to 2 minutes after that.
3. A price older than ten minutes never reaches you as current, so the ceiling on paper is ten minutes plus your poll interval. The typical case is much shorter, and the timestamps tell you what it is.

That is fine for pre-match comparison, line movement tracking, alerts and models. It is not a low-latency in-play feed, and the SockOdds [live odds page](https://sockodds.com/use-cases/live-in-play-odds-api/) says so plainly. Arithmetic like this tells you the ceiling. The timestamps on each price tell you what actually happened, which is covered below.

## Polling, WebSocket or server-sent events

There are three common ways to get updates.

- **Polling:** your code calls the REST endpoint on a timer. Simple, cacheable and easy to debug. Your poll interval adds to the age of every price.
- **WebSocket:** MDN describes the [WebSocket API](https://developer.mozilla.org/en-US/docs/Web/API/WebSockets_API) as a way to open "a two-way interactive communication session" between the browser and a server. The provider pushes when something changes.
- **Server-sent events:** with [SSE](https://developer.mozilla.org/en-US/docs/Web/API/Server-sent_events) a server can "send new data to a web page at any time". One way only, which suits odds since the client rarely needs to talk back.

Push removes your poll interval from the sum. It cannot remove the time before the provider saw the price. Before paying for a stream, ask what cadence sits behind it.

SockOdds is explicit about this. Its [streaming guide](https://sockodds.com/docs/streaming/) describes a WebSocket on the Pro plan that checks prices every 15 seconds, sends a `snapshot` when something changed and a `heartbeat` when nothing did. The prices are the same ones the REST routes serve: the stream saves you polling, not time. A few details that shape a client:

- A heartbeat costs no objects, and a snapshot counts like the matching odds request.
- Each open stream still uses one request every 15 seconds of your per-minute limit.
- A key can hold 2 streams, and a connection lasts at most 3,600 seconds, so build reconnecting in from the start.
- Each snapshot is the full current state, so after a reconnect nothing needs replaying.

## Rate limit maths for polling

If you poll, your plan's request limit decides how many leagues you can watch and how often. The sum is:

**requests per minute = leagues x pages per league / poll interval in minutes**

On SockOdds, `GET /v1/odds` returns up to 100 events per page (25 by default), as the [pagination guide](https://sockodds.com/docs/pagination/) sets out, so most leagues fit in one page if you set `limit`. The [pricing page](https://sockodds.com/pricing/) lists 10 requests a minute on Developer, 50 on Rookie and 300 on Pro.

| Setup | Leagues | Poll every | Requests a minute |
| --- | --- | --- | --- |
| NFL and MLB, one page each | 2 | 2 minutes | 1 |
| 20 leagues, one page each | 20 | 2 minutes | 10 |
| 20 leagues, one page each | 20 | 30 seconds | 40 |

The third row is the trap. It may fit a per-minute limit, but the requests are not what runs out first. SockOdds counts usage in objects: each event is one, and every bookmaker price inside it is one more. Polling 20 leagues every 30 seconds asks for the same prices again and again, and each repeat is billed in full. If the prices have not changed, three of every four calls bought nothing. Poll every few minutes, narrow each request with `markets=` and `bookmakers=`, and ask for a start window with `starts_after` and `starts_before` instead of the whole league.

When you do hit the per-minute limit, the API answers `429` titled "Rate limit reached" with a `Retry-After` header in seconds. MDN describes [Retry-After](https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers/Retry-After) as how long "the user agent should wait before making a follow-up request". Read it and wait, rather than retrying in a tight loop. A `429` titled "Monthly allowance reached" is different: it will not clear until the next calendar month (UTC) or a plan change, so don't retry it.

## How to detect stale and suspended prices

A live odds feed is only useful if you can tell a current price from an old one. These are the fields to build on in SockOdds, from [Reading prices](https://sockodds.com/docs/odds/):

- **`observed_at` on each price:** when SockOdds saw that bookmaker's price. Subtract it from the current time to get the price's age.
- **The ten-minute rule:** a quote not seen again within ten minutes is left out of current responses. A missing price means there is no fresh quote, never that the price is zero.
- **Suspended and withdrawn prices:** a selection the bookmaker pulls drops out of current responses until it is offered again. Hide the cell or show a blank.
- **`status` on the event:** `scheduled`, `live`, `final`, `postponed` or `cancelled`. Filter on it in your own code to build a live board.

A few rules that keep a live display honest:

1. Show the age next to each price, such as "seen 2 min ago", not just the price.
2. Set your own freshness threshold, tighter than ten minutes if your product needs it, and never highlight a price older than it.
3. Drop `final`, `postponed` and `cancelled` events from anything that looks like a live board.
4. Treat delayed and cancelled events separately. Our post on [postponed, delayed and cancelled events](https://sockodds.com/blog/postponed-delayed-cancelled-events-odds-api/) covers how each one shows up.

This program reads one NFL game's head to head prices, works out each price's age from `observed_at`, and skips any older than a five-minute threshold of its own:

```javascript
const MAX_AGE_SECONDS = 5 * 60; // your own threshold, tighter than the API's ten minutes
const url = "https://api.sockodds.com/v1/events/ev_000ckk0ee4ze/odds?markets=winner";
const response = await fetch(url, { headers: { Authorization: `Bearer ${process.env.SOCKODDS_API_KEY}` } });
const { data: event } = await response.json();
console.log(`${event.name} (${event.status})`);

let kept = 0;
let skipped = 0;
for (const market of event.markets) {
  for (const o of market.outcomes) {
    for (const price of o.prices) {
      const age = Math.round((Date.now() - Date.parse(price.observed_at)) / 1000);
      if (age > MAX_AGE_SECONDS) {
        skipped += 1;
        continue;
      }
      kept += 1;
      console.log(`  ${price.bookmaker.padEnd(12)} ${o.name.padEnd(20)} ${price.decimal_odds.toFixed(2)}  seen ${Math.floor(age / 60)} min ago`);
    }
  }
}
console.log(`${kept} prices kept, ${skipped} older than ${MAX_AGE_SECONDS / 60} minutes skipped`);
```

```python
import json, os, urllib.request
from datetime import datetime, timezone

MAX_AGE_SECONDS = 5 * 60  # your own threshold, tighter than the API's ten minutes
url = "https://api.sockodds.com/v1/events/ev_000ckk0ee4ze/odds?markets=winner"
request = urllib.request.Request(url, headers={"Authorization": "Bearer " + os.environ["SOCKODDS_API_KEY"]})
with urllib.request.urlopen(request) as response:
    event = json.load(response)["data"]
print(f"{event['name']} ({event['status']})")

kept = 0
skipped = 0
now = datetime.now(timezone.utc)
for market in event["markets"]:
    for o in market["outcomes"]:
        for price in o["prices"]:
            seen = datetime.fromisoformat(price["observed_at"].replace("Z", "+00:00"))
            age = round((now - seen).total_seconds())
            if age > MAX_AGE_SECONDS:
                skipped += 1
                continue
            kept += 1
            print(f"  {price['bookmaker'].ljust(12)} {o['name'].ljust(20)} {price['decimal_odds']:.2f}  seen {age // 60} min ago")
print(f"{kept} prices kept, {skipped} older than {MAX_AGE_SECONDS // 60} minutes skipped")
```

Output (from a local copy of the API with invented test prices):

```text
Arizona Cardinals v Atlanta Falcons (scheduled)
  betblitz     Arizona Cardinals    1.81  seen 2 min ago
  chasebet     Arizona Cardinals    1.77  seen 2 min ago
  tab          Arizona Cardinals    1.77  seen 2 min ago
  betreal      Arizona Cardinals    1.74  seen 2 min ago
  blondebet    Arizona Cardinals    1.72  seen 2 min ago
  sportsbet    Arizona Cardinals    1.72  seen 2 min ago
  pointsbet    Arizona Cardinals    1.69  seen 2 min ago
  betchamps    Arizona Cardinals    1.65  seen 2 min ago
  betblitz     Atlanta Falcons      2.23  seen 2 min ago
  chasebet     Atlanta Falcons      2.18  seen 2 min ago
  tab          Atlanta Falcons      2.18  seen 2 min ago
  betreal      Atlanta Falcons      2.14  seen 2 min ago
  blondebet    Atlanta Falcons      2.12  seen 2 min ago
  sportsbet    Atlanta Falcons      2.12  seen 2 min ago
  pointsbet    Atlanta Falcons      2.08  seen 2 min ago
  betchamps    Atlanta Falcons      2.04  seen 2 min ago
16 prices kept, 0 older than 5 minutes skipped
```

The prices come from a local copy of the API with invented numbers.

### Measure freshness yourself

Every provider's numbers are claims until you check them. Log now minus `observed_at` for every price you receive, then look at the median and the 95th percentile by bookmaker and league. That gives you the real age distribution for your setup, including your own poll interval. If a provider does not put a timestamp on each price, you cannot run this check at all, and that is worth knowing before you buy.

## Live odds and Australian bookmakers

"In-play betting via online is banned in Australia under the federal Interactive Gambling Act", [The Straight](https://thestraight.com.au/the-push-to-open-up-live-in-play-sports-betting-in-australia/) reported in September 2025, with exceptions for phone betting and in-venue betting through Tabcorp.

For a developer this changes what "live" means on Australian books:

- Pre-match prices are the core of the data. SockOdds records pre-match prices first and says it is not a low-latency in-play feed, with a `status` on every event so a live product knows the state of the game.
- Line movement up to the start and closing prices are the useful live signals for models and content. On Pro, [recorded history](https://sockodds.com/docs/history/) returns each change and the opening and closing price.
- If your product depends on sub-second in-play prices, check whether the market you serve can legally use them before you pay for latency.

## What to ask a live odds API provider

1. How often do you read each market type, pre-match and in-play, and is it published?
2. Does every price carry its own timestamp?
3. How is a suspended price shown, and when does an old price stop being served?
4. What cadence sits behind the WebSocket or SSE stream, and what does a stream message cost?
5. What are the rate limits per plan, and how does the API signal a limit?
6. Which bookmakers and leagues are on the plan you would buy?

## Questions people ask

### How often does a live sports odds API update?

It depends on the provider and the market. Some publish a cadence per market type. SockOdds does not promise one: it puts `observed_at` on every price and withholds any quote it has not seen again within ten minutes.

### Is WebSocket faster than polling for odds?

Only if the provider sees prices faster than you would poll. Push removes your poll interval from the delay, but it cannot make a price fresher than the provider's last look. The SockOdds stream serves the same prices as the REST routes: it saves polling, not time.

### How do I tell if an odds price is stale?

Subtract each price's `observed_at` from the current time and compare it with your own threshold. In SockOdds a quote older than ten minutes is already left out of current responses, and a missing price means no fresh quote, never a price of zero.

### Can I get in-play odds from Australian bookmakers through an API?

Online in-play betting is banned in Australia under the Interactive Gambling Act, with exceptions for phone and in-venue betting. Australian book feeds are therefore mostly pre-match prices with live game state, which is how SockOdds describes its own feed.

### Is there a free live odds API?

SockOdds has a free Developer plan with 10 requests a minute on MLB, NFL, AFL, NRL and both Rugby League World Cups, from Sportsbet, TAB and Ladbrokes. Every price carries `observed_at` and every event its `status`, the same as on paid plans.

## Sources

- [Betfair Exchange API Documentation: Application Keys](https://betfair-developer-docs.atlassian.net/wiki/spaces/1smk3cen4v3lu3yomq5qye0ni/pages/2687105/Application+Keys)
- [MDN: The WebSocket API](https://developer.mozilla.org/en-US/docs/Web/API/WebSockets_API)
- [MDN: Server-sent events](https://developer.mozilla.org/en-US/docs/Web/API/Server-sent_events)
- [MDN: Retry-After header](https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers/Retry-After)
- [The Straight: The push to open up live in-play sports betting in Australia (10 September 2025)](https://thestraight.com.au/the-push-to-open-up-live-in-play-sports-betting-in-australia/)

## Next

- [Check the timestamps yourself with a free Developer key](https://sockodds.com/signup/): the free plan includes NFL and MLB, so try it on a game that is about to start.
- [Streaming](https://sockodds.com/docs/streaming/)
- [Odds API rate limits](https://sockodds.com/blog/odds-api-rate-limits/)
- [Odds format conversion](https://sockodds.com/blog/odds-format-conversion-developers/)
- [How providers compare](https://sockodds.com/compare/)

This guide is for developers building data products. It is not betting advice and does not suggest any bet will win. 18+ only. Please gamble responsibly.

SockOdds provides data only. It does not offer tips or predictions on any game.
