Skip to main content
Live Odds API

Live Odds API: Real-Time Odds over WebSocket

A live odds API sends your code a bookmaker's price the moment it changes. Odds-API.io streams pre-match and in-play odds from 450+ bookmakers across 34 sports over one WebSocket connection, with live scores, match status and the match clock on the same stream.

WebSocket streaming is an add-on for any paid plan. REST is included in every plan.

Bookmakers
450+
Sports
34
Latency
<150ms
Replay window
24h

Why WebSocket Instead of Polling

Polling asks for odds on a timer and gets mostly unchanged data back. A WebSocket stays open and only carries the prices that moved, so you see a change sooner and move far less data.

Why WebSocket Instead of Polling
FeatureREST pollingWebSocket
How changes reach youYou ask again on a timerPushed as soon as a price changes
LatencyYour poll interval plus the request<150ms
ConnectionsA new HTTP request for every pollOne connection that stays open
Withdrawn marketsRe-read the event to notice themExplicit deleted and no_markets messages
Best forScripts, backfills and quick lookupsLive betting apps, in-play models and arbitrage scanners

Connect in a Few Lines

Open wss://api.odds-api.io/v3/ws with your API key and the markets you want. The first message is a welcome that confirms your filters, then odds arrive as they change.

Node.js
// npm i ws
import WebSocket from 'ws'

const API = 'https://api.odds-api.io/v3'
const apiKey = process.env.ODDS_API_KEY
const markets = 'ML,Spread,Totals'
const eventIds = '63017989,63017990' // up to 10, for example from GET /v3/events/live
const bookmakers = 'Bet365,Unibet' // the bookmakers selected on your account
const board = new Map()
let lastSeq = 0
let needsRebuild = false

function connect() {
  const params = new URLSearchParams({ apiKey, markets, eventIds, channels: 'odds,scores,status' })
  if (lastSeq) params.set('lastSeq', String(lastSeq))

  const ws = new WebSocket(`wss://api.odds-api.io/v3/ws?${params}`)

  ws.on('message', (raw) => {
    const msg = JSON.parse(raw)
    if (msg.type === 'resync_required') {
      needsRebuild = true
      return ws.close()
    }
    if (msg.seq > lastSeq) lastSeq = msg.seq
    const key = `${msg.id}:${msg.bookie}`

    if (msg.type === 'created' || msg.type === 'updated') board.set(key, msg.markets)
    if (msg.type === 'deleted' || msg.type === 'no_markets') board.delete(key)
    if (msg.type === 'score') console.log(msg.id, msg.scores.home, msg.scores.away)
  })

  ws.on('error', (err) => console.error('WebSocket error:', err.message || err.code))

  ws.on('close', async () => {
    if (needsRebuild) lastSeq = await rebuildFromRest()
    setTimeout(connect, 2000)
  })
}

// Replay was not possible: reload the subscription from REST and resume from its seq
async function rebuildFromRest() {
  for (let attempt = 1; ; attempt++) {
    try {
      const params = new URLSearchParams({ apiKey, eventIds, bookmakers, markets, includeSeq: 'true' })
      const res = await fetch(`${API}/odds/multi?${params}`)
      const seq = Number(res.headers.get('X-OddsAPI-Seq'))
      if (!res.ok || !(seq > 0)) throw new Error(`snapshot not usable: ${res.status}`)
      const events = await res.json()
      board.clear()
      for (const event of events) {
        for (const [bookie, odds] of Object.entries(event.bookmakers)) board.set(`${event.id}:${bookie}`, odds)
      }
      needsRebuild = false
      return seq
    } catch (err) {
      console.error(`${err.message}, retry ${attempt}`)
      await new Promise((resolve) => setTimeout(resolve, Math.min(30000, 1000 * 2 ** attempt)))
    }
  }
}

connect()
Python
# pip install websockets
import asyncio
import json
import os
from urllib.parse import urlencode

import websockets

