MLB Player Props API: Home Runs, Strikeouts & Hits Odds in Python
You Want MLB Player Props. The Sportsbooks Don’t Publish Them.
You need home run odds for your model. Strikeout lines for a pitcher scanner. Total bases for a +EV tool. DraftKings and FanDuel price all of it, and none of them hand you an API. So you end up scraping HTML that breaks every time they ship a redesign, or you buy a generic feed that gives you 40 books and a shallow MLB menu.
OddsPapi fixes both problems. One call returns every MLB player prop a book is pricing, as JSON, across 350+ bookmakers. This post shows the exact parse path (there is one gotcha that trips up most people), pulls real prices off a live Yankees vs Dodgers game, and line-shops a home run prop across the books that actually carry it.
All the numbers below came off the live API while writing this. Prices move, so yours will differ, but the code and the structure are what you keep.
Why the Usual Routes Fall Short
Three ways people get prop odds today, and why each one hurts:
- Scraping the books directly. DraftKings and FanDuel render props client-side behind rotating tokens and bot detection. Your scraper works until Tuesday.
- Generic sports APIs. The Odds API gives you roughly 40 bookmakers. SportsGameOdds gates props behind paid tiers and adds a delay on the free plan. Their prop coverage on MLB is a headline, not a JSON payload you can parse.
- Assuming the sharps price props. They mostly don’t. Pinnacle, Kalshi and Polymarket quote the game (moneyline, run line, totals). Player props come from the US retail books: DraftKings, FanDuel, Caesars, BetMGM, William Hill. That matters for how you fetch and how you compare.
OddsPapi aggregates the books that carry props and normalizes every price into decimal, American and fractional. You query one endpoint and parse one shape.
| The Old Way | OddsPapi |
|---|---|
| Scrape each book’s prop page, fight bot detection | One /odds call returns every book’s props |
| ~40 books on a generic feed | 350+ bookmakers, US retail props included |
| Player props gated or delayed on free tiers | Player props on the free tier |
| Convert American odds yourself | Decimal, American and fractional pre-computed |
| Historical props behind an enterprise paywall | Free historical prop odds for backtesting |
The One Thing That Trips People Up
OddsPapi’s odds response is nested. For a normal game line, the price sits under a player key of "0":
bookmakerOdds[slug]["markets"][market_id]["outcomes"][outcome_id]["players"]["0"]["price"]
Player props break that assumption. The players dict is keyed by player ID, not "0", and each entry carries a playerName. One outcome (“Over 0.5 hits”) holds every batter in the lineup at once:
"players": {
"1410115": {
"playerName": "Edman, Tommy",
"price": 2.31,
"priceAmerican": "131",
"active": true,
...
},
"2010275": { "playerName": "Betts, Mookie", "price": 1.41, ... }
}
If you hardcode players["0"] the way the game-line tutorials do, every prop market comes back empty and you conclude the data isn’t there. It is. Iterate the dict, skip the "0" key (that’s the team or game line), and read playerName off each entry. Names come back "Last, First".
Build It: MLB Player Props in Python
All code below is tested against the live API. Auth is a query parameter (apiKey), never a header.
Step 1: Authenticate and Build the Name Lookups
Market and outcome IDs are integers. Human-readable names (“Strikeouts”, “Over”) come from /markets. Baseball is sportId=13.
import requests, time
API_KEY = "YOUR_API_KEY"
BASE_URL = "https://api.oddspapi.io/v4"
SPORT_ID = 13 # Baseball / MLB
def get(path, **params):
params["apiKey"] = API_KEY
r = requests.get(f"{BASE_URL}/{path}", params=params)
r.raise_for_status()
return r.json()
catalog = get("markets", sportId=SPORT_ID)
market_name = {m["marketId"]: m["marketName"] for m in catalog}
outcome_name = {
(m["marketId"], o["outcomeId"]): o["outcomeName"]
for m in catalog for o in m.get("outcomes", [])
}
Step 2: Find a Live MLB Fixture
Pull fixtures in a date range (max 10 days apart), then keep the MLB games that have odds. The catalog also carries Triple-A and other leagues under baseball, so filter on tournamentName.
from datetime import date, timedelta
today = date.today()
fixtures = get("fixtures", sportId=SPORT_ID,
**{"from": today.isoformat(),
"to": (today + timedelta(days=3)).isoformat()})
mlb = [f for f in fixtures
if f.get("tournamentName") == "MLB" and f.get("hasOdds")]
for f in mlb[:5]:
print(f["fixtureId"], f["participant1Name"], "vs", f["participant2Name"])
Team names live on participant1Name and participant2Name directly on the fixture object. In our test window this returned 45 MLB games with live odds. We’ll use a marquee interleague matchup, New York Yankees vs Los Angeles Dodgers.
Step 3: Pull the Props
Request the US books that price player props. Filtering by bookmakers keeps the payload small, but note one behavior: if any requested slug is absent on the fixture, the filter can zero the whole response. List only books you expect to be there, or omit the filter and read every book back.
fid = "id1300010963303845" # Yankees vs Dodgers
time.sleep(1) # ~0.9s cooldown between calls to the same endpoint
data = get("odds", fixtureId=fid,
bookmakers="draftkings,fanduel,caesars,betmgm")
books = data["bookmakerOdds"]
On this game DraftKings and Caesars each priced 39 player-prop markets, FanDuel priced 13. BetMGM had game lines up but no props yet: prop menus fill in as first pitch approaches, so an empty prop list an hour out is normal, not a bug.
Step 4: Parse the Prop Structure
Here is the parser that respects the player-ID keying. It yields one row per player per outcome.
def player_props(book, market_id):
"""Yield (player, outcome, decimal, american, active) for a prop market."""
market = books.get(book, {}).get("markets", {}).get(str(market_id))
if not market:
return
for oid, outcome in market["outcomes"].items():
label = outcome_name.get((market_id, int(oid)), oid)
for player_id, p in outcome["players"].items():
if player_id == "0": # game/team line, not a player prop
continue
yield p["playerName"], label, p["price"], p["priceAmerican"], p["active"]
# Anytime home run is market 131663, outcome "1+"
for name, label, price, am, active in player_props("draftkings", 131663):
if label == "1+":
print(f"{name:20} 1+ HR {price} ({am})")
Real output for the Dodgers’ big bats (anytime home run, DraftKings):
| Player | 1+ HR (decimal) | American |
|---|---|---|
| Shohei Ohtani | 2.98 | +198 |
| Freddie Freeman | 4.39 | +339 |
| Mookie Betts | 5.49 | +449 |
Step 5: Line-Shop a Prop Across Books
Props are where line shopping pays off most, because retail books disagree on player pricing far more than they disagree on a moneyline. Same prop, best available decimal price:
def best_prop_price(market_id, player, outcome_label):
best = None
for book in books:
for name, label, price, am, active in player_props(book, market_id):
if name == player and label == outcome_label and active and price > 1:
if best is None or price > best[1]:
best = (book, price)
return best
print(best_prop_price(131663, "Betts, Mookie", "1+"))
Mookie Betts to hit a home run priced at 5.00 on Caesars, 5.30 on FanDuel, and 5.49 on DraftKings. Taking the best line is a 9.8% larger payout on the exact same bet. Over a season of props, that gap is the difference between a break-even model and a losing one. This is best available price, not a value call: the active and price > 1 checks matter, because a stale or suspended outlier will otherwise win your “best price” every time.
Step 6: De-Vig a Two-Way Prop
Pitcher strikeout lines come as a clean Over/Under, which makes them easy to strip of margin. Roki Sasaki’s strikeouts, set at 4.5, priced Over 1.63 / Under 2.24 on FanDuel:
def devig_two_way(over, under):
io, iu = 1 / over, 1 / under
total = io + iu
return {
"vig_pct": round((total - 1) * 100, 2),
"fair_over": round(total / io, 3),
"fair_under": round(total / iu, 3),
"p_over": round(io / total * 100, 1),
}
print(devig_two_way(1.63, 2.24))
# {'vig_pct': 5.99, 'fair_over': 1.728, 'fair_under': 2.374, 'p_over': 57.9}
The book’s margin on that line is 5.99%. Strip it out and the fair price on the Over is 1.728, implying a 57.9% chance Sasaki clears 4.5 strikeouts. That fair number is your benchmark: shop the Over across every book carrying it, and any price above 1.728 is beating the book’s own no-vig line. For a full treatment of margin removal, see the no-vig odds guide.
The Full MLB Prop Menu
Player props are first-class markets in the feed, not an afterthought. Every market below returned live player-level prices on our test fixture. Look each ID up through /markets?sportId=13 rather than trusting a hardcoded list, since the catalog grows.
| Category | Prop | Market ID |
|---|---|---|
| Batter | Home Runs (1+, 2+, 3+) | 131663 |
| Batter | Hits / Over-Under Hits | 131553 / 131543 |
| Batter | Total Bases / O-U | 131589 / 131577 |
| Batter | RBIs / O-U | 131607 / 131597 |
| Batter | Runs | 131569 |
| Batter | Singles / Doubles / Triples | 131840 / 131852 / 131860 |
| Batter | Walks / Stolen Bases | 131824 / 131672 |
| Batter | Hits + Runs + RBIs | 131873 |
| Pitcher | Strikeouts (line ladder) | 131643, 131621, 131623 … |
| Pitcher | Earned Runs / Hits Allowed / Outs | 131676+ / 131709+ / 131766 |
The strikeout market comes as a ladder: 131621 is the Over/Under 4.5 line, 131623 is 5.5, and so on up the board. Gerrit Cole’s number was set higher than Sasaki’s on this card, with his 5.5 line priced Over 2.12 / Under 1.70 on FanDuel. Resolve the ladder from the catalog by reading each market’s handicap field.
Backtest Props with Free Historical Odds
A prop model is only worth as much as its backtest. OddsPapi gives historical prop odds away on the free tier, where competitors put it behind enterprise contracts. The historical endpoint has a different shape from the live one: the top key is bookmakers (not bookmakerOdds), and players[player_id] holds a list of price snapshots instead of a single dict. Cap it at three bookmakers per call.
hist = get("historical-odds", fixtureId=fid,
bookmakers="draftkings,fanduel,caesars")
# hist["bookmakers"][slug]["markets"][mid]["outcomes"][oid]["players"][pid]
# -> [ {"createdAt": ..., "price": ..., "active": ...}, ... ]
Loop it across a slate of finished games and you have a training set of opening-to-closing prop movement, no scraping and no invoice. Pair it with the player props value scanner to hunt outliers automatically.
Where This Fits
This is the MLB-deep companion to our general player props API guide (NFL and NBA included) and the MLB odds API guide for run lines and totals. To line-shop across every market, not just props, read the line shopping tutorial. Start with a free API key and the whole board is one request away.
Frequently Asked Questions
Which bookmakers offer MLB player props on OddsPapi?
US retail books carry the props: DraftKings, FanDuel, Caesars, William Hill and BetMGM. On a marquee game DraftKings and Caesars each priced around 39 prop markets. Sharps like Pinnacle and prediction markets like Kalshi and Polymarket price the game lines (moneyline, run line, totals), not individual player props.
Why is my prop market coming back empty?
Almost always the players["0"] gotcha. On player props the players dict is keyed by player ID, not "0". Iterate the dict and skip the "0" entry. If a whole book is missing, check that every slug in your bookmakers filter is actually on the fixture, since one absent slug can empty the response.
Does the API return American odds for props?
Yes. Every price ships pre-converted: price is decimal, priceAmerican is the American string, and priceFractional is fractional. No converter to write.
Can I get historical MLB prop odds for backtesting?
Yes, on the free tier. Call /historical-odds with up to three bookmakers per request. The response nests a list of timestamped price snapshots under each player, giving you opening-to-closing movement for every prop.
How current are the prop prices?
Live. Each outcome carries a changedAt timestamp and an active flag. Filter on active so suspended or stale lines never enter your line-shopping logic.
Stop Scraping Prop Pages
Home runs, strikeouts, total bases and the rest of the MLB board, as clean JSON from 350+ books, with free historical data to backtest against. Get your free API key and pull your first prop in the next five minutes.