Skip to main content
Version: 0.0.70

🏟️ Welcome to sportsdataverse-py β€” the cross-sport quickstart

One pip install, every major league. sportsdataverse is a single Python package that speaks to the official, premium native data feeds across the sporting world β€” the same endpoints the leagues use to power their own apps β€” plus the ESPN mirror and pre-built parquet release loaders. Everything comes back as a tidy polars DataFrame, ready to model. πŸš€

This page is your map to the whole package. By the end you'll be able to:

  1. πŸ—ΊοΈ see every datasource available for every league, with links straight to its tutorial and its reference index;
  2. 🧭 predict function names you've never seen β€” sportsdataverse uses one consistent naming contract, so knowing one function tells you the others;
  3. 🍳 cook through ~20 cross-sport recipes that show the breadth in action.

If you've used the R sisters β€” hoopR, wehoop, cfbfastR, baseballr, fastRhockey, oddsapiR β€” the names here will feel like home. Let's take the tour! 😊

πŸ—ΊοΈ 1 Β· The master index β€” every datasource, every league​

Here's the whole package on one page. Each row is a league (or the betting-odds module); each cell tells you which datasource families are wired up. πŸ’³ marks the premium native feeds (the leagues' own APIs / tracking systems / Statcast). Click a league's tutorial for the deep dive, or its reference for the full function index.

