Free Sports Data API: 69 Sports of Schedules & Odds (Python)
Free Sports Data API: Schedules & Live Odds for 69 Sports
Search “free sports data API” and you get a wall of providers that either gate the good data behind a sales call or hand you a 20-event free tier that runs dry by lunchtime. If what you actually need is the betting-market data layer — fixtures, schedules, and live odds across every sport and bookmaker — this guide shows you how to pull it in Python from a genuinely free API.
One honest note up front, because it saves you 20 minutes: OddsPapi is an odds-first sports data API. You get the schedule (who plays whom, when, and whether the game is live or finished) plus prices from 370 bookmakers. You do not get play-by-play scores, box scores, or player stat lines. If you need final scores, every fixture ships a map of third-party IDs (Sofascore, Betradar, Pinnacle) so you can join results from a stats feed yourself. More on that below — we’ll be specific about what’s in the JSON and what isn’t.
What “Sports Data” Actually Means (and What You’re Probably Searching For)
“Sports data API” is a catch-all. In practice, devs searching it want one of three different things:
| You want… | Example | Is OddsPapi the right tool? |
|---|---|---|
| Schedules & fixtures | “What MLB games are on tonight?” | ✅ Yes — 69 sports, full boards |
| Betting odds & lines | “What’s the moneyline on Yankees vs Red Sox?” | ✅ Yes — 370 books, live |
| Live & historical odds | “How did the line move before kickoff?” | ✅ Yes — free historical endpoint |
| Final scores & results | “Who won, and what was the score?” | ⚠️ Partial — status only, no score |
| Player/team stats | “How many strikeouts did the pitcher throw?” | ❌ No — use a dedicated stats feed |
If your project lives in the top three rows — an odds comparison tool, an arb scanner, a value-betting model, a line-movement tracker — the rest of this post is your fast path. If you only need the bottom two rows, you want a stats provider, not us, and we’d rather tell you that now than waste your free credits.
The Old Way vs. The Free API
| The Old Way | OddsPapi |
|---|---|
| Scrape each sportsbook site, fight Cloudflare, re-write your parser every time the HTML changes | One JSON endpoint, 370 books normalized to the same shape |
| “Free tier” capped at ~500 credits where one odds call burns 6+ credits | 250 free requests/month, each returning a full board of books |
| Historical odds locked behind a paid add-on | Free /historical-odds endpoint for backtesting |
| ~40 bookmakers, no sharps | 370 books including Pinnacle, Polymarket, Kalshi, crypto |
| Polling on a timer, always a few seconds stale | Real-time WebSocket push available |
Step 1: Authentication
Auth is a query parameter, not a header. Grab a free key and pass it on every call.
import requests
API_KEY = "YOUR_API_KEY"
BASE_URL = "https://api.oddspapi.io/v4"
# apiKey is a QUERY PARAMETER, never a header
r = requests.get(f"{BASE_URL}/sports", params={"apiKey": API_KEY})
print(r.status_code) # 200
print(len(r.json()), "sports") # 69
Step 2: Discover the Sports Catalog
The /sports endpoint returns all 69 sports with their IDs and slugs. You need the sportId for every downstream call.
sports = requests.get(f"{BASE_URL}/sports", params={"apiKey": API_KEY}).json()
for s in sports[:8]:
print(s["sportId"], s["slug"], s["name"])
# 10 soccer Soccer
# 11 basketball Basketball
# 12 tennis Tennis
# 13 baseball Baseball
# 14 american-football American Football
# 15 ice-hockey Ice Hockey
# ...
Step 3: Pull the Schedule (Fixtures)
The /fixtures endpoint is your schedule feed. Give it a sportId and a date range (max 10 days apart) and it returns every fixture — teams, start time, tournament, status, and whether odds are available.
params = {
"apiKey": API_KEY,
"sportId": 13, # Baseball
"from": "2026-06-05",
"to": "2026-06-08",
}
fixtures = requests.get(f"{BASE_URL}/fixtures", params=params).json()
for f in fixtures:
if f["hasOdds"] and f["statusName"] == "Pre-Game":
print(f["fixtureId"], f["participant1Name"], "vs", f["participant2Name"])
# id1300010963301407 New York Yankees vs Boston Red Sox
# id1300010963302659 Chicago Cubs vs San Francisco Giants
# ...
How busy is the schedule? Here’s the real fixture count for a single 3-day window (June 5–8, 2026), and how many already carry live odds:
| Sport | Fixtures (3-day window) | With live odds |
|---|---|---|
| Soccer | 1,553 | 734 |
| Baseball | 603 | 81 |
| Tennis | 357 | 177 |
| Basketball | 192 | 97 |
| Cricket | 128 | 69 |
| Ice Hockey | 21 | 12 |
What’s in a Fixture Object (and What Isn’t)
This is the honesty section. Here’s a real fixture, trimmed:
{
"fixtureId": "id1300010963301407",
"sportId": 13,
"tournamentName": "MLB",
"startTime": "2026-06-05T23:05:00.000Z",
"statusName": "Pre-Game", # Pre-Game | Live | Finished | Cancelled
"hasOdds": true,
"participant1Name": "New York Yankees",
"participant2Name": "Boston Red Sox",
"externalProviders": {
"sofascoreId": 16217421,
"betradarId": 67360970,
"pinnacleId": 1631660206,
"opticoddsId": "2026060539CEEF6E"
}
}
You get status (so you know if a game is live or final) but no score and no stats. The trade-off: every fixture carries externalProviders — a map of IDs into Sofascore, Betradar, Pinnacle, and others. If you need final scores, use the sofascoreId to look the result up in a stats feed and join it back to the odds on fixtureId. That gives you a clean two-source pipeline: OddsPapi for the market data, a stats provider for the box score.
Step 4: Fetch the Odds
Pass a fixtureId to /odds and you get every bookmaker pricing that game. The response is nested — bookmakerOdds → slug → markets → marketId → outcomes → outcomeId → players["0"] → price.
fid = "id1300010963301407" # Yankees vs Red Sox
odds = requests.get(f"{BASE_URL}/odds",
params={"apiKey": API_KEY, "fixtureId": fid}).json()
books = odds["bookmakerOdds"]
print(len(books), "books on this fixture") # 14
def moneyline(slug, outcome):
# market 131 = moneyline; outcome 131 = home, 132 = away
try:
node = books[slug]["markets"]["131"]["outcomes"][outcome]["players"]["0"]
return node["price"] if node.get("active") else None
except KeyError:
return None
for slug in sorted(books):
home, away = moneyline(slug, "131"), moneyline(slug, "132")
if home and away:
print(f"{slug:18} {home} {away}")
# betmgm 1.67 2.25
# caesars 1.69 2.22
# draftkings 1.67 2.23
# fanduel 1.69 2.22
# kalshi 1.724 2.273
# pinnacle 1.719 2.28
# polymarket 1.724 2.326
# williamhill 1.69 2.22
Ten of the 14 books priced the moneyline, including the sharp anchor (Pinnacle) and two prediction markets (Kalshi, Polymarket). That mix — sharps, US retail books, and prediction markets in one call — is the thing the ~40-book “free” APIs can’t give you.
Step 5: Turn Raw Prices Into Fair Odds
Pinnacle is the sharpest book in the field, so de-vigging its line gives you a fair-probability benchmark to grade every other price against. Strip the margin with the simple two-way method:
def devig_two_way(home, away):
ih, ia = 1/home, 1/away
total = ih + ia # > 1; the overround
return ih/total, ia/total, (total - 1) * 100
p_home, p_away, vig = devig_two_way(1.719, 2.28) # Pinnacle
print(f"Vig: {vig:.2f}%") # 2.03%
print(f"Fair: NYY {p_home*100:.1f}% BOS {p_away*100:.1f}%")
# Fair: NYY 57.0% BOS 43.0% -> fair odds 1.754 / 2.326
Pinnacle’s margin on this game was just 2.03%. The no-vig fair line was Yankees 1.754 / Red Sox 2.326. Now line-shop: the best available Red Sox price across all 10 books was Polymarket at 2.326 — exactly the sharp fair number, and better than every retail book (FanDuel/Caesars/William Hill all sat at 2.22). The best Yankees price was Kalshi at 1.724. Finding those outliers is one loop:
def best_price(books, market, outcome):
best, who = 0, None
for slug, data in books.items():
try:
node = data["markets"][market]["outcomes"][outcome]["players"]["0"]
if node.get("active") and node["price"] > best:
best, who = node["price"], slug
except KeyError:
continue
return who, best
print(best_price(books, "131", "132")) # ('polymarket', 2.326)
Free Historical Odds (For Backtesting)
The same fixture has a price history. /historical-odds returns every recorded snapshot per book — free, where competitors charge for it. The shape differs from live: the top key is bookmakers (not bookmakerOdds), and players["0"] is a list of snapshots, not a single price. Max 3 books per call.
params = {"apiKey": API_KEY, "fixtureId": fid, "bookmakers": "pinnacle"}
hist = requests.get(f"{BASE_URL}/historical-odds", params=params).json()
snaps = hist["bookmakers"]["pinnacle"]["markets"]["131"]["outcomes"]["131"]["players"]["0"]
for snap in snaps[:5]:
print(snap["createdAt"], snap["price"])
Feed that into a closing-line-value model and you can grade your bets against where the sharp line actually closed — the only honest measure of whether you’re beating the market. Our historical odds CSV/Excel guide walks through flattening this into a DataFrame.
FAQ
Is there a truly free sports data API?
Yes. OddsPapi’s free tier gives you 250 requests/month across 69 sports and 370 bookmakers, including free historical odds. There’s no credit card required and no sales call to unlock it.
Does the API include live scores and player stats?
No. OddsPapi is odds-first: you get schedules, fixture status (Pre-Game/Live/Finished), and bookmaker odds, but not scores or stat lines. Each fixture carries externalProviders IDs (Sofascore, Betradar) so you can join results from a dedicated stats feed.
How many sports and bookmakers are covered?
69 sports and 370 bookmakers as of June 2026, spanning sharps (Pinnacle, SBOBet), US books (DraftKings, FanDuel, BetMGM), prediction markets (Polymarket, Kalshi), and crypto books (Stake, 1xBet).
What’s the difference between this and an odds API?
None, functionally — “sports data API” and “odds API” describe the same product here. The data is fixtures plus odds. The only thing a generic “sports data” feed adds that we don’t is scores and stats.
How do I get real-time updates instead of polling?
OddsPapi pushes price changes over a WebSocket feed so you don’t have to poll on a timer. For most projects, polling /odds every few seconds on the free tier is plenty.
Start Pulling Sports Data
You now have the full path: discover sports, pull the schedule, fetch odds from 370 books, de-vig against Pinnacle, and line-shop for the best price — all on a free key. Stop scraping sportsbook HTML. Get your free API key and make your first call in the next five minutes.
More tutorials: Free Odds API guide · Sportsbook API · Line shopping in Python · Live betting API · The Odds API free tier limits.