Description

This endpoint provides an alternative to polling the /fixtures/odds endpoint and allows you to passively listen for updates. This endpoint leverages the SSE responses of the http protocol.

Specific Parameter Information

include_fixture_updates

This will return any status or start date changes for fixtures that meet the criteria of the passing in data.

Best Practices

While our endpoint supports passing multiple leagues and game ids, we recommend making a separate connection for each league you are trying to subscribe to.

Example Requests

Python

Requirements

import requests
from requests.exceptions import ChunkedEncodingError
import json
import sseclient # pip install sseclient-py

while True:
    try:
        r = requests.get(
            "https://api.opticodds.com/api/v3/stream/basketball/odds",
            params={
                "key": "1234-5678-124",
                "sportsbook": ["DraftKings", "FanDuel", "Hard Rock"],
                "market": ["Moneyline"],
                "league": ["NCAAB"],
                # "is_main": True,
            },
            stream=True,
        )
        client = sseclient.SSEClient(r)
        for event in client.events():
            if event.event == "odds":
                data = json.loads(event.data)
                print("odds data", ":", data)
            elif event.event == "locked-odds":
                data = json.loads(event.data)
                print("locked-odds data", ":", data)
            else:
                print(event.event, ":", event.data)
    except ChunkedEncodingError as ex:
        print("Disconnected, attempting to reconnect...")
    except Exception as e:
        print("Error:", r.status_code, r.text)
        break

Node.js

const EventSource = require("eventsource");  // npm install eventsource

const url = "https://api.opticodds.com/api/v3/stream/basketball/odds";
const params = {
  key: "1234-5678-124",
  sportsbook: ["DraftKings", "FanDuel", "Hard Rock"],
  market: ["Moneyline"],
  league: ["NCAAB"],
};

function connectToStream() {
  // Construct the query string with repeated parameters
  const queryString = new URLSearchParams();
  queryString.append("key", params.key);
  params.sportsbook.forEach((sportsbook) =>
    queryString.append("sportsbook", sportsbook)
  );
  params.market.forEach((market) => queryString.append("market", market));
  params.league.forEach((league) => queryString.append("league", league));

  console.log(`${url}?${queryString.toString()}`);

  const eventSource = new EventSource(`${url}?${queryString.toString()}`);

  eventSource.onmessage = function (event) {
    try {
      const data = JSON.parse(event.data);
      console.log("message data:", data);
    } catch (e) {
      console.log("Error parsing message data:", e);
    }
  };

  eventSource.addEventListener("odds", function (event) {
    const data = JSON.parse(event.data);
    console.log("odds data:", data);
  });

  eventSource.addEventListener("locked-odds", function (event) {
    const data = JSON.parse(event.data);
    console.log("locked-odds data:", data);
  });

  eventSource.onerror = function (event) {
    console.error("EventSource failed:", event);
    eventSource.close();
    setTimeout(connectToStream, 1000); // Attempt to reconnect after 1 second
  };
}

connectToStream()

Example Events

Connected Event

event: connected
retry: 5000
data: ok go

Ping Event

event: ping
retry: 5000
data: 2024-08-28T18:57:49Z

Odds Event

event: odds
id: 1724871465048-0
retry: 5000
data: {"data":[{"deep_link":null,"fixture_id":"32C781DB02F8","game_id":"39920-20584-2024-35","grouping_key":"default","id":"39920-20584-2024-35:draftkings:2nd_set_moneyline:francisco_comesana","is_live":true,"is_main":true,"league":"ATP","market":"2nd Set Moneyline","name":"Francisco Comesana","player_id":"","points":null,"price":290,"selection":"Francisco Comesana","selection_line":"","selection_points":null,"sport":"tennis","sportsbook":"DraftKings","team_id":"5DA941291C12","timestamp":1724871465.0438228}],"entry_id":"1724871465048-0","type":"odds"}

Locked Odds Event

event: locked-odds
id: 1724871474060-0
retry: 5000
data: {"data":[{"deep_link":null,"fixture_id":"46ED78C63302","game_id":"23675-80541-2024-35","grouping_key":"andrey_rublev:23.5","id":"23675-80541-2024-35:draftkings:player_games_won:andrey_rublev_under_23_5","is_live":true,"is_main":true,"league":"ATP","market":"Player Games Won","name":"Andrey Rublev Under 23.5","player_id":"8B1C0A9FED57","points":23.5,"price":-165,"selection":"Andrey Rublev","selection_line":"under","selection_points":23.5,"sport":"tennis","sportsbook":"DraftKings","team_id":"8B1C0A9FED57","timestamp":1724871474.0576713},{"deep_link":null,"fixture_id":"46ED78C63302","game_id":"23675-80541-2024-35","grouping_key":"andrey_rublev:23.5","id":"23675-80541-2024-35:draftkings:player_games_won:andrey_rublev_over_23_5","is_live":true,"is_main":true,"league":"ATP","market":"Player Games Won","name":"Andrey Rublev Over 23.5","player_id":"8B1C0A9FED57","points":23.5,"price":110,"selection":"Andrey Rublev","selection_line":"over","selection_points":23.5,"sport":"tennis","sportsbook":"DraftKings","team_id":"8B1C0A9FED57","timestamp":1724871474.0576713}],"entry_id":"1724871474060-0","type":"locked-odds"}

Fixture Status Updates (Game Time Change)

This will only be sent if you have include_fixture_updates=true