LeagueTutorial Β· ReferenceESPN (espn_<lg>_*)Native premium APITracking / analyticsRelease loaders (load_*)
πŸ€ NBAtutorial Β· refβœ…β€”β€”load_nba_pbp, load_nba_team_boxscore
πŸ€ WNBAtutorial Β· refβœ…β€”β€”load_wnba_pbp, load_wnba_player_boxscore
πŸ€ MBB (NCAA M)tutorial Β· refβœ…β€”β€”load_mbb_pbp, load_mbb_team_boxscore
πŸ€ WBB (NCAA W)tutorial Β· refβœ…β€”β€”load_wbb_pbp, load_wbb_team_boxscore
🏈 NFLtutorial Β· refβœ…πŸ’³ nfl_* (api.nfl.com)πŸ’³ Next Gen Stats nfl_ngs_*load_nfl_pbp, load_nfl_player_stats, load_injuries
🏈 CFB (College)tutorial Β· refβœ…yahoo_cfb_*, fox_cfb_*β€”load_cfb_pbp
⚾ MLBtutorial Β· refβœ…πŸ’³ mlb_* (MLB Stats API)πŸ’³ Statcast mlb_statcast_*load_mlb_pbp, load_mlb_team_boxscore
πŸ’ NHLtutorial Β· refβœ…πŸ’³ nhl_* (api-web)πŸ’³ NHL EDGE nhl_edge_*load_nhl_pbp, load_nhl_team_boxscore
πŸ’ PWHL (Women's pro)tutorial Β· refβ€”πŸ’³ pwhl_* (HockeyTech)corsi / shifts / TOIload_pwhl_schedules
πŸ’ AHL (Minor pro)tutorial Β· refβ€”πŸ’³ ahl_* (HockeyTech)corsi / shifts / TOIβ€”
πŸ’ OHL (CHL junior)tutorial Β· refβ€”πŸ’³ ohl_* (HockeyTech)corsi / shifts / TOIβ€”
πŸ’ WHL (CHL junior)tutorial Β· refβ€”πŸ’³ whl_* (HockeyTech)corsi / shifts / TOIβ€”
πŸ’ QMJHL (CHL junior)tutorial Β· refβ€”πŸ’³ qmjhl_* (HockeyTech)corsi / shifts / TOIβ€”
🎲 Betting oddstutorial Β· refβ€”πŸ’³ toa_* (The Odds API)line history / propsβ€”

πŸ’‘ HockeyTech leagues (AHL/OHL/WHL/QMJHL/PWHL) ship public client keys β€” no setup needed. Only the betting-odds module wants a free ODDS_API_KEY.

🧩 The five function styles​

Across all those rows, only five families exist. Learn the shape of each once and you can read any function name in the package:

  1. Live ESPN wrappers β€” espn_<lg>_* (e.g. espn_nba_teams, espn_wbb_scoreboard). The same set exists for every ESPN league: teams, rosters, scoreboards, standings, schedules, play-by-play, box scores. πŸͺž
  2. Native premium API wrappers β€” the league's own feed: nfl_* (api.nfl.com), mlb_* (MLB Stats API), nhl_* (api-web), pwhl_*/ahl_*/ohl_*/whl_*/qmjhl_* (HockeyTech), toa_* (The Odds API). πŸ’³
  3. Tracking / analytics feeds β€” the really premium stuff: mlb_statcast_* (Baseball Savant), nhl_edge_* (player tracking), nfl_ngs_* (Next Gen Stats).
  4. Release / parquet loaders β€” load_<sport>_*() reads a pre-built parquet release (fast, reliable, whole-season-at-once): load_nba_pbp, load_mlb_team_boxscore, load_pwhl_schedules, …
  5. Parser layer β€” parse_* turns a raw native payload into a tidy frame (e.g. parse_mlb_api_standings). Most wrappers parse for you; the parsers are there when you fetch the raw Dict yourself.

The return contract never changes. Every wrapper gives you polars by default; pass return_as_pandas=True for a pandas frame, and on the native APIs pass return_parsed=False for the raw JSON Dict. One contract, every sport. πŸŽ›οΈ

πŸ”Œ Setup​

pip install sportsdataverse
# or
uv add sportsdataverse

Every league is a submodule of the umbrella package, and the headline cross-league wrappers + discovery helpers are re-exported at the top level. Let's import it.

import os
import polars as pl
import sportsdataverse as sdv
import sportsdataverse.odds as odds

# Every league hangs off the top-level package:
[m for m in dir(sdv) if m in
("cfb", "nfl", "nba", "wnba", "mbb", "wbb", "nhl", "mlb", "pwhl",
"ahl", "ohl", "whl", "qmjhl", "odds")]
['cfb', 'mbb', 'mlb', 'nba', 'nfl', 'nhl', 'odds', 'pwhl', 'wbb', 'wnba']

Live endpoints are seasonal and occasionally rate-limited, and the naming-convention loops below fan out many live calls at once β€” so a tiny safe() helper runs every network call defensively. You get the frame when the feed is up, and a friendly one-liner when it isn't β€” never a scary traceback. That keeps this whole page runnable offline or in the off-season. πŸ›Ÿ

def safe(label, thunk):
'''Run a live call; return its result, or print a one-liner and return None.'''
try:
out = thunk()
print(f"βœ… {label}")
return out
except Exception as e: # noqa: BLE001 -- demo resilience
print(f"⏭️ {label}: unavailable right now ({type(e).__name__})")
return None


# Odds is the only module that wants a (free) key β€” guard those cells:
HAS_KEY = bool(os.environ.get("ODDS_API_KEY"))
print("ODDS_API_KEY set:", HAS_KEY,
"β€” odds cells will" + ("" if HAS_KEY else " NOT") + " run live")
ODDS_API_KEY set: False β€” odds cells will NOT run live

🧭 2 Β· The naming-convention superpower​

Here's the centerpiece. sportsdataverse names things so predictably that knowing one function name tells you the others. The same style of data is exactly one rename away across every sport β€” swap the league slug and the call just works. Let's prove it. πŸͺ„

πŸͺž The ESPN families are identical across every league​

espn_<lg>_teams, espn_<lg>_team_roster, espn_<lg>_scoreboard, espn_<lg>_standings exist for every ESPN league. A one-line helper + getattr tours them all and returns the same shape each time.

def teams(league):
'''Knowing one name (espn_<lg>_teams) gives you all of them.'''
return getattr(sdv, f"espn_{league}_teams")()

rows = []
for lg in ["nba", "wnba", "nhl", "mlb"]:
df = safe(f"espn_{lg}_teams", lambda lg=lg: teams(lg))
rows.append({"league": lg.upper(),
"fn": f"espn_{lg}_teams()",
"n_teams": None if df is None else df.height,
"n_cols": None if df is None else df.width})

pl.DataFrame(rows) # same columns, same shape β€” one contract, four leagues
βœ… espn_nba_teams


βœ… espn_wnba_teams
βœ… espn_nhl_teams


βœ… espn_mlb_teams





shape: (4, 4)
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚ league ┆ fn ┆ n_teams ┆ n_cols β”‚
β”‚ --- ┆ --- ┆ --- ┆ --- β”‚
β”‚ str ┆ str ┆ i64 ┆ i64 β”‚
β•žβ•β•β•β•β•β•β•β•β•ͺ═══════════════════β•ͺ═════════β•ͺ════════║
β”‚ NBA ┆ espn_nba_teams() ┆ 30 ┆ 14 β”‚
β”‚ WNBA ┆ espn_wnba_teams() ┆ 15 ┆ 14 β”‚
β”‚ NHL ┆ espn_nhl_teams() ┆ 32 ┆ 14 β”‚
β”‚ MLB ┆ espn_mlb_teams() ┆ 30 ┆ 14 β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”˜

Same trick for the scoreboard and standings families β€” the call is identical, only the slug changes.

def call(family, league, **kw):
'''Generic dispatcher: call("scoreboard", "nhl") -> espn_nhl_scoreboard().'''
return getattr(sdv, f"espn_{league}_{family}")(**kw)

board = safe("espn_nfl_scoreboard", lambda: call("scoreboard", "nfl"))
stand = safe("espn_nba_standings", lambda: call("standings", "nba"))
print("NFL scoreboard rows:", None if board is None else board.height,
"| NBA standings rows:", None if stand is None else getattr(stand, "height", None))
βœ… espn_nfl_scoreboard
βœ… espn_nba_standings
NFL scoreboard rows: 16 | NBA standings rows: 30

πŸ“¦ The loaders follow one pattern too​

load_<sport>_pbp and load_<sport>_team_boxscore read pre-built parquet for every sport β€” same signature (seasons=[...]), same return type. Knowing load_nba_pbp means you already know load_nhl_pbp and load_mlb_pbp.

# A single getattr loop loads play-by-play for four different sports:
season = 2024
for sport in ["nba", "wnba", "nhl"]:
fn = getattr(sdv, f"load_{sport}_pbp")
print(f"load_{sport}_pbp(seasons=[{season}]) -> signature is identical for every sport")
# (we don't pull all of them here β€” that's a lot of parquet; Recipe 3 runs one.)
load_nba_pbp(seasons=[2024]) -> signature is identical for every sport
load_wnba_pbp(seasons=[2024]) -> signature is identical for every sport
load_nhl_pbp(seasons=[2024]) -> signature is identical for every sport

πŸ’ The HockeyTech leagues share one surface​

AHL / OHL / WHL / QMJHL / PWHL all expose <lg>_schedule, <lg>_standings, <lg>_teams, <lg>_team_roster, and most_recent_<lg>_season. Learn one, you learned all five.

import sportsdataverse.hockey.ahl as ahl
import sportsdataverse.hockey.ohl as ohl
import sportsdataverse.hockey.whl as whl
import sportsdataverse.hockey.qmjhl as qmjhl
import sportsdataverse.pwhl as pwhl

HOCKEYTECH = {"ahl": ahl, "ohl": ohl, "whl": whl, "qmjhl": qmjhl, "pwhl": pwhl}

rows = []
for lg, mod in HOCKEYTECH.items():
season = safe(f"most_recent_{lg}_season", getattr(mod, f"most_recent_{lg}_season"))
rows.append({"league": lg.upper(),
"schedule_fn": f"{lg}_schedule()",
"standings_fn": f"{lg}_standings()",
"season": season})
pl.DataFrame(rows)
βœ… most_recent_ahl_season


βœ… most_recent_ohl_season


βœ… most_recent_whl_season


βœ… most_recent_qmjhl_season


βœ… most_recent_pwhl_season





shape: (5, 4)
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚ league ┆ schedule_fn ┆ standings_fn ┆ season β”‚
β”‚ --- ┆ --- ┆ --- ┆ --- β”‚
β”‚ str ┆ str ┆ str ┆ i64 β”‚
β•žβ•β•β•β•β•β•β•β•β•ͺ══════════════════β•ͺ═══════════════════β•ͺ════════║
β”‚ AHL ┆ ahl_schedule() ┆ ahl_standings() ┆ 2026 β”‚
β”‚ OHL ┆ ohl_schedule() ┆ ohl_standings() ┆ 2027 β”‚
β”‚ WHL ┆ whl_schedule() ┆ whl_standings() ┆ 2026 β”‚
β”‚ QMJHL ┆ qmjhl_schedule() ┆ qmjhl_standings() ┆ 2027 β”‚
β”‚ PWHL ┆ pwhl_schedule() ┆ pwhl_standings() ┆ 2027 β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”˜

πŸ”Ž Discovery helpers β€” when you don't know the name yet​

Four top-level helpers let you search the surface instead of guessing:

  • list_functions(league=None, search=..., parsers_only=..., wrappers_only=...) β€” list/search every wrapper.
  • function_count(league=None) β€” how many functions each league exposes.
  • find_team(name, league) β€” fuzzy team lookup (returns the ESPN team dict + id).
  • find_athlete(name, league) β€” fuzzy player lookup.
# What does the package know about "scoreboard"? (grouped by league)
hits = sdv.list_functions(search="scoreboard")
for lg, fns in hits.items():
print(f"{lg:>4}: {', '.join(fns)}")
cfb: espn_cfb_scoreboard, scoreboard_event_parsing, yahoo_cfb_scoreboard
mbb: espn_mbb_scoreboard, scoreboard_event_parsing
mlb: espn_mlb_scoreboard
nba: espn_nba_scoreboard, scoreboard_event_parsing
nfl: espn_nfl_scoreboard, scoreboard_event_parsing
nhl: espn_nhl_scoreboard, nhl_scoreboard, parse_nhl_web_scoreboard, scoreboard_event_parsing
wbb: espn_wbb_scoreboard, scoreboard_event_parsing
wnba: espn_wnba_scoreboard, scoreboard_event_parsing
soccer: espn_soccer_scoreboard
cricket: espn_cricket_scoreboard
epl: espn_epl_scoreboard
laliga: espn_laliga_scoreboard
bundesliga: espn_bundesliga_scoreboard
seriea: espn_seriea_scoreboard
ligue1: espn_ligue1_scoreboard
mls: espn_mls_scoreboard
ligamx: espn_ligamx_scoreboard
ucl: espn_ucl_scoreboard
uel: espn_uel_scoreboard
nwsl: espn_nwsl_scoreboard
wwc: espn_wwc_scoreboard
wc: espn_wc_scoreboard
mch: espn_mch_scoreboard
wch: espn_wch_scoreboard
ufl: espn_ufl_scoreboard
xfl: espn_xfl_scoreboard
cfl: espn_cfl_scoreboard
college_baseball: espn_college_baseball_scoreboard
college_softball: espn_college_softball_scoreboard
# How big is each league's surface?
counts = sdv.function_count()
pl.DataFrame({"league": list(counts.keys()), "n_functions": list(counts.values())}) \
.sort("n_functions", descending=True)
shape: (34, 2)
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚ league ┆ n_functions β”‚
β”‚ --- ┆ --- β”‚
β”‚ str ┆ i64 β”‚
β•žβ•β•β•β•β•β•β•β•β•ͺ═════════════║
β”‚ nhl ┆ 337 β”‚
β”‚ mlb ┆ 270 β”‚
β”‚ nfl ┆ 237 β”‚
β”‚ cfb ┆ 169 β”‚
β”‚ wnba ┆ 162 β”‚
β”‚ … ┆ … β”‚
β”‚ pwhl ┆ 44 β”‚
β”‚ ahl ┆ 14 β”‚
β”‚ ohl ┆ 14 β”‚
β”‚ qmjhl ┆ 14 β”‚
β”‚ whl ┆ 14 β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
# Fuzzy lookups β€” no IDs to memorize:
team = sdv.find_team("Lakers", "nba")
ath = sdv.find_athlete("LeBron", "nba")
print("team ->", None if team is None else f"{team['displayName']} (id={team['id']})")
print("athlete ->", None if ath is None else f"{ath['displayName']} (id={ath['id']})")
team -> Los Angeles Lakers (id=13)
athlete -> LeBron James (id=1966)

🍳 3 Β· Twenty cross-sport recipes​

Now the fun part β€” 20 runnable recipes that show the breadth and the overlap. Every recipe is defensively guarded, so a flaky network or off-season just prints a friendly note instead of erroring. Mix, match, and remix. πŸ‘‡

Recipe 1 β€” Any league's teams πŸͺžβ€‹

teams("<lg>") (our helper from above) hits espn_<lg>_teams for any ESPN league. Here's the WBB team list.

wbb_teams = safe("espn_wbb_teams", lambda: teams("wbb"))
cols = ["team_id", "team_abbreviation", "team_display_name", "team_location"]
(wbb_teams.select([c for c in cols if c in wbb_teams.columns]).head()
if wbb_teams is not None and wbb_teams.height else "WBB teams unavailable right now")
βœ… espn_wbb_teams





shape: (5, 4)
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚ team_id ┆ team_abbreviation ┆ team_display_name ┆ team_location β”‚
β”‚ --- ┆ --- ┆ --- ┆ --- β”‚
β”‚ str ┆ str ┆ str ┆ str β”‚
β•žβ•β•β•β•β•β•β•β•β•β•ͺ═══════════════════β•ͺ════════════════════════════β•ͺ═══════════════════║
β”‚ 2000 ┆ ACU ┆ Abilene Christian Wildcats ┆ Abilene Christian β”‚
β”‚ 2005 ┆ AF ┆ Air Force Falcons ┆ Air Force β”‚
β”‚ 2006 ┆ AKR ┆ Akron Zips ┆ Akron β”‚
β”‚ 2010 ┆ AAMU ┆ Alabama A&M Bulldogs ┆ Alabama A&M β”‚
β”‚ 333 ┆ ALA ┆ Alabama Crimson Tide ┆ Alabama β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

Recipe 2 β€” Any league's scoreboard πŸ“‹β€‹

espn_<lg>_scoreboard() returns today's slate as a tidy frame. Same call for MLB, NBA, NHL β€” just change the slug.

sb = safe("espn_mlb_scoreboard", lambda: sdv.espn_mlb_scoreboard())
(sb.head() if sb is not None and getattr(sb, "height", 0)
else "no MLB games on the board right now")
βœ… espn_mlb_scoreboard





shape: (5, 50)
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚ game_id ┆ uid ┆ date ┆ name ┆ … ┆ away_logo ┆ away_scor ┆ away_winn ┆ away_ran β”‚
β”‚ --- ┆ --- ┆ --- ┆ --- ┆ ┆ --- ┆ e ┆ er ┆ k β”‚
β”‚ str ┆ str ┆ str ┆ str ┆ ┆ str ┆ --- ┆ --- ┆ --- β”‚
β”‚ ┆ ┆ ┆ ┆ ┆ ┆ str ┆ bool ┆ str β”‚
β•žβ•β•β•β•β•β•β•β•β•β•β•β•ͺ═══════════β•ͺ═══════════β•ͺ═══════════β•ͺ═══β•ͺ═══════════β•ͺ═══════════β•ͺ═══════════β•ͺ══════════║
β”‚ 401815776 ┆ s:1~l:10~ ┆ 2026-06-1 ┆ Miami ┆ … ┆ https://a ┆ 2 ┆ false ┆ null β”‚
β”‚ ┆ e:4018157 ┆ 6T22:40Z ┆ Marlins ┆ ┆ .espncdn. ┆ ┆ ┆ β”‚
β”‚ ┆ 76 ┆ ┆ at Philad ┆ ┆ com/i/tea ┆ ┆ ┆ β”‚
β”‚ ┆ ┆ ┆ elphia … ┆ ┆ mlo… ┆ ┆ ┆ β”‚
β”‚ 401815775 ┆ s:1~l:10~ ┆ 2026-06-1 ┆ Kansas ┆ … ┆ https://a ┆ 4 ┆ false ┆ null β”‚
β”‚ ┆ e:4018157 ┆ 6T22:45Z ┆ City ┆ ┆ .espncdn. ┆ ┆ ┆ β”‚
β”‚ ┆ 75 ┆ ┆ Royals at ┆ ┆ com/i/tea ┆ ┆ ┆ β”‚
β”‚ ┆ ┆ ┆ Washingt… ┆ ┆ mlo… ┆ ┆ ┆ β”‚
β”‚ 401815779 ┆ s:1~l:10~ ┆ 2026-06-1 ┆ Toronto ┆ … ┆ https://a ┆ 6 ┆ true ┆ null β”‚
β”‚ ┆ e:4018157 ┆ 6T22:45Z ┆ Blue Jays ┆ ┆ .espncdn. ┆ ┆ ┆ β”‚
β”‚ ┆ 79 ┆ ┆ at Boston ┆ ┆ com/i/tea ┆ ┆ ┆ β”‚
β”‚ ┆ ┆ ┆ Re… ┆ ┆ mlo… ┆ ┆ ┆ β”‚
β”‚ 401815774 ┆ s:1~l:10~ ┆ 2026-06-1 ┆ Chicago ┆ … ┆ https://a ┆ 2 ┆ false ┆ null β”‚
β”‚ ┆ e:4018157 ┆ 6T23:05Z ┆ White Sox ┆ ┆ .espncdn. ┆ ┆ ┆ β”‚
β”‚ ┆ 74 ┆ ┆ at New ┆ ┆ com/i/tea ┆ ┆ ┆ β”‚
β”‚ ┆ ┆ ┆ York … ┆ ┆ mlo… ┆ ┆ ┆ β”‚
β”‚ 401815777 ┆ s:1~l:10~ ┆ 2026-06-1 ┆ New York ┆ … ┆ https://a ┆ 3 ┆ false ┆ null β”‚
β”‚ ┆ e:4018157 ┆ 6T23:10Z ┆ Mets at ┆ ┆ .espncdn. ┆ ┆ ┆ β”‚
β”‚ ┆ 77 ┆ ┆ Cincinnat ┆ ┆ com/i/tea ┆ ┆ ┆ β”‚
β”‚ ┆ ┆ ┆ i Re… ┆ ┆ mlo… ┆ ┆ ┆ β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

Recipe 3 β€” Load any sport's season play-by-play πŸ“¦β€‹

load_<sport>_pbp(seasons=[...]) reads the parquet release. One sport here (WNBA, a smaller season) to keep the download light.

wnba_pbp = safe("load_wnba_pbp([2024])", lambda: sdv.load_wnba_pbp(seasons=[2024]))
print("WNBA 2024 pbp rows:", None if wnba_pbp is None else wnba_pbp.height)
(wnba_pbp.select([c for c in ["game_id", "period_number", "clock_display_value", "text"]
if c in wnba_pbp.columns]).head()
if wnba_pbp is not None and wnba_pbp.height else "pbp unavailable right now")
βœ… load_wnba_pbp([2024])
WNBA 2024 pbp rows: 101501





shape: (5, 4)
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚ game_id ┆ period_number ┆ clock_display_value ┆ text β”‚
β”‚ --- ┆ --- ┆ --- ┆ --- β”‚
β”‚ i32 ┆ i32 ┆ str ┆ str β”‚
β•žβ•β•β•β•β•β•β•β•β•β•β•β•ͺ═══════════════β•ͺ═════════════════════β•ͺ═════════════════════════════════║
β”‚ 401726992 ┆ 1 ┆ 10:00 ┆ Napheesa Collier vs. Jonquel J… β”‚
β”‚ 401726992 ┆ 1 ┆ 9:35 ┆ Napheesa Collier makes 3-foot … β”‚
β”‚ 401726992 ┆ 1 ┆ 9:12 ┆ Sabrina Ionescu misses 24-foot… β”‚
β”‚ 401726992 ┆ 1 ┆ 9:09 ┆ Bridget Carleton defensive reb… β”‚
β”‚ 401726992 ┆ 1 ┆ 8:55 ┆ Betnijah Laney-Hamilton person… β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

Recipe 4 β€” The same box-score shape for two different sports πŸͺžβ€‹

load_<sport>_team_boxscore returns the same kind of frame for basketball and hockey. Load one season of each and compare the shapes.

nba_box = safe("load_nba_team_boxscore([2024])", lambda: sdv.load_nba_team_boxscore(seasons=[2024]))
nhl_box = safe("load_nhl_team_boxscore([2024])", lambda: sdv.load_nhl_team_boxscore(seasons=[2024]))
print("NBA team-box shape:", None if nba_box is None else nba_box.shape)
print("NHL team-box shape:", None if nhl_box is None else nhl_box.shape)
βœ… load_nba_team_boxscore([2024])


βœ… load_nhl_team_boxscore([2024])
NBA team-box shape: (2640, 57)
NHL team-box shape: (2800, 19)

Recipe 5 β€” Standings for several leagues at once πŸ”β€‹

One loop over espn_<lg>_standings tours basketball, hockey, and baseball.

rows = []
for lg in ["nba", "nhl", "mlb"]:
df = safe(f"espn_{lg}_standings", lambda lg=lg: getattr(sdv, f"espn_{lg}_standings")())
rows.append({"league": lg.upper(),
"rows": None if df is None else getattr(df, "height", None),
"cols": None if df is None else getattr(df, "width", None)})
pl.DataFrame(rows)
βœ… espn_nba_standings
βœ… espn_nhl_standings
βœ… espn_mlb_standings





shape: (3, 3)
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”
β”‚ league ┆ rows ┆ cols β”‚
β”‚ --- ┆ --- ┆ --- β”‚
β”‚ str ┆ i64 ┆ i64 β”‚
β•žβ•β•β•β•β•β•β•β•β•ͺ══════β•ͺ══════║
β”‚ NBA ┆ 30 ┆ 31 β”‚
β”‚ NHL ┆ 32 ┆ 35 β”‚
β”‚ MLB ┆ 30 ┆ 46 β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”˜

Recipe 6 β€” Find a team by name πŸ”Žβ€‹

find_team fuzzy-matches across the ESPN leagues and hands back the team dict (with its id, ready to feed into a roster call).

for nm, lg in [("Patriots", "nfl"), ("Yankees", "mlb"), ("Bruins", "nhl"), ("Crimson Tide", "cfb")]:
t = sdv.find_team(nm, lg)
print(f"{lg:>3} {nm:<14} -> {None if t is None else t['displayName']} (id={None if t is None else t['id']})")
nfl Patriots -> New England Patriots (id=17)


mlb Yankees -> New York Yankees (id=10)


nhl Bruins -> Boston Bruins (id=1)


cfb Crimson Tide -> Alabama Crimson Tide (id=333)

Recipe 7 β€” Find an athlete by name πŸƒβ€‹

find_athlete does the same for players β€” great for grabbing an ESPN athlete id without leaving Python.

for nm, lg in [("Caitlin Clark", "wnba"), ("Patrick Mahomes", "nfl"), ("Connor McDavid", "nhl")]:
a = sdv.find_athlete(nm, lg)
print(f"{lg:>4} {nm:<16} -> {None if a is None else a['displayName']} (id={None if a is None else a['id']})")
wnba Caitlin Clark -> None (id=None)


nfl Patrick Mahomes -> Patrick Mahomes (id=3139477)


nhl Connor McDavid -> Connor McDavid (id=3895074)

Recipe 8 β€” A team and its roster, end to end πŸ‘₯​

Chain find_team β†’ espn_<lg>_team_roster: look up an ID by name, then pull the roster. The roster wrapper is parsed to polars by default.

lal = sdv.find_team("Lakers", "nba")
roster = None
if lal is not None:
roster = safe(f"espn_nba_team_roster(team_id={lal['id']})",
lambda: sdv.espn_nba_team_roster(team_id=lal["id"], return_as_pandas=False))
(roster.head() if roster is not None and getattr(roster, "height", 0)
else "roster unavailable right now")
βœ… espn_nba_team_roster(team_id=13)





shape: (5, 68)
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚ id ┆ uid ┆ guid ┆ first_nam ┆ … ┆ birth_pla ┆ hand_type ┆ hand_abbr ┆ hand_disp β”‚
β”‚ --- ┆ --- ┆ --- ┆ e ┆ ┆ ce_state ┆ --- ┆ eviation ┆ lay_value β”‚
β”‚ str ┆ str ┆ str ┆ --- ┆ ┆ --- ┆ str ┆ --- ┆ --- β”‚
β”‚ ┆ ┆ ┆ str ┆ ┆ str ┆ ┆ str ┆ str β”‚
β•žβ•β•β•β•β•β•β•β•β•β•ͺ════════════β•ͺ═══════════β•ͺ═══════════β•ͺ═══β•ͺ═══════════β•ͺ═══════════β•ͺ═══════════β•ͺ═══════════║
β”‚ 4278129 ┆ s:40~l:46~ ┆ 9af41ea8- ┆ Deandre ┆ … ┆ null ┆ null ┆ null ┆ null β”‚
β”‚ ┆ a:4278129 ┆ a24c-025f ┆ ┆ ┆ ┆ ┆ ┆ β”‚
β”‚ ┆ ┆ -a63f-826 ┆ ┆ ┆ ┆ ┆ ┆ β”‚
β”‚ ┆ ┆ 3fb… ┆ ┆ ┆ ┆ ┆ ┆ β”‚
β”‚ 3945274 ┆ s:40~l:46~ ┆ 583794eb- ┆ Luka ┆ … ┆ null ┆ null ┆ null ┆ null β”‚
β”‚ ┆ a:3945274 ┆ 0f38-9bbd ┆ ┆ ┆ ┆ ┆ ┆ β”‚
β”‚ ┆ ┆ -3e25-9dd ┆ ┆ ┆ ┆ ┆ ┆ β”‚
β”‚ ┆ ┆ 33b… ┆ ┆ ┆ ┆ ┆ ┆ β”‚
β”‚ 4066648 ┆ s:40~l:46~ ┆ 40c1bcf6- ┆ Rui ┆ … ┆ null ┆ null ┆ null ┆ null β”‚
β”‚ ┆ a:4066648 ┆ 675b-f217 ┆ ┆ ┆ ┆ ┆ ┆ β”‚
β”‚ ┆ ┆ -f97c-1d6 ┆ ┆ ┆ ┆ ┆ ┆ β”‚
β”‚ ┆ ┆ 280… ┆ ┆ ┆ ┆ ┆ ┆ β”‚
β”‚ 4397077 ┆ s:40~l:46~ ┆ 4cd92ac1- ┆ Jaxson ┆ … ┆ OK ┆ null ┆ null ┆ null β”‚
β”‚ ┆ a:4397077 ┆ 73ce-653d ┆ ┆ ┆ ┆ ┆ ┆ β”‚
β”‚ ┆ ┆ -c3b1-9c6 ┆ ┆ ┆ ┆ ┆ ┆ β”‚
β”‚ ┆ ┆ 8e9… ┆ ┆ ┆ ┆ ┆ ┆ β”‚
β”‚ 4683774 ┆ s:40~l:46~ ┆ 456f71fd- ┆ Bronny ┆ … ┆ OH ┆ null ┆ null ┆ null β”‚
β”‚ ┆ a:4683774 ┆ 2ce5-3f50 ┆ ┆ ┆ ┆ ┆ ┆ β”‚
β”‚ ┆ ┆ -8d0d-f30 ┆ ┆ ┆ ┆ ┆ ┆ β”‚
β”‚ ┆ ┆ c01… ┆ ┆ ┆ ┆ ┆ ┆ β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

Recipe 9 β€” polars β†’ pandas in one keyword πŸΌβ€‹

Every wrapper honors return_as_pandas=True. Same data, different frame β€” handy when the next step (sklearn, statsmodels, seaborn) wants pandas.

teams_pl = safe("espn_wnba_teams (polars)", lambda: sdv.espn_wnba_teams())
teams_pd = safe("espn_wnba_teams (pandas)", lambda: sdv.espn_wnba_teams(return_as_pandas=True))
print("polars:", type(teams_pl).__name__, None if teams_pl is None else teams_pl.shape)
print("pandas:", type(teams_pd).__name__, None if teams_pd is None else teams_pd.shape)
βœ… espn_wnba_teams (polars)
βœ… espn_wnba_teams (pandas)
polars: DataFrame (15, 14)
pandas: DataFrame (15, 14)

Recipe 10 β€” The return_parsed toggle on a native API πŸŽ›οΈβ€‹

Native API wrappers parse to polars by default; return_parsed=False hands back the raw JSON Dict straight from the league feed.

parsed = safe("nhl_standings (parsed)", lambda: sdv.nhl.nhl_standings())
raw = safe("nhl_standings (raw dict)", lambda: sdv.nhl.nhl_standings(return_parsed=False))
print("parsed ->", type(parsed).__name__, None if parsed is None else getattr(parsed, "shape", None))
print("raw ->", type(raw).__name__, "(top-level keys:", None if not isinstance(raw, dict) else list(raw.keys())[:4], ")")
βœ… nhl_standings (parsed)
βœ… nhl_standings (raw dict)
parsed -> DataFrame (32, 84)
raw -> dict (top-level keys: ['wildCardIndicator', 'standingsDateTimeUtc', 'standings'] )

Recipe 11 β€” 🏈 Premium NFL pull (api.nfl.com)​

nfl_standings() hits the league's own API and returns one tidy row per team.

nfl_st = safe("nfl_standings (api.nfl.com)", lambda: sdv.nfl.nfl_standings(season=2024, week=18))
cols = ["team_abbr", "team_full_name", "overall_wins", "overall_losses",
"division_name", "conference_name"]
(nfl_st.select([c for c in cols if c in nfl_st.columns]).head(8)
if nfl_st is not None and getattr(nfl_st, "height", 0) else "NFL standings unavailable right now")
βœ… nfl_standings (api.nfl.com)





shape: (8, 3)
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚ team_full_name ┆ overall_wins ┆ overall_losses β”‚
β”‚ --- ┆ --- ┆ --- β”‚
β”‚ str ┆ i64 ┆ i64 β”‚
β•žβ•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•ͺ══════════════β•ͺ════════════════║
β”‚ Arizona Cardinals ┆ 8 ┆ 9 β”‚
β”‚ Atlanta Falcons ┆ 8 ┆ 9 β”‚
β”‚ Baltimore Ravens ┆ 12 ┆ 5 β”‚
β”‚ Buffalo Bills ┆ 13 ┆ 4 β”‚
β”‚ Carolina Panthers ┆ 5 ┆ 12 β”‚
β”‚ Chicago Bears ┆ 5 ┆ 12 β”‚
β”‚ Cincinnati Bengals ┆ 9 ┆ 8 β”‚
β”‚ Cleveland Browns ┆ 3 ┆ 14 β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

Recipe 12 β€” ⚾ Premium MLB pull (MLB Stats API + parser)​

mlb_* wrappers return the raw Dict; pair them with the matching parse_mlb_api_* for a tidy frame. Here's division standings, parsed.

def mlb_standings():
raw = sdv.mlb.mlb_standings(league_id="103,104", season=2024)
return sdv.mlb.parse_mlb_api_standings(raw)

mlb_st = safe("MLB standings (Stats API + parser)", mlb_standings)
keep = ["standings_division_name", "team_name", "wins", "losses", "winning_percentage", "games_back"]
(mlb_st.select([c for c in keep if c in mlb_st.columns]).head(10)
if mlb_st is not None and getattr(mlb_st, "height", 0) else "MLB standings unavailable right now")
βœ… MLB standings (Stats API + parser)





shape: (10, 6)
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚ standings_division_name ┆ team_name ┆ wins ┆ losses ┆ winning_percentage ┆ games_back β”‚
β”‚ --- ┆ --- ┆ --- ┆ --- ┆ --- ┆ --- β”‚
β”‚ str ┆ str ┆ i64 ┆ i64 ┆ str ┆ str β”‚
β•žβ•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•ͺ═══════════β•ͺ══════β•ͺ════════β•ͺ════════════════════β•ͺ════════════║
β”‚ null ┆ Yankees ┆ 94 ┆ 68 ┆ .580 ┆ - β”‚
β”‚ null ┆ Orioles ┆ 91 ┆ 71 ┆ .562 ┆ 3.0 β”‚
β”‚ null ┆ Red Sox ┆ 81 ┆ 81 ┆ .500 ┆ 13.0 β”‚
β”‚ null ┆ Rays ┆ 80 ┆ 82 ┆ .494 ┆ 14.0 β”‚
β”‚ null ┆ Blue Jays ┆ 74 ┆ 88 ┆ .457 ┆ 20.0 β”‚
β”‚ null ┆ Guardians ┆ 92 ┆ 69 ┆ .571 ┆ - β”‚
β”‚ null ┆ Royals ┆ 86 ┆ 76 ┆ .531 ┆ 6.5 β”‚
β”‚ null ┆ Tigers ┆ 86 ┆ 76 ┆ .531 ┆ 6.5 β”‚
β”‚ null ┆ Twins ┆ 82 ┆ 80 ┆ .506 ┆ 10.5 β”‚
β”‚ null ┆ White Sox ┆ 41 ┆ 121 ┆ .253 ┆ 51.5 β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

Recipe 13 β€” ⚾ MLB Statcast β€” the premium tracking firehose​

mlb_statcast_search() returns one row per pitch β€” the raw Baseball Savant tracking data. Grab a single day and pull a few of the most useful columns.

pitches = safe("mlb_statcast_search (1 day)",
lambda: sdv.mlb.mlb_statcast_search(start_dt="2024-07-01", end_dt="2024-07-01"))
show = [c for c in ["game_date", "player_name", "pitch_type", "release_speed",
"launch_speed", "launch_angle", "events"]
if pitches is not None and c in pitches.columns]
(pitches.select(show).head(10)
if pitches is not None and getattr(pitches, "height", 0) else "no Statcast rows for that day right now")
βœ… mlb_statcast_search (1 day)





shape: (10, 7)
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚ game_date ┆ player_name ┆ pitch_type ┆ release_spee ┆ launch_speed ┆ launch_angle ┆ events β”‚
β”‚ --- ┆ --- ┆ --- ┆ d ┆ --- ┆ --- ┆ --- β”‚
β”‚ str ┆ str ┆ str ┆ --- ┆ f64 ┆ f64 ┆ str β”‚
β”‚ ┆ ┆ ┆ f64 ┆ ┆ ┆ β”‚
β•žβ•β•β•β•β•β•β•β•β•β•β•β•β•ͺ═══════════════β•ͺ════════════β•ͺ══════════════β•ͺ══════════════β•ͺ══════════════β•ͺ═══════════║
β”‚ 2024-07-01 ┆ Alonso, Pete ┆ FF ┆ 94.8 ┆ 78.0 ┆ 46.0 ┆ field_out β”‚
β”‚ 2024-07-01 ┆ Alonso, Pete ┆ FF ┆ 96.6 ┆ null ┆ null ┆ null β”‚
β”‚ 2024-07-01 ┆ Alonso, Pete ┆ FF ┆ 96.3 ┆ 73.3 ┆ 20.0 ┆ null β”‚
β”‚ 2024-07-01 ┆ Alonso, Pete ┆ FF ┆ 97.2 ┆ null ┆ null ┆ null β”‚
β”‚ 2024-07-01 ┆ Alonso, Pete ┆ FF ┆ 95.6 ┆ null ┆ null ┆ null β”‚
β”‚ 2024-07-01 ┆ Alonso, Pete ┆ FF ┆ 95.8 ┆ null ┆ null ┆ null β”‚
β”‚ 2024-07-01 ┆ Varsho, ┆ FF ┆ 97.4 ┆ null ┆ null ┆ strikeout β”‚
β”‚ ┆ Daulton ┆ ┆ ┆ ┆ ┆ β”‚
β”‚ 2024-07-01 ┆ Varsho, ┆ KC ┆ 84.0 ┆ 94.3 ┆ -12.0 ┆ null β”‚
β”‚ ┆ Daulton ┆ ┆ ┆ ┆ ┆ β”‚
β”‚ 2024-07-01 ┆ Martinez, ┆ FF ┆ 97.5 ┆ null ┆ null ┆ strikeout β”‚
β”‚ ┆ J.D. ┆ ┆ ┆ ┆ ┆ β”‚
β”‚ 2024-07-01 ┆ Martinez, ┆ FF ┆ 96.6 ┆ null ┆ null ┆ null β”‚
β”‚ ┆ J.D. ┆ ┆ ┆ ┆ ┆ β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

Recipe 14 β€” πŸ’ Premium NHL pull (api-web)​

nhl_standings() reads the modern NHL api-web feed β€” one row per team, parsed to polars.

nhl_st = safe("nhl_standings (api-web)", lambda: sdv.nhl.nhl_standings())
keep = ["team_abbrev", "team_name", "wins", "losses", "ot_losses", "points",
"conference_name", "division_name"]
(nhl_st.select([c for c in keep if c in nhl_st.columns]).head(8)
if nhl_st is not None and getattr(nhl_st, "height", 0) else "NHL standings unavailable right now")
βœ… nhl_standings (api-web)





shape: (8, 6)
β”Œβ”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚ wins ┆ losses ┆ ot_losses ┆ points ┆ conference_name ┆ division_name β”‚
β”‚ --- ┆ --- ┆ --- ┆ --- ┆ --- ┆ --- β”‚
β”‚ i64 ┆ i64 ┆ i64 ┆ i64 ┆ str ┆ str β”‚
β•žβ•β•β•β•β•β•β•ͺ════════β•ͺ═══════════β•ͺ════════β•ͺ═════════════════β•ͺ═══════════════║
β”‚ 55 ┆ 16 ┆ 11 ┆ 121 ┆ Western ┆ Central β”‚
β”‚ 53 ┆ 22 ┆ 7 ┆ 113 ┆ Eastern ┆ Metropolitan β”‚
β”‚ 50 ┆ 20 ┆ 12 ┆ 112 ┆ Western ┆ Central β”‚
β”‚ 50 ┆ 23 ┆ 9 ┆ 109 ┆ Eastern ┆ Atlantic β”‚
β”‚ 50 ┆ 26 ┆ 6 ┆ 106 ┆ Eastern ┆ Atlantic β”‚
β”‚ 48 ┆ 24 ┆ 10 ┆ 106 ┆ Eastern ┆ Atlantic β”‚
β”‚ 46 ┆ 24 ┆ 12 ┆ 104 ┆ Western ┆ Central β”‚
β”‚ 45 ┆ 27 ┆ 10 ┆ 100 ┆ Eastern ┆ Atlantic β”‚
β””β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

Recipe 15 β€” πŸ’ NHL EDGE tracking leaderboard​

NHL EDGE is the league's player- and puck-tracking system. The nhl_edge_skater_speed_top_10 board surfaces the fastest skating bursts.

edge = safe("nhl_edge_skater_speed_top_10",
lambda: sdv.nhl.nhl_edge_skater_speed_top_10(positions="forwards",
sort_by="maxskatingspeed"))
(edge.head(10) if edge is not None and getattr(edge, "height", 0)
else "NHL EDGE leaderboard unavailable right now")
⏭️ nhl_edge_skater_speed_top_10: unavailable right now (NoESPNDataError)





'NHL EDGE leaderboard unavailable right now'

Recipe 16 β€” πŸ’ Premium PWHL pull (HockeyTech)​

The women's pro league rides the HockeyTech feed. pwhl_standings() returns the table; load_pwhl_schedules() reads the parquet release for a whole season.

pwhl_st = safe("pwhl_standings", lambda: sdv.pwhl.pwhl_standings(season=sdv.pwhl.most_recent_pwhl_season()))
pwhl_sched = safe("load_pwhl_schedules([2024])", lambda: sdv.pwhl.load_pwhl_schedules(seasons=[2024]))
print("standings rows:", None if pwhl_st is None else getattr(pwhl_st, "height", None),
"| schedule rows:", None if pwhl_sched is None else getattr(pwhl_sched, "height", None))
(pwhl_st.head() if pwhl_st is not None and getattr(pwhl_st, "height", 0)
else "PWHL standings unavailable right now")
⏭️ pwhl_standings: unavailable right now (ValueError)


βœ… load_pwhl_schedules([2024])
standings rows: None | schedule rows: 85





'PWHL standings unavailable right now'

Recipe 17 β€” πŸ’ Junior hockey: schedule for all four CHL/AHL loops πŸ”β€‹

Because AHL/OHL/WHL/QMJHL share one surface, a single loop tours every league's schedule.

rows = []
for lg, mod in {"ahl": ahl, "ohl": ohl, "whl": whl, "qmjhl": qmjhl}.items():
season = safe(f"{lg} season", getattr(mod, f"most_recent_{lg}_season"))
sch = (safe(f"{lg}_schedule", lambda mod=mod, lg=lg: getattr(mod, f"{lg}_schedule")())
if season else None)
rows.append({"league": lg.upper(), "season": season,
"games": None if sch is None else getattr(sch, "height", None)})
pl.DataFrame(rows)
βœ… ahl season


βœ… ahl_schedule


βœ… ohl season


βœ… ohl_schedule


βœ… whl season


βœ… whl_schedule


βœ… qmjhl season


βœ… qmjhl_schedule





shape: (4, 3)
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”
β”‚ league ┆ season ┆ games β”‚
β”‚ --- ┆ --- ┆ --- β”‚
β”‚ str ┆ i64 ┆ i64 β”‚
β•žβ•β•β•β•β•β•β•β•β•ͺ════════β•ͺ═══════║
β”‚ AHL ┆ 2026 ┆ 10000 β”‚
β”‚ OHL ┆ 2027 ┆ 10000 β”‚
β”‚ WHL ┆ 2026 ┆ 10000 β”‚
β”‚ QMJHL ┆ 2027 ┆ 10000 β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”˜

Recipe 18 β€” 🎲 A quick odds peek (key-guarded)​

odds.toa_sports() lists every in-season sport/league key β€” it's free (doesn't touch your quota). Set a free ODDS_API_KEY to light it up.

