ParlayAPI Documentation
Real-time sports odds API: 22 sources, 13+ books in a single call, 6× cheaper than the-odds-api. Drop-in compatible with TOA's URL surface where it makes sense, with extensions for player props, prediction-market exchanges, and WebSocket streaming.
Quick Start
Three lines to your first call:
pip install parlay-api
from parlay_api import ParlayAPI
client = ParlayAPI(api_key="YOUR_KEY")
odds = client.odds("baseball_mlb", regions="us")curl "https://parlay-api.com/v1/sports/baseball_mlb/odds?regions=us" \ -H "X-API-Key: YOUR_KEY"
const r = await fetch(
"https://parlay-api.com/v1/sports/baseball_mlb/odds?regions=us",
{ headers: { "X-API-Key": "YOUR_KEY" } }
);
const odds = await r.json();import requests
r = requests.get(
"https://parlay-api.com/v1/sports/baseball_mlb/odds",
headers={"X-API-Key": "YOUR_KEY"},
params={"regions": "us"},
)
odds = r.json()Sign up free for a 1,000-credit/month key. No card required.
Authentication
Pass your API key one of two ways:
- Header:
X-API-Key: YOUR_KEY(recommended) - Query param:
?apiKey=YOUR_KEY(TOA-compatible)
WebSocket connections use the query param: wss://parlay-api.com/ws/odds/{sport_key}?apiKey=YOUR_KEY
Credits & Pricing
Every paid endpoint deducts a fixed number of credits per call regardless of how many books, players, or markets come back. One /props call returns ALL books for that sport.
| Endpoint | Credits | Notes |
|---|---|---|
/v1/sports | 0 | Free, lists active sport keys |
/v1/sports/{key}/events | 1 | Deduped via canonical_event_id |
/v1/sports/{key}/odds | 1 | TOA-shape moneyline/spread/total |
/v1/sports/{key}/props | 3 | All books, all markets, single call |
/v1/sports/{key}/consensus | 3 | Best/worst per (player, market, line) |
/v1/sports/{key}/arbitrage | 10 | Cross-book arb scanner |
/v1/sports/{key}/ev | 5 | +EV picks vs Pinnacle baseline |
/v1/sports/{key}/live | 1 | In-play games |
/v1/sports/{key}/live/points | 1 | Live PBP snapshot (Free tier OK) |
/v1/sports/{key}/live/sse | 7 / event | Live PBP stream (Starter+, billed per delivered event) |
/v1/sports/{key}/live/book_latency | 5 | Per-book lag for arb-mining (Pro+) |
/v1/sports/{key}/live/period_markets | 2 | 1H, Q1-Q4 spreads/totals/h2h (Free OK) |
/v1/inplay/arbs | 10 | Live arb scanner (5s refresh) |
/v1/event-markets/search | 0 beta | Kalshi, Polymarket, and Novig event-market discovery |
/v1/historical/... | 5 | 2012-present closing lines |
/v1/historical/stats | 0 | Public summary, cached 10min |
/v1/stats | 0 | Public |
/ws/odds/{key} | 0 + tier | Business+ tier, no per-frame charge |
Tiers
| Tier | Price | Credits/mo | Concurrent SSE/WS | Best for |
|---|---|---|---|---|
| Free | $0 | 1,000 | 1 (polling only) | Trying it out, light testing |
| Starter | $5 | 20,000 | 3 (SSE) | Small scanner, 1-2 sports |
| Pro | $20 | 100,000 | 25 (SSE + WS) | Serious bettor, multi-sport |
| Business | $40 | 1,000,000 | 100 (SSE + WS) | Tools, content, reseller |
| Enterprise / Scale | contact | 5M+ | 1000 (SSE + WS) | Bespoke deals, custom SLA |
Concurrent SSE/WS = max simultaneous push-stream connections per API key. Polling endpoints aren't connection-capped, only credit-capped. Hit the limit and new SSE / WS connections return 429 / WS close 4002 with a clear reason; existing connections aren't affected.
Sandbox (no auth, fake data)
Hit /v1/sandbox/sports, /v1/sandbox/sports/{sport_key}/odds, /v1/sandbox/sports/{sport_key}/live/period_markets, or /v1/sandbox/sports/{sport_key}/live/sse to see the response shape with deterministic synthetic data. No API key, no credits consumed, IP rate-limited at 60 req/min. Useful for verifying integration shape during off-hours when no real games are live.
Source health diagnostic
Live-betting bots need to know when a source goes stale so they don't trade on dead data. GET /v1/sports/{sport_key}/live/source-health?apiKey=YOUR_KEY returns per-source freshness for the requested sport (events in the last 5 min, seconds since last event, latest capture timestamp). 1 credit per call. Recommended polling cadence: 30 seconds. A source with seconds_since_last_event > 60 during a known-live game has likely failed; failover yourself or rely on our internal failover (one source going down doesn't break customer SSE — primary auto-promotes).
Postman + OpenAPI spec
Full machine-readable OpenAPI spec at /openapi.json (129 paths). Postman supports importing OpenAPI directly: Postman → Import → Link → paste the URL above → Import. Auto-generated collection with every endpoint pre-populated.
Official SDKs
Python: pip install parlay-api · source
JavaScript / Node: npm install parlay-api · source
Both SDKs ship with built-in math helpers (devig, Kelly sizing, American↔implied conversions) and async iterators for SSE / WebSocket streams.
API stability
Read the full versioning + deprecation policy. Short version: paths under /v1/ are stable. Additive changes ship without notice. Breaking changes ship under /v2/ with 12+ months of overlap. Pricing changes get 30+ days of notice. Your integration won't break overnight.
Every response includes x-requests-used, x-requests-remaining, and x-requests-last headers so you always know how much you've burned. Empty responses still bill normally (no auto-refund, see the leak fix if curious).
Python SDK
Pure-Python single-file SDK on PyPI: pip install parlay-api. Source on GitHub.
The SDK is a near drop-in replacement for the-odds-api's official Python clients with extra methods for our extensions and built-in devig math helpers.
from parlay_api import ParlayAPI
client = ParlayAPI(api_key="YOUR_KEY")
# TOA-compatible methods
sports = client.sports()
events = client.events("baseball_mlb")
odds = client.odds("baseball_mlb", regions="us", markets=["h2h", "spreads"])
historical = client.historical_odds("baseball_mlb", date="2024-10-15")
# Extensions
props = client.props("baseball_mlb", markets=["player_total_bases"])
arbs = client.arbitrage("baseball_mlb", limit=20)
consensus = client.consensus("baseball_mlb")
# Devig math (no network call)
fair_over, fair_under = ParlayAPI.devig(over_price=-110, under_price=-110)
edge_pct = ParlayAPI.edge(book_price=-105, fair_prob=fair_over)
# WebSocket URL builder
ws_url = client.websocket_url("baseball_mlb")Sports
List all sport keys with at least one event in the last 24 hours.
Response
[
{
"key": "baseball_mlb",
"group": "Baseball",
"title": "MLB",
"description": "Major League Baseball",
"active": true,
"has_outrights": false
},
...
]See Sport Keys reference for the full list.
Events
List events for a sport. Deduped by canonical_event_id (an MD5 of sport + date + sorted team names) so the same matchup from books with different team naming conventions ("NY Yankees" vs "New York Yankees") collapses into one event.
Parameters
iso (default) or unixResponse
[
{
"id": "8b1f3a2c0e9d4...",
"canonical_event_id": "ee78855a3bdd1019",
"sport_key": "baseball_mlb",
"sport_title": "MLB",
"commence_time": "2026-05-01T19:35:00Z",
"home_team": "New York Yankees",
"away_team": "Kansas City Royals"
},
...
]Odds
TOA-compatible game-line odds. Returns moneyline, spread, totals (and player_* markets if you ask for them) across every book that publishes them.
Parameters
us, us2, uk, eu, au. Default: ush2h (moneyline), spreads, totals, outrights. Player markets accepted too: player_total_bases, etc.draftkings,fanduel,pinnacle. See Bookmaker Keys.american (default) or decimaliso (default) or unixExample
odds = client.odds(
"baseball_mlb",
regions="us",
markets=["h2h", "spreads", "totals"],
bookmakers=["draftkings", "fanduel", "pinnacle"],
)curl "https://parlay-api.com/v1/sports/baseball_mlb/odds?regions=us&markets=h2h,spreads,totals&bookmakers=draftkings,fanduel,pinnacle" \ -H "X-API-Key: YOUR_KEY"
Player Props
Player prop odds across every book in one call. Each row has over_price, under_price, line, and the bookmaker source. Includes the standard sportsbooks plus DFS apps (PrizePicks, Underdog, Betr, Sleeper, Pick6, ParlayPlay) and exchange data (Novig, ProphetX, Kalshi).
Parameters
player_total_bases,player_hits_runs_rbis. See Market Keys.?player=Judge)midpoint (default, +100/-100 zero-vig) or effective (-137/-137 reflecting actual 2-pick payout)Response shape
[
{
"bookmaker": "draftkings",
"bookmaker_title": "DraftKings",
"player": "Aaron Judge",
"market_key": "player_home_runs",
"market": "Home Runs",
"line": 0.5,
"over_price": 290,
"under_price": -370,
"home_team": "New York Yankees",
"away_team": "Kansas City Royals",
"canonical_event_id": "ee78855a3bdd1019",
"commence_time": "2026-05-01T19:35:00Z",
"last_update": 1746130000000
},
...
]Consensus
For each unique (event, player, market, line), returns the best and worst price across all books, the median consensus, and the spread between them. Useful for line-shopping. DFS books are excluded from the math.
Response per row
{
"canonical_event_id": "ee78855a3bdd1019",
"home_team": "New York Yankees", "away_team": "Kansas City Royals",
"player": "Aaron Judge", "market_key": "player_home_runs", "line": 0.5,
"num_books": 4, "total_books": 4,
"consensus_odds": 290, "consensus_prob": 0.2564,
"best_odds": {"bookmaker": "fliff", "price": 310},
"worst_odds": {"bookmaker": "draftkings", "price": 270},
"spread": 40,
"all_books": [
{"bookmaker": "fliff", "price": 310},
{"bookmaker": "fanduel", "price": 295},
...
]
}Arbitrage
Two-leg arbs across books on the same prop, with optimal stake split and projected profit. DFS books excluded. Profit cap 15% (anything higher is almost certainly stale or mis-paired data).
Parameters
+EV Picks
Bets where one book's price implies a higher win probability than a "fair" baseline (Pinnacle de-vigged, with Novig and ProphetX as exchange-priced cross-checks). Returns book, line, edge percentage, and Kelly-optimal stake.
Live Games
Currently in-progress games with grouped book quotes. Sub-10s freshness on our in-play collectors.
Live Point-by-Point (PBP)
Real-time match-state events. Covers tennis, baseball (MLB), basketball (NBA), hockey (NHL), MMA (UFC), boxing, NFL, and soccer (Premier League, La Liga, Bundesliga, Serie A, Ligue 1, UEFA Champions / Europa, MLS). Cross-source redundancy: when our primary feed for a sport drops, a fallback (ESPN / SofaScore) auto-promotes within 30 seconds.
Snapshot (polling)
Returns current state for one match (with match_id) or all in-play matches for the sport (omit match_id). Free tier OK.
Parameters
Stream (Server-Sent Events)
Persistent SSE connection. Server pushes each state change (point won, game won, set closed, goal, foul, pitch outcome, period change) within ~50ms of the event. Starter+ tier required. Use the standard EventSource API.
A typical 3-set tennis match delivers ~200 events (~1,400 credits). A typical NBA game delivers ~400-500 actions. You pay proportional to the volume of state changes the match produces.
// JS
const es = new EventSource('https://parlay-api.com/v1/sports/tennis/live/sse?match_id=...&apiKey=...');
es.addEventListener('initial_state', e => console.log('snap', JSON.parse(e.data)));
es.addEventListener('pbp_event', e => console.log('event', JSON.parse(e.data)));
Cross-book latency
Per-book lag relative to our primary PBP feed. Returns each book's effective latency in seconds. Pro+ tier. Use case: arb scanners check this every few seconds and flag matches where a specific book has stale lines (positive lag > 5s typically means an exploitable window).
{
"sport_key": "baseball_mlb",
"results": [
{"match": "Yankees vs Rangers", "book": "fanduel", "lag_seconds": 7.8, ...},
{"match": "Yankees vs Rangers", "book": "draftkings","lag_seconds": 1.2, ...},
{"match": "Yankees vs Rangers", "book": "caesars", "lag_seconds": 4.0, ...}
]
}
Period markets (1H, Q1-Q4, halves, NHL periods)
In-game spreads, totals, and h2h for sub-game periods: 1st half, 2nd half, quarters (NBA, WNBA, NFL, NCAAF), hockey periods (NHL), or first 5 / first 7 innings (MLB). Includes alternate lines: a single Q1 spread query for one NBA game returns ~8 to 10 alt lines. Open to all tiers (Free can run ~500 test calls; continuous high-frequency polling needs Pro or Business). Latency: 1 to 4 seconds (vs 30s+ on the-odds-api).
Query params: period (FT, 1H, 2H, Q1, Q2, Q3, Q4, OT, P1, P2, P3, F5, F7, or 'all'), match_id, source, market (spread, total, h2h). All optional except apiKey.
GET /v1/sports/basketball_nba/live/period_markets?period=Q1&market=spread&apiKey=...
{
"sport_key": "basketball_nba",
"period": "Q1",
"market": "spread",
"count": 18,
"results": [
{"source":"pinnacle", "home_team":"Oklahoma City Thunder",
"away_team":"Los Angeles Lakers", "period_key":"Q1",
"market":"spread", "side":"home", "line":-3.5, "price":-144,
"age_seconds":1, ...},
{"source":"pinnacle", "side":"away", "line":3.5, "price":120, ...},
...alt lines from -2.5 through -6.0...
]
}
GET /v1/sports/{sport_key}/live/period_markets/sources returns which books have which periods active right now (last 10 min). Useful for client-side discovery.
Sports with period coverage: NBA (1H, 2H, Q1-Q4), WNBA (1H, 2H, Q1-Q4), NCAAB (1H, 2H), NFL (1H, 2H, Q1-Q4), NCAAF (1H, 2H, Q1-Q4), NHL (P1, P2, P3), MLB (F5, F7), soccer leagues (1H, 2H). Coverage depends on book availability per period; e.g. Pinnacle has every period for every sport, DraftKings/FanDuel/BetMGM/Caesars vary by league.
In-Play Arbitrage Scanner
Cross-book arbs detected during live games. Updated every 5 seconds. Pairs with the WebSocket: subscribers receive {"type":"arb_flagged"} frames the moment a new arb is found.
Historical Odds
Historical is split by product shape so modelers can tell prices from results. We do not derive or invent missing odds.
| Product | Endpoint | Use case | Important distinction |
|---|---|---|---|
| Point-in-time odds | /odds | TOA-compatible historical snapshots | Requires date; limited by tier window. |
| Closing odds | /closing-odds | Backtests at final pregame price | Game lines and prop closing rows where real prices exist. |
| Match/results archive | /matches | Schedules, teams, scores, esports results | Rows include has_odds; result-only rows are not price history. |
| Forward line movement | /line-movement | CLV and price-change tracking | Starts when ParlayAPI began capturing that market. |
Esports note: CS2, Dota 2, and Valorant have historical match/result archives plus current forward Pinnacle price capture. They do not yet have deep historical before/during/after odds movement for past years.
Parameters
/matches, return only rows that include real odds.Exchanges
Exchange-specific data including order book depth where available. Currently novig (sports) and prophetx (sports + politics).
Event Market Search
Free-text discovery across Kalshi, Polymarket, and Novig event markets. Built for Specials, next-team markets, coach-out markets, trade deadline markets, and other non-standard contracts that do not fit a fixed sport/event schema.
Try the live demo at /event-markets.
Parameters
AJ Brown next team or Mike Vrabel out before September.kalshi,polymarket,novig. Default checks all three.0.balanced shows a mix of venues. match sorts by match confidence and volume.Example
curl 'https://parlay-api.com/v1/event-markets/search?q=AJ%20Brown%20next%20team&sources=kalshi,novig,polymarket&min_volume=1000'
Response highlights
{
"query": "AJ Brown next team",
"credits_charged": 0,
"source_summary": {
"kalshi": {"count": 10, "max_volume": 283989.29},
"novig": {"count": 4, "max_volume": 9458.94}
},
"markets": [
{
"source": "kalshi",
"event_title": "A.J. Brown's Next Team",
"outcome": "New England",
"prices": {"yes_bid": 0.79, "yes_ask": 0.81}
}
],
"clusters": [
{
"cluster_key": "aj brown next team",
"sources": ["kalshi", "novig"],
"note": "Candidate text match only. Prices remain source-native and are not blended."
}
]
}WebSocket: /ws/odds
Real-time odds streaming for one sport. Business / Enterprise / Scale tier required. Receives a JSON frame the moment a price changes anywhere in our collector pipeline.
Frame types
| type | When | Payload |
|---|---|---|
initial_state | On connect | Last 500 props for the sport |
odds_update | Every change | Array of changed rows |
arb_flagged | New arb detected | The arb opportunity (5s scanner) |
heartbeat | Every 30s | Connection health |
Filter to one game
Send a subscribe frame after connect:
{"type": "subscribe", "event_id": "ee78855a3bdd1019"}To unsubscribe and receive sport-wide updates again:
{"type": "unsubscribe"}Python example
from parlay_api import ParlayAPI
import asyncio, json
import websockets
async def stream():
client = ParlayAPI(api_key="YOUR_KEY")
url = client.websocket_url("baseball_mlb")
async with websockets.connect(url) as ws:
async for raw in ws:
frame = json.loads(raw)
if frame["type"] == "odds_update":
for row in frame["data"]:
print(row["bookmaker"], row["player"],
row["over_price"], row["under_price"])
asyncio.run(stream())End-to-end delivery latency: 300–800 ms on Scale (raw), up to the tier coalesce window otherwise (Business 1 s, Enterprise 0.5 s). Source cadence varies by book and market.
SSE Hot Feed: /v1/sse/hot
EventSource-compatible HTTP stream for enterprise hot paths. It sends a connection frame, source freshness, initial state, then live updates with a 5-second heartbeat.
Filters
| param | example | meaning |
|---|---|---|
bookmakers | fanduel,pinnacle,caesars | Only these books |
kinds | game,prop | Game lines, props, or both |
markets | player_points,player_rebounds | Prop market keys |
event_id | 2026-05-07_Team_A_Team_B | Single event filter |
heartbeat_s | 5 | 1 to 30 seconds |
const es = new EventSource(
"https://parlay-api.com/v1/sse/hot/baseball_mlb?apiKey=YOUR_KEY&bookmakers=fanduel,pinnacle&kinds=game&heartbeat_s=5"
);
es.onmessage = (ev) => {
const msg = JSON.parse(ev.data);
if (msg.type === "odds_update") console.log(msg.data);
};# Python quickstart
import json, requests
url = "https://parlay-api.com/v1/sse/hot/baseball_mlb"
params = {
"apiKey": "YOUR_KEY",
"bookmakers": "fanduel,pinnacle",
"kinds": "game",
"heartbeat_s": 5,
}
with requests.get(url, params=params, stream=True, timeout=60) as r:
r.raise_for_status()
for line in r.iter_lines(decode_unicode=True):
if line and line.startswith("data: "):
msg = json.loads(line[6:])
if msg["type"] in ("hot_feed_status", "odds_update"):
print(msg)Hot feed means fast delivery once a book update lands in our pipeline. It does not invent prices or promise that every external book publishes a new price every 5 seconds.
Operational check: admins run python3 scripts/verify_book_coverage.py before broad outreach or deploys to prove active books survive REST and SSE visibility.
WebSocket: /ws/live
Same protocol as /ws/odds but session-cookie authenticated (used by the live dashboard). For programmatic streaming, use /ws/odds with an API key.
Errors & Status Codes
| Code | Meaning | Action |
|---|---|---|
200 | OK | Use response body |
400 | Invalid sport_key or param | Check spelling and Sport Keys |
401 | Missing or invalid API key | Pass X-API-Key header or ?apiKey= |
403 | Credit limit exceeded | Wait for monthly reset or upgrade tier |
404 | Resource not found | Endpoint or event_id doesn't exist |
422 | Missing required param | Check the param table for that endpoint |
429 | Rate limited | Slow down or retry with backoff |
500 | Server error | Retry. If it persists, email peakpotentialmediaventures@gmail.com |
The Python SDK raises typed exceptions: InvalidAPIKeyError, CreditLimitExceededError, RateLimitedError, TierGatedError, all subclasses of ParlayAPIError.
Sport Keys
Live keys (those with active events in the last 24h). The /v1/sports endpoint returns the current authoritative list.
| Key | Sport |
|---|---|
baseball_mlb | MLB |
basketball_nba | NBA |
basketball_wnba | WNBA |
basketball_ncaab | NCAAB |
icehockey_nhl | NHL |
americanfootball_nfl | NFL |
americanfootball_ncaaf | NCAAF |
mma_mixed_martial_arts | MMA / UFC |
tennis_atp | ATP |
tennis_wta | WTA |
soccer_epl | English Premier League |
soccer_spain_la_liga | La Liga |
soccer_germany_bundesliga | Bundesliga |
soccer_italy_serie_a | Serie A |
soccer_france_ligue_one | Ligue 1 |
soccer_usa_mls | MLS |
golf_pga | PGA Tour |
golf_liv | LIV Golf |
esports_lol | League of Legends |
esports_cs2 | Counter-Strike 2 |
esports_val | Valorant |
Plus 100+ regional soccer/basketball/baseball/hockey leagues via Pinnacle. Hit /v1/sports for the current full list.
Bookmaker Keys
| Key | Book | Type |
|---|---|---|
draftkings | DraftKings | Sportsbook |
fanduel | FanDuel | Sportsbook |
caesars | Caesars | Sportsbook |
bovada | Bovada | Sportsbook |
betmgm | BetMGM | Sportsbook |
fanatics | Fanatics | Sportsbook |
pinnacle | Pinnacle | Sharp book (de-vig baseline) |
fliff | Fliff | Sportsbook |
bet365 | bet365 | Sportsbook |
betrivers | BetRivers | Sportsbook |
hardrock | Hard Rock | Sportsbook |
tipico | Tipico | Sportsbook |
pointsbet | PointsBet | Sportsbook |
parx | Parx | Sportsbook |
stake | Stake | Sportsbook |
sugarhouse | SugarHouse | Sportsbook |
novig | Novig | Exchange |
prophetx | ProphetX | Exchange |
kalshi | Kalshi | Prediction market |
prizepicks | PrizePicks | DFS pick'em |
underdog | Underdog | DFS pick'em |
betr | Betr | DFS pick'em |
sleeper | Sleeper | DFS pick'em |
pick6 | Pick6 (DraftKings) | DFS pick'em |
parlayplay | ParlayPlay | DFS pick'em |
Market Keys
The most-used market keys per sport. Hit /v1/sports/{sport_key}/props/markets for the full live list.
MLB
player_total_bases, player_hits, player_home_runs, player_rbis, player_runs, player_singles, player_doubles, player_triples, player_walks, player_strikeouts, player_pitcher_outs, player_hits_allowed, player_earned_runs, player_hits_runs_rbis, player_first_hit, player_first_home_run
NBA / WNBA
player_points, player_rebounds, player_assists, player_threes, player_steals, player_blocks, player_turnovers, player_pra (pts+reb+ast), player_pts_rebs, player_pts_asts, player_rebs_asts, player_double_double, player_triple_double
NHL
player_goals, player_assists, player_points_nhl, player_shots_on_goal, player_saves, player_anytime_goal, player_powerplay_points, player_first_goal_scorer, player_anytime_goal_scorer
NFL
player_pass_yds, player_pass_tds, player_pass_completions, player_rush_yds, player_rec_yds, player_receptions, player_anytime_td, player_first_td, player_longest_rec, player_interceptions
Soccer
player_anytime_goalscorer, player_shots_on_target, player_assists, player_goals_assists, player_fouls, player_total_sets, plus the standard h2h, spreads, totals at the game level.
Migration from the-odds-api
If you're already using TOA, the URL surface for moneyline/spread/total odds is API-compatible. Change the host and you're done:
# Was: TOA_BASE = "https://api.the-odds-api.com/v4" # Now: TOA_BASE = "https://parlay-api.com/v1"
Your existing TOA Python clients pointing at this base URL will work for /sports, /sports/{key}/odds, /sports/{key}/events, and /historical/sports/{key}/odds. Player props are available at /sports/{key}/props with a different (better) shape, see Player Props.
Detailed comparison: parlay-api.com vs the-odds-api