params = urlencode({
    "apiKey": os.environ["ODDS_API_KEY"],
    "markets": "ML,Spread,Totals",
    "leagues": "england-premier-league",
    "status": "live",
})

async def main():
    async with websockets.connect(f"wss://api.odds-api.io/v3/ws?{params}") as ws:
        async for message in ws:
            msg = json.loads(message)
            if msg["type"] == "updated":
                for market in msg["markets"]:
                    print(msg["id"], msg["bookie"], market["name"], market["odds"][0])

asyncio.run(main())

Compression is required

Your client must offer permessage-deflate when it connects. It cuts bandwidth 3 to 5 times, and a connection without it is refused with HTTP 426. Most libraries handle it for you:

Compression is required
PlatformLibraryCompression
Node.jswsOn by default
Browsernative WebSocketOn by default
PythonwebsocketsOn by default
Pythonwebsocket-clientNot supported, use websockets
PHPamphp/websocket-clientOn by default (needs zlib)
PHPtextalk/websocketNot supported, use amphp/websocket-client
Gogorilla/websocketSet EnableCompression = true
.NETClientWebSocketSet DangerousDeflateOptions (.NET 6+)

Message Types

Every message is a JSON object with a type. Odds messages use the same format as the /v3/odds endpoint, so one parser covers both.

created
A bookmaker added an event you are subscribed to
updated
Odds changed. The message carries the full current set of your markets for that event and bookmaker
deleted
The event was removed, or its bookmaker went down
no_markets
The event is still listed but none of your markets are open
score
The live score changed (scores channel)
status
The event went pending, live, settled or cancelled, with the current score (status channel)
clock
A match clock snapshot about every 30 seconds for each live event with a running clock

Replace, never merge

A suspended market is left out of the next updated message rather than flagged. Overwrite what you hold for that event and bookmaker with each message, and a pulled price disappears from your app on its own.

updated
{
  "type": "updated",
  "seq": 482917,
  "timestamp": 1723992773,
  "id": "63017989",
  "bookie": "Bet365",
  "markets": [
    {
      "name": "ML",
      "updatedAt": "2026-11-08T15:42:10Z",
      "odds": [{ "home": "1.85", "draw": "3.25", "away": "2.10" }]
    },
    {
      "name": "Totals",
      "updatedAt": "2026-11-08T15:42:10Z",
      "odds": [{ "hdp": 2.5, "over": "1.85", "under": "2.10" }]
    }
  ]
}

Channels and Filters

Choose the streams you want with channels, then narrow them with query parameters so you only receive the events you show. You cannot combine leagues and eventIds.

Channels

odds
The default. created, updated, deleted and no_markets for the bookmakers selected on your account
scores
score messages on every score change, plus clock snapshots
status
status messages for pending, live, settled and cancelled, plus clock snapshots

Filters

markets
Required for the odds channel. Up to 40 market names, such as ML,Spread,Totals
sport
Up to 10 sports, such as football,basketball
leagues
Up to 20 league slugs, such as england-premier-league
eventIds
Up to 50 event IDs
status
live for in-play only, prematch for pre-match only

channels is an allowlist. Send channels=odds,scores,status to keep odds next to scores; channels=scores,status alone stops the odds.

Reconnect Without Gaps

Networks drop. Every odds message carries a seq number, and the feed uses it to catch you up after a reconnect instead of making you start over.

  1. 1

    Keep the last seq

    Store the highest seq you have received. Seqs only go up on a connection, but they are shared by all subscribers, so a jump is not a gap.

  2. 2

    Reconnect with lastSeq

    Add lastSeq to the connection URL. You get the latest state of every event and bookmaker that changed while you were away, then the live stream continues.

  3. 3

    Know when you are live

    Add replayComplete=true to get one replay_complete message when the catch-up is done. Replay data is kept for up to 24 hours.

  4. 4

    Rebuild if asked

    If a clean replay is not possible you get resync_required. Fetch a snapshot from /v3/odds with includeSeq=true and reconnect with the seq from the X-OddsAPI-Seq header.