if HAS_KEY:
sports = safe("odds.toa_sports", lambda: odds.toa_sports(all_sports=False))
out = (sports.select([c for c in ["key", "group", "title"] if c in sports.columns]).head(10)
if sports is not None and getattr(sports, "height", 0) else "no in-season sports returned")
else:
out = "set ODDS_API_KEY to run: odds.toa_sports() (free, doesn't touch quota)"
out
"set ODDS_API_KEY to run: odds.toa_sports() (free, doesn't touch quota)"

Recipe 19 β€” 🎲 Live odds for a league (key-guarded)​

odds.toa_sports_odds() returns long-format odds β€” one row per event Γ— book Γ— market Γ— outcome β€” exactly the shape you want for modelling.

if HAS_KEY:
board = safe("odds.toa_sports_odds (NFL h2h)",
lambda: odds.toa_sports_odds(sport="americanfootball_nfl", regions="us", markets="h2h"))
keep = ["home_team", "away_team", "bookmaker_key", "market_key", "outcome_name", "outcome_price"]
out = (board.select([c for c in keep if c in board.columns]).head(10)
if board is not None and getattr(board, "height", 0) else "no NFL odds on the board right now")
else:
out = "set ODDS_API_KEY to run: odds.toa_sports_odds(sport='americanfootball_nfl')"
out
"set ODDS_API_KEY to run: odds.toa_sports_odds(sport='americanfootball_nfl')"

