The Unofficial Python Wrapper for the MLB Stats API
This package and its authors are not affiliated with MLB or any MLB team. This API wrapper interfaces with MLB's Stats API. Use of MLB data is subject to the notice posted at http://gdx.mlb.com/components/copyright.txt.
Python-mlb-statsapi is a Python library that provides access to the MLB Stats API, allowing developers to retrieve information related to MLB teams, players, stats, and more. Written in Python 3.10+.
All models are built with Pydantic for robust data validation and serialization. Field names follow Python's snake_case convention for a more Pythonic experience.
For detailed documentation, check out the Wiki which contains information on return objects, endpoint structure, usage examples, and more.
python3 -m pip install python-mlb-statsapi>>> import mlbstatsapi
>>> mlb = mlbstatsapi.Mlb()
>>> mlb.get_people_id("Ty France")
[664034]
>>> player = mlb.get_person(664034)
>>> print(player.full_name)
Ty France
>>> stats = ['season', 'seasonAdvanced']
>>> groups = ['hitting']
>>> params = {'season': 2022}
>>> mlb.get_player_stats(664034, stats, groups, **params)
{'hitting': {'season': Stat, 'seasonAdvanced': Stat }}
>>> mlb.get_team_id("Seattle Mariners")
[136]
>>> team = mlb.get_team(136)
>>> print(team.name, team.franchise_name)
Seattle Mariners SeattleVersion 0.8.0 added shared HTTP Sessions, explicit timeouts, optional Session injection, bounded retries, and structured transport exceptions. Version 0.9.0 builds on that transport with configurable HTTP behavior: a public retry policy, richer MlbHttpError context, an optional strict mode, compatibility warnings, and a versioned User-Agent.
The Mlb client remains synchronous. Shared Sessions pool reusable connections; they do not cache MLB response bodies, and the client does not enable response caching by default.
For the complete reference see the HTTP transport documentation. For what changed in this release see the 0.9.0 release notes.
Prefer a context manager so library-owned HTTP resources are closed when the block exits, including when the block exits because of an exception:
import mlbstatsapi
with mlbstatsapi.Mlb() as mlb:
player = mlb.get_person(664034)
team = mlb.get_team(136)One Mlb client uses one shared requests.Session. The v1 and v1.1 adapters share that Session, so repeated requests can reuse pooled connections. A Session manages a pool of reusable connections; it is not one permanent network connection.
Callers who do not use a context manager may call mlb.close() instead. Repeated close() calls are safe. Closing a client only closes a Session the library created; a caller-injected Session is left open for its owner.
Existing construction continues to work unchanged:
import mlbstatsapi
with mlbstatsapi.Mlb() as mlb:
player = mlb.get_person(664034)That is equivalent to:
mlb = mlbstatsapi.Mlb(
strict_http=False,
)Compatibility mode remains the default in version 0.9.0. A final non-404 4xx response still returns the historical empty result instead of raising, so existing applications keep working after upgrading.
Applications that would rather fail loudly can opt in to strict mode:
import mlbstatsapi
with mlbstatsapi.Mlb(
strict_http=True,
) as mlb:
player = mlb.get_person(664034)In strict mode:
- A final non-404 4xx response raises
MlbHttpError - A final 5xx response raises
MlbHttpError, as it already did in compatibility mode - A 404 keeps the existing endpoint-specific behavior and does not raise
"Final" means after the bounded retry policy has been exhausted. Strict mode is opt-in; it is not the default.
When compatibility mode converts a final non-404 4xx response into the historical empty result, the library emits MlbHttpCompatibilityWarning. The warning marks a response that strict mode would have raised on, so it doubles as migration guidance.
The category inherits from FutureWarning, so it stays visible under default Python warning filters. Applications can promote only this package category to an error:
import warnings
import mlbstatsapi
warnings.filterwarnings(
"error",
category=mlbstatsapi.MlbHttpCompatibilityWarning,
)Filter on mlbstatsapi.MlbHttpCompatibilityWarning specifically rather than disabling all warnings or all FutureWarning instances, which would also hide unrelated notices from other libraries. No warning is emitted for successful responses, 404 responses, intermediate retries, final 5xx responses, or in strict mode.
Every request uses an explicit timeout. The defaults are:
Connection timeout: 3.05 seconds
Read timeout: 30 seconds
The read timeout is the maximum wait while reading response data. It is not one absolute total duration for the complete request.
Use a scalar to apply the same value to both connect and read phases:
import mlbstatsapi
with mlbstatsapi.Mlb(timeout=10) as mlb:
player = mlb.get_person(664034)Or provide separate connection and read timeouts:
import mlbstatsapi
with mlbstatsapi.Mlb(
timeout=(5.0, 60.0),
) as mlb:
player = mlb.get_person(664034)5.0 seconds: connection timeout
60.0 seconds: read timeout
Advanced callers may inject a caller-owned Session:
import requests
import mlbstatsapi
session = requests.Session()
session.headers.update({
"User-Agent": "my-baseball-project/1.0",
})
try:
with mlbstatsapi.Mlb(session=session) as mlb:
player = mlb.get_person(664034)
finally:
session.close()Ownership rules:
Library-created Session
The library configures and closes it
Caller-injected Session
The caller configures and closes it
Mlb.close() does not close a caller-injected Session, and exiting with Mlb(session=session) does not close the injected Session either. The library does not replace or reconfigure adapters or headers on an injected Session. Callers control custom retry, TLS, proxy, header, and adapter configuration.
create_retry_policy() is public in version 0.9.0. It returns a new instance of the same tested policy the library mounts on Sessions it creates, so a caller-managed Session can opt in to identical retry behavior:
import requests
import mlbstatsapi
session = requests.Session()
adapter = requests.adapters.HTTPAdapter(
max_retries=mlbstatsapi.create_retry_policy(),
)
session.mount("https://", adapter)
session.mount("http://", adapter)
try:
with mlbstatsapi.Mlb(session=session) as mlb:
player = mlb.get_person(664034)
finally:
session.close()- The caller mounts the adapters
- The caller closes the injected Session
- The library never reconfigures an injected Session
A Session created by the library sends a package-specific User-Agent:
python-mlb-statsapi/<installed-version>
For this release that resolves to python-mlb-statsapi/0.9.0. The version is read from the installed distribution metadata, so it always matches the installed release. Only the User-Agent header is set; other Requests defaults such as Accept-Encoding remain intact, and the header carries no identifiers beyond the package name and version.
Headers on a caller-injected Session are left untouched, so applications that set their own User-Agent keep it.
import mlbstatsapi
try:
with mlbstatsapi.Mlb() as mlb:
player = mlb.get_person(664034)
except mlbstatsapi.MlbTimeoutError:
print("The MLB API timed out")
except mlbstatsapi.MlbTransportError:
print("The request could not reach the MLB API")
except mlbstatsapi.MlbHttpError as exc:
print(exc.method)
print(exc.status_code)
print(exc.reason)
print(exc.url)
print(exc.response_data)
print(exc.body_excerpt)
except mlbstatsapi.MlbDecodeError:
print("The MLB API returned invalid JSON")MlbTimeoutErrorrepresents connection and read timeoutsMlbTransportErrorrepresents other request transport failuresMlbHttpErrorrepresents an unexpected final HTTP responseMlbDecodeErrorrepresents invalid JSON in a successful response
Version 0.9.0 adds method, response_data, and body_excerpt to MlbHttpError alongside the existing status_code, reason, and url. response_data holds the decoded JSON dictionary or list when the error body contains one, and is None otherwise. body_excerpt is a bounded excerpt of the response text, capped at 500 characters. Complete response bodies are never automatically logged, and str(exc) stays concise.
All new transport exceptions inherit from TheMlbStatsApiException, so existing broad exception handling remains compatible:
import mlbstatsapi
try:
with mlbstatsapi.Mlb() as mlb:
player = mlb.get_person(664034)
except mlbstatsapi.TheMlbStatsApiException:
print("The MLB request failed")Library-created Sessions automatically retry temporary GET failures for:
429
500
502
503
504
Initial request: 1
Maximum retries: 3
Maximum total attempts: 4
Backoff factor: 0.5
Retry-After respected: yes
Only GET requests are retried, and retries are bounded. Ordinary client errors such as 400, 401, 403, and 404 are not retried. Invalid JSON and Pydantic validation failures are not retried. Retries improve resilience for transient failures, but they do not guarantee success. The retry values are unchanged from version 0.8.0.
Version 0.9.0 preserves existing endpoint-specific not-found behavior in both compatibility mode and strict mode. Depending on the endpoint, a 404 may still produce:
None
[]
{}
Not every 404 raises MlbHttpError, and strict mode does not change that.
| Final response | Compatibility mode (default) | Strict mode |
|---|---|---|
| Successful 2xx | Normal result | Normal result |
| Non-404 4xx | Warning and historical empty result | MlbHttpError |
| 404 | Existing endpoint behavior | Existing endpoint behavior |
| Final 429 | Warning and historical empty result | MlbHttpError |
| Final 5xx | MlbHttpError |
MlbHttpError |
See the HTTP transport documentation for the complete retry policy, Session ownership rules, warning behavior, cleanup behavior, and exception hierarchy, and the 0.9.0 release notes for the release summary and migration guidance.
All returned objects are Pydantic models, giving you access to powerful serialization and validation features.
>>> player = mlb.get_person(664034)
>>> player.model_dump()
{'id': 664034, 'full_name': 'Ty France', 'link': '/api/v1/people/664034', ...}
# Exclude None values
>>> player.model_dump(exclude_none=True)
{'id': 664034, 'full_name': 'Ty France', 'link': '/api/v1/people/664034', ...}
# Include only specific fields
>>> player.model_dump(include={'id', 'full_name', 'primary_position'})
{'id': 664034, 'full_name': 'Ty France', 'primary_position': Position(...)}>>> player = mlb.get_person(664034)
>>> player.model_dump_json()
'{"id": 664034, "full_name": "Ty France", "link": "/api/v1/people/664034", ...}'
# Pretty print with indentation
>>> print(player.model_dump_json(indent=2))
{
"id": 664034,
"full_name": "Ty France",
"link": "/api/v1/people/664034",
...
}>>> player = mlb.get_person(664034)
>>> player.full_name # Not fullName
'Ty France'
>>> player.primary_position # Not primaryPosition
Position(code='3', name='First Base', ...)
>>> player.bat_side # Not batSide
CodeDesc(code='R', description='Right')Mlb.get_people_id(self, fullname: str, sport_id: int = 1, search_key: str = 'fullname', **params)- Return Person Id(s) from fullnameMlb.get_person(self, player_id: int, **params)- Return Person Object from IdMlb.get_people(self, sport_id: int = 1, **params)- Return all Players from Sport
Mlb.get_draft(self, year_id: int, **params)- Return a draft for a given year
Mlb.get_awards(self, award_id: int, **params)- Return award recipients for a given award
Mlb.get_team_id(self, team_name: str, search_key: str = 'name', **params)- Return Team Id(s) from nameMlb.get_team(self, team_id: int, **params)- Return Team Object from Team IdMlb.get_teams(self, sport_id: int = 1, **params)- Return all Teams for SportMlb.get_team_coaches(self, team_id: int, **params)- Return coaching roster for team for current or specified seasonMlb.get_team_roster(self, team_id: int, **params)- Return player roster for team for current or specified season
Mlb.get_player_stats(self, person_id: int, stats: list, groups: list, **params)- Return stats by player id, stat type and groupsMlb.get_team_stats(self, team_id: int, stats: list, groups: list, **params)- Return stats by team id, stat types and groupsMlb.get_stats(self, stats: list, groups: list, **params: dict)- Return stats by stat type and group argsMlb.get_players_stats_for_game(self, person_id: int, game_id: int, **params)- Return player stats for a game
Mlb.get_gamepace(self, season: str, sport_id=1, **params)- Return pace of game metrics for specific sport, league or team.
Mlb.get_venue_id(self, venue_name: str, search_key: str = 'name', **params)- Return Venue Id(s)Mlb.get_venue(self, venue_id: int, **params)- Return Venue Object from venue IdMlb.get_venues(self, **params)- Return all Venues
Mlb.get_sport(self, sport_id: int, **params)- Return a Sport object from IdMlb.get_sports(self, **params)- Return all SportsMlb.get_sport_id(self, sport_name: str, search_key: str = 'name', **params)- Return Sport Id from name
Mlb.get_schedule(self, date: str, start_date: str, end_date: str, sport_id: int, team_id: int, **params)- Return a Schedule
Mlb.get_division(self, division_id: int, **params)- Return a DivisionMlb.get_divisions(self, **params)- Return all DivisionsMlb.get_division_id(self, division_name: str, search_key: str = 'name', **params)- Return Division Id(s) from name
Mlb.get_league(self, league_id: int, **params)- Return a League from IdMlb.get_leagues(self, **params)- Return all LeaguesMlb.get_league_id(self, league_name: str, search_key: str = 'name', **params)- Return League Id(s)
Mlb.get_season(self, season_id: str, sport_id: int = None, **params)- Return a seasonMlb.get_seasons(self, sportid: int = None, **params)- Return all seasons
Mlb.get_standings(self, league_id: int, season: str, **params)- Return standings
Mlb.get_schedule(self, date: str = None, start_date: str = None, end_date: str = None, sport_id: int = 1, team_id: int = None, **params)- Return a Schedule from datesMlb.get_scheduled_games_by_date(self, date: str = None,start_date: str = None, end_date: str = None, sport_id: int = 1, **params)- Return game ids from dates
Mlb.get_game(self, game_id: int, **params)- Return the Game for a specific Game IdMlb.get_game_play_by_play(self, game_id: int, **params)- Return Play by play data for a gameMlb.get_game_line_score(self, game_id: int, **params)- Return a Linescore for a gameMlb.get_game_box_score(self, game_id: int, **params)- Return a Boxscore for a game
Contributions are welcome! Whether it's bug fixes, new features, or documentation improvements, we appreciate your help.
- Fork the repository
- Clone your fork:
git clone https://github.com/YOUR_USERNAME/python-mlb-statsapi.git - Install dependencies:
poetry install - Create a branch:
git checkout -b feat/your-feature
Offline tests are deterministic and should run before every pull request:
poetry run pytest \
tests/ \
--ignore=tests/external_testsExternal tests contact the live MLB API. They require internet access and are separate from normal offline CI:
poetry run pytest \
tests/external_tests/These live tests may fail because the MLB service is unavailable or because MLB changes undocumented payloads.
Full local validation:
poetry run pytest tests/
rm -rf dist
poetry build
python3 scripts/validate_release.pyscripts/validate_release.py is the same release check offline CI runs. It inspects the built wheel and source distribution, installs the wheel into a temporary virtual environment, and runs a public-import smoke test against the installed package. It never contacts the MLB API.
Offline CI is the normal pull-request gate. External tests are available manually, on a weekly schedule, and before releases.
- Run offline tests before submitting a PR
- Use the PR template when creating your pull request
- Follow the branch naming convention:
feat/- New featuresfix/- Bug fixesdocs/- Documentation updatesrefactor/- Code improvements
Found a bug or have a feature request? Please open an issue with:
- A clear description of the problem or feature
- Steps to reproduce (for bugs)
- Expected vs actual behavior
- Python version and package version
Let's show some examples of getting stat objects from the API. What is baseball without stats, right?
Get the Id(s) of the players you want stats for and set stat types and groups.
>>> mlb = mlbstatsapi.Mlb()
>>> player_id = mlb.get_people_id("Ty France")[0]
>>> stats = ['season', 'career']
>>> groups = ['hitting', 'pitching']
>>> params = {'season': 2022}Use player id with stat types and groups to return a stats dictionary
>>> stat_dict = mlb.get_player_stats(player_id, stats=stats, groups=groups, **params)
>>> season_hitting_stat = stat_dict['hitting']['season']
>>> career_pitching_stat = stat_dict['pitching']['career']Print season hitting stats using Pydantic's model_dump()
>>> for split in season_hitting_stat.splits:
... print(split.stat.model_dump(exclude_none=True))
{'games_played': 140, 'groundouts': 163, 'airouts': 148, 'runs': 65, 'doubles': 27, ...}Or access individual fields directly
>>> for split in season_hitting_stat.splits:
... print(f"Games: {split.stat.games_played}")
... print(f"Home Runs: {split.stat.home_runs}")
... print(f"Batting Avg: {split.stat.avg}")
Games: 140
Home Runs: 20
Batting Avg: .274Get the Team Id(s)
>>> mlb = mlbstatsapi.Mlb()
>>> team_id = mlb.get_team_id('Seattle Mariners')[0]Set the stat types and groups
>>> stats = ['season', 'seasonAdvanced']
>>> groups = ['hitting']
>>> params = {'season': 2022}Use team id and the stat types and groups to return season hitting stats
>>> stats = mlb.get_team_stats(team_id, stats=stats, groups=groups, **params)
>>> season_hitting = stats['hitting']['season']
>>> advanced_hitting = stats['hitting']['seasonAdvanced']Print stats as JSON
>>> for split in season_hitting.splits:
... print(split.stat.model_dump_json(indent=2, exclude_none=True))
{
"games_played": 162,
"groundouts": 1273,
"runs": 690,
"doubles": 229,
...
}>>> player_id = mlb.get_people_id('Ty France')[0]
>>> stats = ['expectedStatistics']
>>> group = ['hitting']
>>> params = {'season': 2022}
>>> stats = mlb.get_player_stats(player_id, stats=stats, groups=group, **params)
>>> expected = stats['hitting']['expectedStatistics']
>>> for split in expected.splits:
... print(f"Expected AVG: {split.stat.avg}")
... print(f"Expected SLG: {split.stat.slg}")
Expected AVG: .259
Expected SLG: .394Get pitcher and batter player Ids
>>> ty_france_id = mlb.get_people_id('Ty France')[0]
>>> shohei_ohtani_id = mlb.get_people_id('Shohei Ohtani')[0]Set stat type, stat groups, and params
>>> stats = ['vsPlayer']
>>> group = ['hitting']
>>> params = {'opposingPlayerId': shohei_ohtani_id, 'season': 2022}Get stats
>>> stats = mlb.get_player_stats(ty_france_id, stats=stats, groups=group, **params)
>>> vs_player = stats['hitting']['vsPlayer']
>>> for split in vs_player.splits:
... print(f"Games: {split.stat.games_played}, Hits: {split.stat.hits}")
Games: 2, Hits: 2>>> ty_france_id = mlb.get_people_id('Ty France')[0]
>>> stats = ['hotColdZones']
>>> hitting_group = ['hitting']
>>> params = {'season': 2022}
>>> hotcoldzones = mlb.get_player_stats(ty_france_id, stats=stats, groups=hitting_group, **params)
>>> zones = hotcoldzones['stats']['hotColdZones']
>>> for split in zones.splits:
... print(f"Stat: {split.stat.name}")
... for zone in split.stat.zones:
... print(f" Zone {zone.zone}: {zone.value}")
Stat: battingAverage
Zone 01: .226
Zone 02: .400
...Get a schedule for a given date
>>> mlb = mlbstatsapi.Mlb()
>>> schedule = mlb.get_schedule(date='2022-10-13')
>>> dates = schedule.dates
>>> for date in dates:
... for game in date.games:
... print(f"Game: {game.game_pk}")
... print(f"Status: {game.status.detailed_state}")
... print(f"Home: {game.teams.home.team.name}")
... print(f"Away: {game.teams.away.team.name}")Get a Game for a given game id
>>> mlb = mlbstatsapi.Mlb()
>>> game = mlb.get_game(662242)Get the weather for a game
>>> weather = game.game_data.weather
>>> print(f"Condition: {weather.condition}")
>>> print(f"Temperature: {weather.temp}")
>>> print(f"Wind: {weather.wind}")Get the current status of a game
>>> linescore = game.live_data.linescore
>>> home_info = game.game_data.teams.home
>>> away_info = game.game_data.teams.away
>>> home_status = linescore.teams.home
>>> away_status = linescore.teams.away
>>> print(f"Home: {home_info.franchise_name} {home_info.club_name}")
>>> print(f" Runs: {home_status.runs}, Hits: {home_status.hits}, Errors: {home_status.errors}")
>>> print(f"Away: {away_info.franchise_name} {away_info.club_name}")
>>> print(f" Runs: {away_status.runs}, Hits: {away_status.hits}, Errors: {away_status.errors}")
>>> print(f"Inning: {linescore.inning_half} {linescore.current_inning_ordinal}")Get play by play, line score, and box score objects
>>> play_by_play = game.live_data.plays
>>> line_score = game.live_data.linescore
>>> box_score = game.live_data.boxscoreGet only the play by play for a given game id
>>> playbyplay = mlb.get_game_play_by_play(662242)Get only the line score for a given game id
>>> linescore = mlb.get_game_line_score(662242)Get only the box score for a given game id
>>> boxscore = mlb.get_game_box_score(662242)Get pace of game metrics for a specific season
>>> mlb = mlbstatsapi.Mlb()
>>> gamepace = mlb.get_gamepace(season=2021)
>>> print(f"Hits per game: {gamepace.sports[0].sport_game_pace.hits_per_game}")Get all Players for a given sport id
>>> mlb = mlbstatsapi.Mlb()
>>> players = mlb.get_people(sport_id=1)
>>> for player in players:
... print(f"{player.id}: {player.full_name}")Get a player id
>>> player_id = mlb.get_people_id("Ty France")
>>> print(player_id[0])
664034Get a Team
>>> mlb = mlbstatsapi.Mlb()
>>> team_id = mlb.get_team_id("Seattle Mariners")[0]
>>> team = mlb.get_team(team_id)
>>> print(f"{team.id}: {team.name}")
>>> print(f"Venue: {team.venue.name}")Get a Player Roster
>>> mlb = mlbstatsapi.Mlb()
>>> players = mlb.get_team_roster(136)
>>> for player in players:
... print(f"#{player.jersey_number} {player.person.full_name}")Get a Coach Roster
>>> mlb = mlbstatsapi.Mlb()
>>> coaches = mlb.get_team_coaches(136)
>>> for coach in coaches:
... print(f"{coach.person.full_name}: {coach.title}")Get a draft for a year
>>> mlb = mlbstatsapi.Mlb()
>>> draft = mlb.get_draft('2019')Get Players from Draft
>>> draftpicks = draft[0].picks
>>> for pick in draftpicks:
... print(f"Round {pick.pick_round}, Pick {pick.pick_number}: {pick.person.full_name}")Get awards for a given award id
>>> mlb = mlbstatsapi.Mlb()
>>> retired_numbers = mlb.get_awards(award_id='RETIREDUNI_108')
>>> for recipient in retired_numbers.awards:
... print(f"{recipient.player.full_name}: {recipient.name} ({recipient.date})")Get a Venue
>>> mlb = mlbstatsapi.Mlb()
>>> venue_id = mlb.get_venue_id('PNC Park')[0]
>>> venue = mlb.get_venue(venue_id)
>>> print(f"{venue.name} - {venue.location.city}, {venue.location.state}")Get a division
>>> mlb = mlbstatsapi.Mlb()
>>> division = mlb.get_division(200)
>>> print(division.name)
American League WestGet a league
>>> mlb = mlbstatsapi.Mlb()
>>> league = mlb.get_league(103)
>>> print(league.name)
American LeagueGet a Season
>>> mlb = mlbstatsapi.Mlb()
>>> season = mlb.get_season(2018)
>>> print(f"Season: {season.season_id}")
>>> print(f"Regular Season: {season.regular_season_start_date} to {season.regular_season_end_date}")Get Standings
>>> mlb = mlbstatsapi.Mlb()
>>> standings = mlb.get_standings(103, 2018)
>>> for record in standings:
... print(f"Division: {record.division.name}")
... for team in record.team_records:
... print(f" {team.team.name}: {team.wins}-{team.losses}")