event: fixture-status
id: 1724871720243-0
retry: 5000
data: {"data":{"fixture":{"away_team_display":"Lorenzo Musetti","game_id":"88067-26534-2024-35","home_team_display":"Miomir Kecmanovic","id":"FB0D30DCDD6C","start_date":"2024-08-28T20:30:00+00:00"},"fixture_id":"FB0D30DCDD6C","game_id":"88067-26534-2024-35","league":"ATP","new_start_date":"2024-08-28T20:30:00+00:00","new_status":null,"old_start_date":"2024-08-28T20:20:00+00:00","old_status":null,"sport":"tennis","timestamp":1724871720.2406485},"entry_id":"1724871720243-0"}

Fixture Status Updates (Status Change)

This will only be sent if you have include_fixture_updates=true

event: fixture-status
id: 1724871720243-0
retry: 5000
data: { "data": { "fixture": { "away_team_display": "Saisai Zheng", "game_id": "30697-79652-2024-35", "home_team_display": "Jessika Ponchet", "id": "E145B23767E0", "start_date": "2024-08-27T15:00:00+00:00" }, "fixture_id": "E145B23767E0", "game_id": "30697-79652-2024-35", "league": "WTA", "new_start_date": null, "new_status": "cancelled", "old_start_date": null, "old_status": "unplayed", "sport": "tennis", "timestamp": 1724761586.2172334 }, "entry_id": "1724761586216-0" }

Notes

Choosing a good key to store the data.

If you are tracking multiple sportsbooks for a Game / Market combination. We would recommend making a key of (Fixture ID + Sportsbook + Market + Name).

If you are tracking a single sportsbook for a Game / Market combination. We would recommend making a key of (Fixture Id + Market + Name).

Common Cases

Case 1: Sportsbook moves main line and DOES NOT have any alternate lines

Original Lines:

  • Over 5.5 | -110 | is_main=True
  • Under 5.5 | +110 | is_main=True

Sportsbook changes lines:

  • Over 5.5 | -110 | is_main=True ----> Over 6.5 | -110 | is_main=True
  • Under 5.5 | +110 | is_main=True ----> Under 6.5 | +110 | is_main=True
Filter is_main=True:

The events you will receive are:

  • {event: "locked-odds", data: {"data":[{"bet_name": "Over 5.5", "is_main": True, "bet_price": -110, ....}
  • {event: "locked-odds", data: {"data":[{"bet_name": "Under 5.5", "is_main": True, "bet_price": +110, ....}
  • {event: "odds", data: {"data":[{"bet_name": "Over 6.5", "is_main": True, "bet_price": -110, ....}
  • {event: "odds", data: {"data":[{"bet_name": "Under 6.5", "is_main": True, "bet_price": +110, ....}
Filter is_main=False:

You will not receive any events.

Filter is_main is not set

The events you will receive are:

  • {event: "locked-odds", data: {"data":[{"bet_name": "Over 5.5", "is_main": True, "bet_price": -110, ....}
  • {event: "locked-odds", data: {"data":[{"bet_name": "Under 5.5", "is_main": True, "bet_price": +110, ....}
  • {event: "odds", data: {"data":[{"bet_name": "Over 6.5", "is_main": True, "bet_price": -110, ....}
  • {event: "odds", data: {"data":[{"bet_name": "Under 6.5", "is_main": True, "bet_price": +110, ....}

Case 2: Sportsbook moves main line and DOES HAVE alternate lines.

Original Lines:

  • Over 5.5 | -110 | is_main=True
  • Under 5.5 | +110 | is_main=True
  • Over 6.5 | +120 | is_main=False
  • Under 6.5 | -120 | is_main=False

Sportsbook changes lines:

  • Over 5.5 | -110 | is_main=True ----> Over 5.5 | -120 | is_main=False
  • Under 5.5 | +110 | is_main=True ----> Under 5.5 | +120 | is_main=False
  • Over 6.5 | +120 | is_main=False ----> Over 6.5 | -110 | is_main=True
  • Under 6.5 | -120 | is_main=False ----> Under 6.5 | +110 | is_main=True
Filter is_main=True:

The events you will receive are:

  • {event: "odds", data: {"data":[{"bet_name": "Over 6.5", "is_main": True, "bet_price": -110, ....}
  • {event: "odds", data: {"data":[{"bet_name": "Under 6.5", "is_main": True, "bet_price": +110, ....}

Note that we WILL NOT send any locked events for:

  • Over 5.5 | -110 | is_main=True
  • Under 5.5 | +110 | is_main=True
Filter is_main=False:

The events you will receive are:

  • {event: "odds", data: {"data":[{"bet_name": "Over 5.5", "is_main": False, "bet_price": -120, ....}
  • {event: "odds", data: {"data":[{"bet_name": "Under 5.5", "is_main": False, "bet_price": +120, ....}

Note that we WILL NOT send any locked events for:

  • Over 6.5 | +120 | is_main=False
  • Under 6.5 | -120 | is_main=False
Filter is_main is not set

The events you will receive are:

  • {event: "odds", data: {"data":[{"bet_name": "Over 5.5", "is_main": False, "bet_price": -120, ....}
  • {event: "odds", data: {"data":[{"bet_name": "Under 5.5", "is_main": False, "bet_price": +120, ....}
  • {event: "odds", data: {"data":[{"bet_name": "Over 6.5", "is_main": True, "bet_price": -110, ....}
  • {event: "odds", data: {"data":[{"bet_name": "Under 6.5", "is_main": True, "bet_price": +110, ....}

Note that we WILL NOT send any locked events for:

  • Over 5.5 | -110 | is_main=True
  • Under 5.5 | +110 | is_main=True
  • Over 6.5 | +120 | is_main=False
  • Under 6.5 | -120 | is_main=False
Language
Authorization
Click Try It! to start a request and see the response here!