Recipe 20 β€” Count the whole surface, per league πŸ”’β€‹

function_count() returns the exposed-function tally for every league β€” a quick sense of how much each sport gives you. (HockeyTech + odds modules are counted in their own submodules.)

counts = sdv.function_count()
df = (pl.DataFrame({"league": list(counts.keys()), "n_functions": list(counts.values())})
.sort("n_functions", descending=True))
print("Total wrappers across the counted leagues:", sum(counts.values()))
df
Total wrappers across the counted leagues: 4063





shape: (34, 2)
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚ league ┆ n_functions β”‚
β”‚ --- ┆ --- β”‚
β”‚ str ┆ i64 β”‚
β•žβ•β•β•β•β•β•β•β•β•ͺ═════════════║
β”‚ nhl ┆ 337 β”‚
β”‚ mlb ┆ 270 β”‚
β”‚ nfl ┆ 237 β”‚
β”‚ cfb ┆ 169 β”‚
β”‚ wnba ┆ 162 β”‚
β”‚ … ┆ … β”‚
β”‚ pwhl ┆ 44 β”‚
β”‚ ahl ┆ 14 β”‚
β”‚ ohl ┆ 14 β”‚
β”‚ qmjhl ┆ 14 β”‚
β”‚ whl ┆ 14 β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

πŸŽ‰ Where to next​

You've now seen the whole map β€” every datasource, the naming contract that makes the package guessable, and 20 recipes spanning ten-plus leagues. Each sport has a dedicated tutorial that leads with its premium endpoints:

Reference indexes: NBA Β· WNBA Β· MBB Β· WBB Β· NFL Β· CFB Β· MLB Β· NHL Β· PWHL Β· AHL Β· OHL Β· WHL Β· QMJHL Β· Odds.

Part of the SportsDataverse β€” the names here mirror the R sisters (hoopR, wehoop, cfbfastR, baseballr, fastRhockey, oddsapiR). Now go build something great! πŸ†