Frequently Asked Questions
Common questions about using the SportsGameOdds API. Don’t see your question? Contact us or join our Discord.
Getting Started
What is an odds API and how does it work?
An odds API delivers betting odds in machine-readable format, aggregating data from multiple sportsbooks. It provides moneylines, spreads, totals, player props, alternate lines, and settlement data through REST or WebSocket endpoints. SportsGameOdds updates every 30-60 seconds across 25+ sports, 55+ leagues, and 80+ bookmakers.
Instead of scraping bookmakers manually, an odds API aggregates odds from multiple sportsbooks and provides them through a simple REST interface.
How do I get an API key?
- Sign up at sportsgameodds.com/pricing
- Select a plan (free tier available)
- Check your email for your API key
- Start making requests immediately
No credit card is required for the free (Amateur) tier.
Can I test the API for free?
Yes! SportsGameOdds offers a free tier (Amateur plan) for testing and small projects with:
- 10 requests per minute
- 2,500 objects per month
- Access to all sports, leagues, and bookmakers
- No credit card required
The free tier is perfect for prototyping and testing. Sign up here.
What’s the fastest way to make my first API call?
Paste this URL into your browser (replace YOUR_API_KEY):
https://api.sportsgameodds.com/v2/events?apiKey=YOUR_API_KEY&leagueID=NBA,NFL,MLB&oddsAvailable=trueOr use code:
JavaScript:
const response = await fetch("https://api.sportsgameodds.com/v2/events?leagueID=NBA&oddsAvailable=true", { headers: { "x-api-key": "YOUR_API_KEY" } });
const { data } = await response.json();
console.log(data);Python:
import requests
response = requests.get(
"https://api.sportsgameodds.com/v2/events",
headers={"x-api-key": "YOUR_API_KEY"},
params={"leagueID": "NBA", "oddsAvailable": "true"}
)
print(response.json()["data"])Ruby:
require 'net/http'
require 'json'
uri = URI('https://api.sportsgameodds.com/v2/events?leagueID=NBA&oddsAvailable=true')
response = Net::HTTP.get_response(uri, {'x-api-key' => 'YOUR_API_KEY'})
puts JSON.parse(response.body)['data']PHP:
$ch = curl_init('https://api.sportsgameodds.com/v2/events?leagueID=NBA&oddsAvailable=true');
curl_setopt($ch, CURLOPT_HTTPHEADER, ['x-api-key: YOUR_API_KEY']);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
$response = json_decode(curl_exec($ch), true);
print_r($response['data']);Java:
HttpClient client = HttpClient.newHttpClient();
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create("https://api.sportsgameodds.com/v2/events?leagueID=NBA&oddsAvailable=true"))
.header("x-api-key", "YOUR_API_KEY")
.build();
HttpResponse<String> response = client.send(request, HttpResponse.BodyHandlers.ofString());
System.out.println(response.body());How do I authenticate my requests?
Include your API key in one of two ways:
Option 1: Header (recommended)
x-api-key: YOUR_API_KEYOption 2: Query parameter
?apiKey=YOUR_API_KEYThe header method is recommended because it keeps your API key out of URL logs.
Do you have an SDK I can use?
Yes! Official SDKs are available for:
- TypeScript/JavaScript:
npm install sports-odds-api - Python:
pip install sports-odds-api - Ruby:
gem install sports-odds-api - Go:
go get github.com/SportsGameOdds/sports-odds-api-go - Java: Maven/Gradle packages available
SDKs include auto-pagination, type safety, retry logic, and error handling. See the SDK Guide for details.
Data Coverage
What sports and leagues do you support?
We support 55+ leagues across 25+ sports including NFL, NBA, MLB, NHL, UFC, EPL, La Liga, Champions League, and many more.
See Sports Reference for all sports and Leagues Reference for the complete league list. Call the /leagues endpoint to see leagues available to your API key.
How many bookmakers do you cover?
We cover 80+ bookmakers including DraftKings, FanDuel, BetMGM, Caesars, Pinnacle, Bet365, PrizePicks, and many more US, international, and DFS platforms.
See Bookmakers Reference for the complete list.
Do you support player props?
Yes! We provide extensive player prop coverage across all major sports. To see specific league + market + bookmaker coverage, check out the Supported Markets page.
Do you offer historical odds data?
Yes, but the availability can vary. Depending on your plan, you may have access to up to 2 years of historical data. In general, you may see less specific data the further back you go.
We’re expanding historical data access in phases:
- Event metadata + final team scores
- Team & player stats + main market opening/closing odds
- Prop markets + bookmaker-specific odds
Use startsAfter and startsBefore to query historical events by date.
Do you provide live/in-play odds?
Yes! We provide live odds for games currently in progress. Use live=true to filter:
/events?leagueID=NBA&live=trueWhat betting markets are available?
We support all major betting markets including moneyline, spread, over/under, 3-way moneyline (soccer), yes/no props, and more. Markets are available for full game, halves, quarters, and other periods.
See Supported Markets for coverage by league and bookmaker, and Bet Types Reference for all betTypeID values.
How do I see a list of supported markets?
You can check out our Supported Markets page to see a list of supported markets by league and bookmaker.
However, this changes frequently so for the most comprehensive and up-to-date information you should use the /markets endpoint.
Note: The endpoint defaults to isSupported=true, returning only markets full support/coverage. In many cases, markets with isSupported=false may sometimes return data as well.
See GET /markets for full parameter documentation.
Do you have deeplinks to bookmakers?
Yes we do. However, we don’t guarentee that every market for every bookmaker will include a deeplink. Each event includes links.bookmakers with event-level deeplinks. We also include a deeplink field at the top level of byBookmaker objects (odds.<oddID>.byBookmaker.<bookmakerID>.deeplink). We’re in the process of adding more deeplink coverage across bookmakers.
Understanding Odds Data
What is an oddID?
The oddID uniquely identifies a specific betting option. It allows us to uniquely identify the market (what you’re betting on) and the side (which side of the market you’re taking) for a given event. Here is the format:
{statID}-{statEntityID}-{periodID}-{betTypeID}-{sideID}Examples:
points-home-game-ml-home= Moneyline on home team (full game)points-all-game-ou-over= Total points over (full game)assists-LEBRON_JAMES_NBA-game-ou-over= LeBron assists over (player prop)
See Odds Reference for complete details on each component.
What’s the difference between fairOdds and bookOdds?
fairOdds: Consensus odds with juice/vig removed - represents true probabilitybookOdds: Consensus odds across bookmakers with juice included
Use fairOdds to calculate true implied probability and compare byBookmaker odds to find value bets.
See Consensus Odds for calculation methodology.
What are the different bet types (betTypeID)?
The main bet types are ml (moneyline), sp (spread), ou (over/under), ml3way (3-way moneyline for soccer), yn (yes/no), and eo (even/odd).
See Bet Types Reference for all betTypeID and sideID values.
How do I convert American odds to decimal/fractional?
American to Decimal:
JavaScript:
function americanToDecimal(american) {
if (american > 0) return american / 100 + 1;
return 100 / Math.abs(american) + 1;
}
americanToDecimal(-110); // 1.909
americanToDecimal(150); // 2.5Python:
def american_to_decimal(american):
if american > 0:
return american / 100 + 1
return 100 / abs(american) + 1
american_to_decimal(-110) # 1.909
american_to_decimal(150) # 2.5Ruby:
def american_to_decimal(american)
return american / 100.0 + 1 if american > 0
100 / american.abs.to_f + 1
end
american_to_decimal(-110) # 1.909
american_to_decimal(150) # 2.5PHP:
function americanToDecimal($american) {
if ($american > 0) return $american / 100 + 1;
return 100 / abs($american) + 1;
}
americanToDecimal(-110); // 1.909
americanToDecimal(150); // 2.5Decimal to Implied Probability:
function decimalToImpliedProb(decimal) {
return (1 / decimal) * 100;
}
decimalToImpliedProb(1.909); // ~52.4%
decimalToImpliedProb(2.5); // 40%See Handling Odds Guide for more conversions.
What does statEntityID mean?
statEntityID identifies who the stat applies to: home (home team), away (away team), all (combined/total), or a specific playerID for player props.
See Stat Entity Reference for details.
What are the different periodIDs?
periodID defines the time segment: game (full game), reg (regulation only), 1h/2h (halves), 1q-4q (quarters), 1p-3p (hockey periods), 1i-9i (innings), 1s-5s (tennis sets), and ot (overtime).
See Periods Reference for all values by sport.
Why does “points” mean different things in different sports?
The statID of points represents the primary scoring stat used to determine the winner - actual points in basketball/football, runs in baseball, goals in hockey/soccer, and sets in tennis.
This allows consistent querying across sports (e.g., points-home-game-ml-home works for any sport). Sport-specific stats like goals or runs are also available. See Stats Reference.
Making API Requests
How do I get odds data?
All odds data comes from the /events endpoint. Each event includes an odds object keyed by oddID:
JavaScript:
const response = await fetch("https://api.sportsgameodds.com/v2/events?leagueID=NBA&oddsAvailable=true", { headers: { "x-api-key": API_KEY } });
const { data } = await response.json();
// Access odds for an event
const event = data[0];
const moneyline = event.odds["points-home-game-ml-home"];
console.log(`Home ML: ${moneyline.bookOdds}`);Python:
import requests
response = requests.get(
"https://api.sportsgameodds.com/v2/events",
headers={"x-api-key": API_KEY},
params={"leagueID": "NBA", "oddsAvailable": "true"}
)
data = response.json()["data"]
# Access odds for an event
event = data[0]
moneyline = event["odds"]["points-home-game-ml-home"]
print(f"Home ML: {moneyline['bookOdds']}")Ruby:
require 'net/http'
require 'json'
uri = URI('https://api.sportsgameodds.com/v2/events?leagueID=NBA&oddsAvailable=true')
response = Net::HTTP.get_response(uri, {'x-api-key' => API_KEY})
data = JSON.parse(response.body)['data']
# Access odds for an event
event = data[0]
moneyline = event['odds']['points-home-game-ml-home']
puts "Home ML: #{moneyline['bookOdds']}"PHP:
$ch = curl_init('https://api.sportsgameodds.com/v2/events?leagueID=NBA&oddsAvailable=true');
curl_setopt($ch, CURLOPT_HTTPHEADER, ['x-api-key: ' . $API_KEY]);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
$data = json_decode(curl_exec($ch), true)['data'];
// Access odds for an event
$event = $data[0];
$moneyline = $event['odds']['points-home-game-ml-home'];
echo "Home ML: " . $moneyline['bookOdds'];Use query parameters to filter which odds are returned. See Odds Reference.
How do I filter by specific bookmaker(s)?
Use the bookmakerID parameter:
// Single bookmaker
/v2/events?leagueID=NBA&bookmakerID=draftkings
// Multiple bookmakers (comma-separated)
/v2/events?leagueID=NBA&bookmakerID=draftkings,fanduel,betmgmThis filters the byBookmaker object in each odd to only include specified bookmakers, reducing response size and improving performance.
See Bookmakers Reference for all available values.
How do I filter by specific team(s)?
Use the teamID parameter:
// Single team
/v2/events?leagueID=NBA&teamID=LOS_ANGELES_LAKERS_NBA
// Multiple teams
/v2/events?leagueID=NBA&teamID=LOS_ANGELES_LAKERS_NBA,GOLDEN_STATE_WARRIORS_NBAThis returns only events where the specified teams are playing. Team IDs follow the pattern {TEAM_NAME}_{LEAGUE}.
How do I get only player props?
Filter using statEntityID=player with bet type ou (over/under):
// All player props for NBA
/v2/events?leagueID=NBA&betTypeID=ou&statEntityID=player
// Specific stat (points props only)
/v2/events?leagueID=NBA&betTypeID=ou&statID=points&statEntityID=player
// Specific player
/v2/events?leagueID=NBA&playerID=LEBRON_JAMES_NBAWhat’s the difference between live=true and finalized=false?
live=true: Only games currently in progressfinalized=false: Upcoming + live games (not yet settled)started=false: Only upcoming games (not started yet)finalized=true: Only completed games (historical results)
/v2/events?leagueID=NBA&live=true // Live games only
/v2/events?leagueID=NBA&finalized=false // Upcoming + live
/v2/events?leagueID=NBA&started=false // Upcoming onlyHow do I use pagination (cursor)?
Use cursor-based pagination for large result sets:
// First request
const page1 = await fetch("/v2/events?leagueID=NBA&limit=50", { headers });
const data1 = await page1.json();
// data1.nextCursor = "n.1720564800000.abc123"
// Next page
const page2 = await fetch(`/v2/events?leagueID=NBA&limit=50&cursor=${data1.nextCursor}`, { headers });- Increase
limit(default: 10, max varies by endpoint) to get more results per request - Continue until
nextCursoris null - Cursors are opaque strings - don’t parse or construct them
See Data Batches Guide.
How do I optimize API response times?
Follow these best practices:
Use specific filters - More parameters = smaller response
javascript// Slow - gets everything /v2/events?leagueID=NBA // Fast - filtered /v2/events?leagueID=NBA&bookmakerID=draftkings&oddID=points-home-game-ml-homeSpecify
oddIDwhen you only need specific marketsjavascript/v2/events?leagueID=NBA&oddID=points-home-game-ml-home,points-all-game-ou-overAvoid
includeAltLines=trueunless needed - Alt lines dramatically increase response sizeUse appropriate date ranges
javascript/v2/events?leagueID=NBA&startsAfter=2025-01-15&startsBefore=2025-01-16Cache responses - Pre-game odds don’t change frequently
See Response Speed Guide for more tips.
How do I get alternate lines?
Add includeAltLines=true to your request:
/v2/events?leagueID=NBA&includeAltLines=trueAlternate lines appear in the byBookmaker object with different spread/overUnder values:
{
"byBookmaker": {
"draftkings": {
"odds": "-110",
"spread": "-5.5",
"altLines": [
{ "spread": "-4.5", "odds": "-125" },
{ "spread": "-6.5", "odds": "+105" }
]
}
}
}Note: Alt lines significantly increase response size. Only include when needed.
Pricing & Rate Limits
What does “object” mean in pricing?
An object = 1 event (game), regardless of how many odds markets or bookmakers are included. You get ALL markets and ALL bookmakers for the same price - unlike competitors who charge per market or per bookmaker.
This pricing model makes SportsGameOdds 60-80% cheaper than competitors. See Rate Limiting for details.
What are the rate limits for each plan?
Rate limits vary by plan tier. The free Amateur plan includes 10 requests/min and 2,500 objects/month. Paid plans (Rookie, Pro, AllStar) offer higher limits up to unlimited.
Check your current usage with /account/usage. See Rate Limiting for full details and pricing page for plan comparison.
How often should I poll for odds updates?
- Pre-game (7+ days out): 15-30 minutes
- Pre-game (24-48 hours): 5-15 minutes
- Pre-game (game day): 2-5 minutes
- Live in-game: 30-60 seconds
- Critical moments: Use streaming (AllStar plan)
Cache data longer for games far in the future. Many markets aren’t offered until 24-48 hours before gametime.
How do I check my current usage?
Call the /account/usage endpoint:
const response = await fetch("https://api.sportsgameodds.com/v2/account/usage", {
headers: { "x-api-key": API_KEY },
});
const { data } = await response.json();
console.log(`Requests this minute: ${data.rateLimits["per-minute"].currentIntervalRequests}`);
console.log(`Objects this month: ${data.rateLimits["per-month"].currentIntervalEntities}`);This endpoint doesn’t count against your rate limits.
What happens if I exceed my rate limit?
You’ll receive a 429 Too Many Requests error. To handle this:
- Wait and retry - Rate limits reset at the start of each interval
- Check usage - Use
/account/usageto see which limit you hit - Optimize requests - Use filters to reduce object count
- Upgrade plan - If consistently hitting limits
if (response.status === 429) {
const retryAfter = response.headers.get("Retry-After") || 60;
await new Promise((resolve) => setTimeout(resolve, retryAfter * 1000));
// Retry request
}Why is SportsGameOdds cheaper than competitors?
Our pricing model charges per event, not per market or bookmaker. A single NBA game with 100 markets × 20 bookmakers costs 1 unit with SportsGameOdds vs. potentially 2,000 units with per-market-per-bookmaker pricing.
This means 60-80% lower costs, predictable billing, and no need to optimize which data you fetch. See Rate Limiting for details.
Real-Time & Streaming
Do you offer webhooks?
Real-time updates are available via our Streaming API on the AllStar plan. Instead of webhooks, we provide WebSocket connections via Pusher that notify you instantly when:
- Odds change
- Lines move
- Events start/finish
- Scores update
For non-AllStar plans, we recommend polling at appropriate intervals. Contact us to upgrade.
How does the real-time streaming API work?
The Streaming API uses WebSockets (via Pusher protocol) for instant updates:
- Get connection details from
/v2/stream/events - Connect via WebSocket using provided credentials
- Receive eventID notifications when data changes
- Fetch updated data via
/v2/events?eventIDs=...
const axios = require("axios");
const Pusher = require("pusher-js");
// 1. Get stream info
const streamInfo = await axios.get("/v2/stream/events", {
headers: { "x-api-key": API_KEY },
params: { feed: "events:live" },
});
// 2. Connect to WebSocket
const pusher = new Pusher(streamInfo.data.pusherKey, streamInfo.data.pusherOptions);
// 3. Subscribe and listen
pusher.subscribe(streamInfo.data.channel).bind("data", async (changedEvents) => {
const eventIDs = changedEvents.map((e) => e.eventID).join(",");
const updated = await axios.get(`/v2/events?eventIDs=${eventIDs}`, { headers });
// Process updated events
});See Real-Time Streaming Guide for complete examples.
What feeds are available for streaming?
Three feeds are available: events:live (all live games), events:upcoming (upcoming games by league), and events:byid (specific event updates).
Streaming requires AllStar plan. See Streaming Guide for setup and examples.
Errors & Troubleshooting
Why am I getting a 401 error?
A 401 Unauthorized error means authentication failed. Common causes:
Missing API key header
javascript// Wrong - key in URL only fetch("/v2/events?apiKey=xxx"); // Correct - key in header fetch("/v2/events", { headers: { "x-api-key": "xxx" } });Whitespace in key - Use
apiKey.trim()Wrong header name - Must be exactly
x-api-key(lowercase)Old/revoked key - Check your email or account dashboard
Why am I getting a 403 error?
A 403 Forbidden error means your key lacks permission. Causes:
Cancelled subscription - Check subscription status
Failed payment - Update billing info
Feature not in plan - e.g., streaming requires AllStar
Wrong API version - Keys default to v2; v1 requires special access
Check the error message field for specifics.
Why am I getting a 429 error?
A 429 Too Many Requests error means you’ve exceeded rate limits:
- Wait - Limits reset each minute/hour/month
- Check which limit - Use
/account/usage - Optimize - Use filters to reduce objects returned
- Upgrade - If consistently hitting limits
// Check your limits
const usage = await fetch("/v2/account/usage", { headers });
const data = await usage.json();
console.log(data.data.rateLimits);See Rate Limiting.
How should I handle errors in my code?
Implement retry logic with exponential backoff:
JavaScript:
async function fetchWithRetry(url, options, maxRetries = 3) {
for (let i = 0; i < maxRetries; i++) {
const response = await fetch(url, options);
if (response.ok) return response.json();
// Don't retry client errors (except 429)
if (response.status >= 400 && response.status < 500 && response.status !== 429) {
throw new Error(`Client error: ${response.status}`);
}
// Wait with exponential backoff
const delay = Math.pow(2, i) * 1000 + Math.random() * 1000;
await new Promise((resolve) => setTimeout(resolve, delay));
}
throw new Error("Max retries exceeded");
}Python:
import time
import random
def fetch_with_retry(url, headers, max_retries=3):
for i in range(max_retries):
response = requests.get(url, headers=headers)
if response.ok:
return response.json()
# Don't retry client errors (except 429)
if 400 <= response.status_code < 500 and response.status_code != 429:
raise Exception(f"Client error: {response.status_code}")
# Wait with exponential backoff
delay = (2 ** i) + random.random()
time.sleep(delay)
raise Exception("Max retries exceeded")See Error Reference for all error codes and handling examples.
How accurate is your data?
We aggregate data from multiple premium providers with validation algorithms. Odds accuracy is 99.9%+ (direct bookmaker feeds), scores/stats are 99.5%+ (verified against official sources), and event metadata is 99.9%+ (verified against league sources).
If you notice issues, report them with the eventID.
Advanced Topics
Can I use the API in my frontend/browser?
Not recommended. This exposes your API key publicly. Instead:
Option 1: Backend proxy (recommended)
// Your backend (Node.js/Express)
app.get("/api/odds", async (req, res) => {
const response = await fetch("https://api.sportsgameodds.com/v2/events?leagueID=NBA", {
headers: { "x-api-key": process.env.SGO_API_KEY },
});
res.json(await response.json());
});
// Your frontend
fetch("/api/odds"); // Calls your backend, not SGO directlyOption 2: Sync to database
- Server-side process syncs data to your database
- Frontend queries your database instead
See Best Practices for security guidelines.
How do I get a draw-no-bet line?
A draw-no-bet line functions as a moneyline where a draw returns your stake. Look for:
betTypeID:mlsideID:homeorawayperiodID:reg(regulation only, common for soccer)
/v2/events?leagueID=EPL&betTypeID=ml&periodID=regFor soccer, if no reg period odds exist, use game period odds as fallback.
Why don’t I see players (lineups) in Event data?
Players appear in event data based on odds availability:
Future events: We add players when sportsbooks start offering props on them (more accurate than roster-based lineups)
Smaller leagues: Some leagues may not have player data. Contact us to add support.
If you need lineups for a specific event, contact us with the eventID.
Where do I get season stats?
We don’t currently provide a dedicated season stats endpoint. However, you can:
- Calculate yourself: Query
/eventsfor completed games and aggregate stats - Use historical data: Sum player/team stats across events
// Get completed games for a player
/v2/events?leagueID=NBA&playerID=LEBRON_JAMES_NBA&finalized=true&startsAfter=2024-10-01Season stats endpoint is on our roadmap. Contact us for specific needs.
How do I get consensus/average odds?
Consensus odds are included in every response via fairOdds (juice removed) and bookOdds (with juice). These are calculated automatically using median values across all bookmakers.
See Consensus Odds for the calculation methodology.
What if I need a league or bookmaker you don’t list?
We can add additional leagues, regional bookmakers, and exchange data through custom plans. Most additions can be completed within 1-2 weeks.
Contact us to discuss your needs.
How fast do you update odds compared to bookmaker sites?
The speed at which you will receive updates can vary based on your plan as well as certain factors related to the event itself. For example, some events in the far future may receive updates less frequently than events that are happening right now. However, on average, we update odds data every 30-60 seconds.
