NBA Odds API: Player Props, Spreads & Moneylines
Why You Need an NBA Odds API
The NBA betting market moves fast. Lines shift within seconds of injury reports. Player props are the fastest-growing market in US sports betting. If you’re building models, scanners, or dashboards — you need more than DraftKings and FanDuel. You need the sharp line from Pinnacle, you need 117 bookmakers, and you need 1,154 markets per game.
Most APIs give you moneylines from 20 soft books. OddsPapi gives you moneylines, spreads, totals, player props, halves, and quarters from 117+ bookmakers — including the sharps that set the market. Pinnacle, Singbet, SBOBet. The books that move first.
One API call. 117 bookmakers. 1,154 markets. Every NBA game. Let’s build.
NBA Coverage: OddsPapi vs The Competition
| Feature | The Odds API | SportsGameOdds | OddsPapi |
|---|---|---|---|
| Bookmakers per game | ~15-20 | ~30 | 117+ |
| Moneylines | Yes | Yes | Yes (111 books) |
| Spreads | Basic | Yes | Every line (76-93 books) |
| Game Totals | Basic | Yes | Every line (82-95 books) |
| Player Props | No | Limited | Yes (full depth) |
| Half/Quarter lines | No | Limited | Yes (1H, 1Q, team totals) |
| Sharp books (Pinnacle) | No | No | Yes |
| DraftKings/FanDuel | Yes | Yes | Yes |
| Free tier | 500 req/mo | Limited | 250 req/mo |
OddsPapi also includes free historical odds data on the free tier — backtest your NBA models without paying a cent. Competitors charge extra for this. And when you need real-time updates, WebSocket streaming pushes line changes to your app the instant they happen. No polling. No stale data.
1,154 Markets Per Game: What’s Covered
Every NBA fixture on OddsPapi comes loaded with markets. Here’s what you’re working with:
| Market Type | Market ID(s) | Books | Notes |
|---|---|---|---|
| Moneyline | 111 | 111+ | Stable ID. Home/Away. |
| Game Spread | 113xx-114xx | 76-93 | Each line gets unique ID. Parse bookmakerOutcomeId for line value. |
| Game Total (O/U) | 112xx | 82-95 | Each line gets unique ID. Parse bookmakerOutcomeId for line. |
| 1st Half Moneyline | 11344 | ~60 | Home/Away for first half. |
| 1st Quarter Moneyline | 11350 | ~50 | Home/Away for first quarter. |
| 1st Half Spread | 118xxx | ~40 | Dynamic per line. |
| Team Totals | 111xxx | ~30 | Home and away team totals separate. |
| Player Props | 111xxx | 10-30 | Each player/prop combo is a unique market. |
How Spread & Total Market IDs Work
Unlike soccer’s fixed market IDs (e.g., 101 for Full Time Result), NBA spreads and totals are dynamic — each line gets its own market ID per fixture. The bookmakerOutcomeId field tells you the actual line: -7.5/home means home team -7.5, 228.5/over means game total over 228.5.
This makes it easy to compare the same spread across 90+ bookmakers programmatically. No guessing which market is which — the bookmakerOutcomeId is your decoder ring.
Python Tutorial: NBA Odds in 5 Steps
Step 1: Setup
import requests
from datetime import datetime, timedelta
API_KEY = "YOUR_API_KEY" # Free at oddspapi.io
BASE_URL = "https://api.oddspapi.io/v4"
SPORT_ID = 11 # Basketball
Authentication is simple: pass your API key as a query parameter. No headers, no OAuth, no tokens. Get a free API key here.
Step 2: Get Tonight’s NBA Games
def get_nba_fixtures():
"""Get today's NBA fixtures with odds."""
today = datetime.now().strftime("%Y-%m-%d")
tomorrow = (datetime.now() + timedelta(days=1)).strftime("%Y-%m-%d")
response = requests.get(f"{BASE_URL}/fixtures", params={
"apiKey": API_KEY,
"sportId": SPORT_ID,
"from": today,
"to": tomorrow
})
fixtures = response.json()
nba = [f for f in fixtures if "NBA" in f.get("tournamentName", "") and f.get("hasOdds")]
print(f"{len(nba)} NBA games tonight")
return nba
games = get_nba_fixtures()
for g in games:
print(f" {g['participant1Name']} vs {g['participant2Name']}")
The fixtures endpoint returns a flat list. Filter by tournamentName to isolate NBA games. The hasOdds flag tells you which fixtures have pricing data available. OddsPapi covers 462 basketball tournaments globally — NBA, NCAA, EuroLeague, WNBA, CBA, and more.
Step 3: Fetch Odds from 117+ Bookmakers
def get_odds(fixture_id):
"""Get odds from all bookmakers for a game."""
response = requests.get(f"{BASE_URL}/odds", params={
"apiKey": API_KEY,
"fixtureId": fixture_id
})
return response.json()
game = games[0]
odds = get_odds(game["fixtureId"])
bookmakers = list(odds.get("bookmakerOdds", {}).keys())
print(f"{len(bookmakers)} bookmakers pricing {game['participant1Name']} vs {game['participant2Name']}")
One call to /odds returns every bookmaker, every market, every outcome. The response is a flat dict with bookmakerOdds at the top level — no pagination, no nested wrappers.
Step 4: Compare Moneylines
def compare_moneylines(odds_data):
"""Compare moneyline (market 111) across all bookmakers."""
results = []
for slug, bookie in odds_data.get("bookmakerOdds", {}).items():
market = bookie.get("markets", {}).get("111")
if not market:
continue
row = {"bookmaker": slug}
for outcome_id, outcome in market.get("outcomes", {}).items():
for player_id, player in outcome.get("players", {}).items():
boid = player.get("bookmakerOutcomeId", "")
price = player.get("price")
if "home" in str(boid).lower() or outcome_id == list(market["outcomes"].keys())[0]:
row["home"] = price
else:
row["away"] = price
if "home" in row and "away" in row:
results.append(row)
results.sort(key=lambda x: x.get("home", 0), reverse=True)
print(f"\nMoneylines from {len(results)} bookmakers:")
print(f"{'Bookmaker':<20} {'Home':>8} {'Away':>8}")
print("-" * 38)
for r in results[:10]:
print(f"{r['bookmaker']:<20} {r.get('home', 'N/A'):>8} {r.get('away', 'N/A'):>8}")
best_home = max(results, key=lambda x: x.get("home", 0))
best_away = max(results, key=lambda x: x.get("away", 0))
print(f"\nBest Home: {best_home['home']} @ {best_home['bookmaker']}")
print(f"Best Away: {best_away['away']} @ {best_away['bookmaker']}")
compare_moneylines(odds)
Market 111 is the moneyline — the only stable, universal market ID for NBA. Every bookmaker prices it. This function finds the best home and away odds across 111+ books in one pass.
Step 5: Parse Spreads & Totals
def find_spreads(odds_data, target_line=None):
"""Find spread markets and compare across bookmakers.
NBA spreads use dynamic market IDs. The bookmakerOutcomeId
contains the line (e.g., '-7.5/home').
"""
spreads = {} # {line: [{bookmaker, home_price, away_price}]}
for slug, bookie in odds_data.get("bookmakerOdds", {}).items():
for mid, market in bookie.get("markets", {}).items():
for oid, outcome in market.get("outcomes", {}).items():
for pid, player in outcome.get("players", {}).items():
boid = str(player.get("bookmakerOutcomeId", ""))
price = player.get("price")
# Spread boids look like "-7.5/home" or "-7.5/away"
if "/" in boid and ("home" in boid or "away" in boid):
parts = boid.split("/")
try:
line = float(parts[0])
side = parts[1]
except (ValueError, IndexError):
continue
# Filter to spread-like lines (not totals which are 200+)
if abs(line) > 50:
continue
if line not in spreads:
spreads[line] = {}
if slug not in spreads[line]:
spreads[line][slug] = {}
spreads[line][slug][side] = price
# Show available lines
print(f"Available spread lines: {sorted(spreads.keys())}")
# Compare a specific line
if target_line and target_line in spreads:
line_data = spreads[target_line]
print(f"\nSpread {target_line} from {len(line_data)} bookmakers:")
print(f"{'Bookmaker':<20} {'Home':>8} {'Away':>8}")
print("-" * 38)
for slug, prices in sorted(line_data.items(), key=lambda x: x[1].get('home', 0), reverse=True)[:10]:
print(f"{slug:<20} {prices.get('home', 'N/A'):>8} {prices.get('away', 'N/A'):>8}")
return spreads
spreads = find_spreads(odds)
The key insight: NBA spreads don’t have a single market ID. Each line (-7.5, -3.5, +1.5, etc.) gets its own unique market ID per fixture. The bookmakerOutcomeId field is your decoder — it contains both the line and the side in a format like -7.5/home.
Accessing NBA Player Props
Player props are where the edge lives in NBA betting. Soft bookmakers are slow to adjust lines after injury news or lineup changes. With 117 bookmakers in one API call, you can spot stale lines in seconds.
Player props use high market IDs (111xxx range). Each player/prop combination — LeBron points over 25.5, Curry assists over 6.5 — gets its own market ID. The playerName field identifies the player.
def find_player_props(odds_data):
"""Find player prop markets. Props have playerName set."""
props = []
for slug, bookie in odds_data.get("bookmakerOdds", {}).items():
for mid, market in bookie.get("markets", {}).items():
for oid, outcome in market.get("outcomes", {}).items():
for pid, player in outcome.get("players", {}).items():
name = player.get("playerName")
if name: # Player props have playerName set
props.append({
"market_id": mid,
"player": name,
"price": player.get("price"),
"bookmaker": slug,
"line": player.get("bookmakerOutcomeId", "")
})
# Group by player
from collections import defaultdict
by_player = defaultdict(list)
for p in props:
by_player[p["player"]].append(p)
print(f"Player props found: {len(props)} across {len(by_player)} players")
for player, entries in sorted(by_player.items()):
books = len(set(e["bookmaker"] for e in entries))
markets = len(set(e["market_id"] for e in entries))
print(f" {player}: {markets} prop markets from {books} bookmakers")
find_player_props(odds)
This is the data that sharp bettors use to find value. When DraftKings has Jokic over 25.5 points at -110 and Pinnacle has it at -105, someone is wrong. OddsPapi lets you see both in one response.
DraftKings vs Pinnacle: Where the Lines Diverge
DraftKings and FanDuel set the lines that 90% of the US market sees. Pinnacle sets the line that sharps respect. When these two diverge, there’s opportunity.
def compare_books(odds_data, book1="pinnacle", book2="draftkings"):
"""Compare two bookmakers across all shared markets."""
b1 = odds_data.get("bookmakerOdds", {}).get(book1, {}).get("markets", {})
b2 = odds_data.get("bookmakerOdds", {}).get(book2, {}).get("markets", {})
shared = set(b1.keys()) & set(b2.keys())
print(f"{book1} markets: {len(b1)}, {book2} markets: {len(b2)}, shared: {len(shared)}")
# Compare moneyline
if "111" in shared:
print(f"\nMoneyline (market 111):")
for slug, markets in [(book1, b1), (book2, b2)]:
m = markets["111"]
for oid, outcome in m.get("outcomes", {}).items():
for pid, player in outcome.get("players", {}).items():
boid = player.get("bookmakerOutcomeId", oid)
print(f" {slug}: {boid} = {player.get('price')}")
compare_books(odds)
Pinnacle’s margin on NBA moneylines is typically 2-3%. DraftKings runs 4-5%. That difference is where value bettors and arbitrageurs operate. With OddsPapi, you can compare both — plus 115 other books — in a single API call.
FAQ: NBA Odds API
What sport ID is basketball/NBA?
Sport ID 11 covers all basketball. The NBA specifically is tournament ID 132. Use sportId=11 in the fixtures endpoint, then filter by tournamentName to isolate NBA games.
How many bookmakers cover NBA games?
117+ bookmakers per game, including sharp books like Pinnacle, SBOBet, and Singbet, US books like DraftKings and FanDuel, the Betfair Exchange, and crypto books like 1xBet.
Do you have NBA player props?
Yes. Each player/prop combination has its own market ID in the 111xxx range. The playerName field identifies the player, and the bookmakerOutcomeId contains the line and direction.
How do NBA spread market IDs work?
Each spread line gets a unique market ID per fixture. A game might have 50+ spread markets (one for each line like -7.5, -3.5, +1.5, etc.). Parse the bookmakerOutcomeId field to get the actual line — it’ll look like -7.5/home or +7.5/away.
Is the API free for NBA data?
The free tier gives you 250 requests per month. That’s enough to check lines for every NBA game, every night. Historical odds data is also included free — backtest your models without upgrading.
Do you cover college basketball (NCAA)?
Yes. OddsPapi covers 462 basketball tournaments globally, including NCAA, EuroLeague, WNBA, CBA (China), NBB (Brazil), and more. Same market depth, same bookmaker coverage.
Start Building with NBA Data
DraftKings and FanDuel set lines that 90% of bettors see. Pinnacle sets the line that sharps respect. With OddsPapi, you get both — plus 115 more bookmakers — in one API call.
117 bookmakers. 1,154 markets. Player props, spreads, totals, halves, quarters. Every NBA game, every night. Free tier included.