Each API key can hold two connections at once, for example a primary and a standby. More are available on request.

Live Scores and Match Clock

In-play odds make more sense next to the score. Subscribe to the scores and status channels on the same connection, or read the current state over REST.

GET /v3/events/live
Every event in play right now, with the score and a clock object. Filter by sport
clock
minute, playedSeconds, period, running and statusDetail, plus injury time in football and the server in tennis
Period scores
Halves, quarters, sets, overtime and penalties under one set of keys for every sport
Ticking clock
Count forward from playedSeconds while running is true, and reset to the server value on every snapshot

REST Fallback: /odds and /odds/updated

Every plan includes the REST API, and it serves the same live odds. Use it for snapshots, or poll it where a socket does not fit.

GET /v3/odds
A full snapshot of one event from the bookmakers you choose. /v3/odds/multi takes up to 10 events in one request
GET /v3/odds/updated
A true delta: only the markets that changed since since, which can be at most 90 seconds old. Pass the X-Next-Since response header as since on the next call to continue with no gap
includeSeq=true
Adds the current stream position as X-OddsAPI-Seq, so you can hand off from a REST snapshot to the WebSocket without missing an update

A delta cannot tell you a market was withdrawn, so a polling client should re-read events with /v3/odds now and then. The WebSocket sends deleted and no_markets instead.

What the Live Odds Feed Covers

Bookmakers
450+, including Bet365, DraftKings, FanDuel, BetMGM, Betfair Exchange and regional books in Europe, Asia, Australia and Latin America
Sports
34, including football, basketball, tennis, American football, baseball, ice hockey, cricket and esports
In-play
Live odds from every bookmaker that prices in play, refreshed about every second
Markets
Moneyline, spreads, totals, Asian handicap, corners, cards and player props, depending on the bookmaker
Latency
<150ms for updates pushed over the WebSocket
Format
The same JSON as /v3/odds, over WebSocket and REST

Plans and WebSocket Access

Every paid plan includes the REST API with live odds. WebSocket streaming is an add-on you can add to any plan.

  • The add-on costs the plan price again, so 2× the plan price in total, monthly or yearly
  • The stream carries every bookmaker selected on your account, so the plan decides how many books you get
  • Scores, status and the match clock come on the same connection
  • Cancel any month
Compare plans →

Live Odds API FAQ

What is a live odds API?

A live odds API gives your code the current betting odds for events in play, as JSON, and keeps them up to date as bookmakers move their prices. Odds-API.io streams live and pre-match odds from 450+ bookmakers over a WebSocket, and serves the same odds over REST.

How fast are the live odds?

WebSocket updates arrive with <150ms latency, and in-play odds refresh about every second. Polling REST adds your poll interval on top, which is why we recommend WebSocket for in-play apps.

What happens if my WebSocket connection drops?

Reconnect with the lastSeq parameter set to the last seq you received. The server replays the latest state of everything that changed while you were away, for gaps of up to 24 hours, then continues live. If a clean replay is not possible it sends resync_required, and you rebuild from a REST snapshot.

Which WebSocket clients can I use?

Any client that supports permessage-deflate compression, which is required. Node.js ws, browser WebSocket and Python websockets do it by default. Python websocket-client and PHP textalk/websocket cannot, and are refused with HTTP 426. In Go, set EnableCompression on gorilla/websocket.

Does the feed include live scores and the match clock?

Yes. Add channels=odds,scores,status to the connection URL to get score changes, status changes and a match clock snapshot about every 30 seconds next to the odds. GET /v3/events/live returns the same scores and clock over REST.

How do I get WebSocket access?

Pick a paid plan and add WebSocket streaming. It costs the plan price again and streams every bookmaker selected on your account, with two connections per API key. The REST API, including live odds and /v3/odds/updated, is part of every plan.

Stream Live Odds Today

Pick a plan, add WebSocket streaming and open one connection for live odds from the 450+ bookmakers on the feed.

Cancel anytime · Same JSON over REST and WebSocket