Push Stream for Odds API Documentation
Description
Push fromat & stream all the available betting odds for a specific game, team, player, sportsbook or market. Including American odds, decimal & fractional.
You'll need a license key to use OpticOdds' API. You can get one by contacting us at www.opticodds.com.
API Endpoint
https://api.opticodds.com/api/v2/stream/odds
Parameters
You must provide at least one of game_id
or league
per GET
request.
key
(required)
Your OpticOdds API license key.
sportsbooks
(required)
You can pass in multiple of this parameter.
The sportsbooks that you want to recieve odd changes for. The maximum is 25.
game_id
You can pass in multiple of this parameter.
The id of the game that you want to recieve odd changes for.
market
You can pass in multiple of this parameter.
The name of the market that you want to recieve odd changes for (e.g. Moneyline
).
league
You can pass in multiple of this parameter.
The league you want to receive odd changes for (e.g. NCAAB
)
is_main
If this is set to True, you will only get main lines and no alternates, if this is set to False, you will only get alternate lines. If this is ommitted, you will get all the lines.
The most balanced line and the main line are synonymous.
odds_format
The format of the odd price. Options are AMERICAN|PROBABILITY|DECIMAL
. We default to american prices.
last_entry_id
If this is set, it will get all events after the entry with this id, you can use this on reconnection events so you do not miss any updates. Note that this if you try and set a last_entry_id older than 30s ago, it will not use that and it will fallback to the last_entry_id that was at most 30s ago. The reason for this is to prevent abuse and cause our servers to go down.
include_game_updates
This is an optional field, and if it is set to true
, it will send game-status-notifications
and game-time-notifications
events in addition to the odds events.
include_grouping_keys
This is an optional field, and if it is set to true
, it will add a grouping_key
field to each odd that you can use to group odds across bet points. This is useful for cases when you have a spread market with these bet names: ["Home -1", "Away +1", "Home +1", "Away -1"]
, it is not straightforward how to know which home/away pair makeup the same market. The grouping_key
will be unique for a given market and should faciliate this.
These keys are unique by game_id/market. If you want a globally unique grouping_key, then we recommend adding {game_id}:{market}:{grouping_key}
.
Best Practices
While our endpoint supports passing multiple leagues and game ids, we recommend making a separate connection for each league or game id that you are trying to subscribe to. This will help reduce and isolate issues on a game or league basis. For instance, if you pass [game_id1, game_id2]
, if game_id2 ends and you are reconnecting to the stream, you will get an error saying game_id2
is no longer active.
Example Request Python
Requirements
- Python 3.10.2
- requests==2.31.0
- sseclient-py==1.8.0 | Need to use this sseclient dependency: https://pypi.org/project/sseclient-py/
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/v2/stream/odds",
params={
"key": "1234-5678-124",
"sportsbooks": ["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
Example Request Node.js
const EventSource = require("eventsource"); // npm install eventsource
const url = "https://api-external.oddsjam.com/api/v2/stream/odds";
const params = {
key: "1234-5678-124",
sportsbooks: ["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.sportsbooks.forEach((sportsbook) =>
queryString.append("sportsbooks", 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 Raw Response (from CURL)
We send a connected event.
event: connected
data: OK
We send a ping from the server to the client every 5 seconds.
event: ping
data: 2023-02-22 16:30:08.952760
If an odd gets locked. You can use this to tell if an odd is no longer available on a sportsbook.
event: locked-odds
id: 1715724429897-0
retry: 5000
data: {"data":[{"bet_name":"Jared Triolo Over 0.5","bet_points":0.5,"bet_price":-184,"bet_type":"Player Hits","game_id":"37337-36707-2024-05-14-16","id":"37337-36707-2024-05-14-16:william_hill:player_hits:jared_triolo_over_0_5","is_live":false,"is_main":true,"league":"MLB","player_id":"6266E41BE40F","selection":"Jared Triolo","selection_line":"over","selection_points":0.5,"sport":"baseball","sportsbook":"Caesars","team_id":"","timestamp":1715724429.8944778},{"bet_name":"Jared Triolo Under 0.5","bet_points":0.5,"bet_price":133,"bet_type":"Player Hits","game_id":"37337-36707-2024-05-14-16","id":"37337-36707-2024-05-14-16:william_hill:player_hits:jared_triolo_under_0_5","is_live":false,"is_main":true,"league":"MLB","player_id":"6266E41BE40F","selection":"Jared Triolo","selection_line":"under","selection_points":0.5,"sport":"baseball","sportsbook":"Caesars","team_id":"","timestamp":1715724429.8944778}],"entry_id":"1715724429897-0","type":"locked-odds"}
If an odd gets changed or added.
event: odds
id: 1715724413520-0
retry: 5000
data: {"data":[{"bet_name":"Chicago White Sox","bet_points":null,"bet_price":-101,"bet_type":"Moneyline","game_id":"37569-36174-2024-05-14-13","id":"37569-36174-2024-05-14-13:pinnacle:moneyline:chicago_white_sox","is_live":true,"is_main":true,"league":"MLB","player_id":"","selection":"Chicago White Sox","selection_line":"","selection_points":null,"sport":"baseball","sportsbook":"Pinnacle","team_id":"97700EF0AC9B","timestamp":1715724413.5053203},{"bet_name":"Washington Nationals","bet_points":null,"bet_price":-115,"bet_type":"Moneyline","game_id":"37569-36174-2024-05-14-13","id":"37569-36174-2024-05-14-13:pinnacle:moneyline:washington_nationals","is_live":true,"is_main":true,"league":"MLB","player_id":"","selection":"Washington Nationals","selection_line":"","selection_points":null,"sport":"baseball","sportsbook":"Pinnacle","team_id":"99DF68AB44BE","timestamp":1715724413.5053203}],"entry_id":"1715724413520-0","type":"odds"}
If the status changes for a game.
event: game-status-notifications
id: 1682533962108-1
retry: 5000
data: {"data":{"game":{"away_team":"Kansas City Royals","home_team":"Arizona Diamondbacks"},"game_id":"14412-35183-2023-04-26-12","is_notification":false,"league":"mlb","new_status":"half","old_status":"unplayed","timestamp":"2023-04-26T18:32:42.104724+00:00"},"entry_id":"1682533962108-1"}
If the start time changes for a game.
event: game-time-notifications
id: 1682533957709-0
retry: 5000
data: {"data":{"game":{"away_team":"Kansas City Royals","home_team":"Arizona Diamondbacks"},"game_id":"14412-35183-2023-04-26-12","is_notification":false,"league":"mlb","new_start_time":"2023-04-26 15:40 EST","old_start_time":"2023-04-26 15:45 EST","time_delta":5,"timestamp":"2023-04-26T18:32:37.705081+00:00"},"entry_id":"1682533957709-0"}
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 (Game ID + Sportsbook + Market + Bet Name)
.
If you are tracking a single sportsbook for a Game / Market combination. We would recommend making a key of (Game Id + Market + Bet 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