MLB Stats API: Free Endpoints for Schedules, Live Games and Player Stats
MLB's own JSON feed, no key required. We called every endpoint below in November 2026.
Last updated:
What is the MLB Stats API?
The MLB Stats API is the JSON backend that MLB Advanced Media runs for MLB's own apps, and developers also search for it as the MLB Gameday API. It's free, it needs no API key, and it isn't documented for outside developers: MLB publishes no public docs, and the root of statsapi.mlb.com serves a sign-in page titled "Please Login". The responses carry a copyright line that points at MLB's usage notice, covered under terms.
We called every endpoint on this page on 2 November 2026 with the default headers of curl, Python requests and Node fetch. The examples use Game 5 of the 2026 World Series: Brewers 4, Rays 1, gamePk 849799. The feed has no betting lines; for prices on the same games, see MLB odds APIs. The baseball/mlb scoreboard of the ESPN API lists the same games under other ids: ESPN calls this game 401908046.
Base URLs
Two hosts carry the data, and a third holds the usage notice.
| Host | What it serves |
|---|---|
https://statsapi.mlb.com/api/v1 | Schedule, teams, rosters, standings, people, stats, box scores and line scores |
https://statsapi.mlb.com/api/v1.1 | The live game feed, /game/{gamePk}/feed/live. The same path under v1 returned 404 |
https://baseballsavant.mlb.com | Statcast pitch data as CSV (see below) |
http://gdx.mlb.com/components/copyright.txt | The usage notice that every response links to |
What It Covers
/api/v1/sports returned 20 levels of baseball, and you pass the id as sportId:
- MLB:
sportId=1, 30 teams - Minor leagues: 11 Triple-A, 12 Double-A, 13 High-A, 14 Single-A, 16 Rookie
- Elsewhere: 31 Nippon Professional Baseball, 32 Korean Baseball Organization, 17 Winter Leagues, 22 College Baseball, 51 International Baseball
We tested MLB and Triple-A. /api/v1/teams?sportId=11 returned 30 clubs, and the feed for Rochester 15, Buffalo 8 on 20 September 2026 (gamePk 816903) held 96 plays and 378 pitches with velocity. The season list at /api/v1/seasons/all?sportId=1 runs from 1876 to 2027; we did not test how far back the game feeds go. For decades of play-by-play in flat files, Retrosheet and the Lahman database are on our free sports datasets page.
Key Endpoints
Every path sits under https://statsapi.mlb.com, and each one returned HTTP 200 in our test.
| Endpoint | What it returns | Example |
|---|---|---|
/api/v1/schedule | Games for a day or a date range, each with a gamePk, teams, score and status | /api/v1/schedule?sportId=1&date=2026-10-28 |
/api/v1/teams | Every team at one level, with league, division and venue | /api/v1/teams?sportId=1 |
/api/v1/teams/{teamId}/roster | The active roster: 20 Brewers on 2 November, in the offseason. rosterType=40Man gave 33 | /api/v1/teams/158/roster |
/api/v1/standings | One record per division: wins, losses, games back, streak | /api/v1/standings?leagueId=103,104&season=2026 |
/api/v1/people/{personId} | Name, position, bats and throws, debut date | /api/v1/people/660271 (Shohei Ohtani) |
/api/v1/people/{personId}/stats | A stat line; stats is required | /api/v1/people/660271/stats?stats=season&season=2026&group=hitting |
/api/v1.1/game/{gamePk}/feed/live | The whole game (see below) | /api/v1.1/game/849799/feed/live |
/api/v1/game/{gamePk}/boxscore | Box score only, 174 KB for our test game | /api/v1/game/849799/boxscore |
/api/v1/game/{gamePk}/linescore | Runs, hits and errors by inning, 3.7 KB | /api/v1/game/849799/linescore |
/api/v1/sports | The 20 levels and their sportId | /api/v1/sports |
/api/v1/seasons | Key dates of the current season | /api/v1/seasons?sportId=1 |
Watch the types. Rate stats come back as strings: Ohtani's 2026 line had avg ".275" and ops ".889" next to the integer homeRuns 30. Standings give the short team name ("Rays") and a division id with no name, so match the id against /api/v1/teams, where 201 is the American League East.
The Live Game Feed (feed/live)
The live feed is the endpoint developers look for most. One call returns the whole game: players, weather, every plate appearance, every pitch with speed and spin, the box score and the line score.
https://statsapi.mlb.com/api/v1.1/game/{gamePk}/feed/live
The gamePk comes from the schedule, where each game also carries a link field that already points at its feed. Our World Series Game 5 response was 764 KB with 70 plays and 255 pitches, and a Triple-A game ran to 993 KB, so cache what you fetch.
| Field | Type | What it holds |
|---|---|---|
gameData.datetime.officialDate | string | The game's date, "2026-10-28". Group games by it: the UTC gameDate of this night game read 29 October |
gameData.status.detailedState | string | "Final" here. "In Progress" and "Postponed" also appeared in our tests |
gameData.players | object | 52 players, keyed "ID" plus the person id (ID694192) |
gameData.absChallenges | object | Ball-strike challenges: each team had 1 successful challenge and 2 left |
liveData.plays.allPlays | array | One entry per plate appearance: result.event, result.description, about.inning, matchup.batter |
liveData.plays.scoringPlays | array | Indexes into allPlays: [10, 33, 40, 43, 68] |
allPlays[].playEvents[].pitchData | object | startSpeed (95.8 on the first pitch), breaks.spinRate (2341), zone |
allPlays[].playEvents[].hitData | object | launchSpeed, launchAngle and trajectory on balls in play |
liveData.linescore | object | Innings and totals, the same object as the linescore endpoint |
liveData.boxscore | object | Team and player stats; the boxscore endpoint adds fuller team objects |
liveData.decisions | object | Winning and losing pitcher: Jacob Misiorowski, Drew Rasmussen |
metaData | object | wait (10) and timeStamp, the last update in UTC ("20261029_031311") |
Replay a game at any moment
/api/v1.1/game/849799/feed/live/timestamps returned 445 timecodes. Pass one back as ?timecode= and the feed shows the game as it stood then: timecode=20261029_013436 gave 36 plays, top of the 5th, Brewers up 2-0, status "In Progress".
Parameters and Date Filters
The schedule accepts a date range, which ESPN's scoreboard does not. startDate=2026-10-01&endDate=2026-10-31 returned all 33 October postseason games on 22 dates; ESPN's dates=20261001-20261031 returned HTTP 400.
| Parameter | What it does | Example |
|---|---|---|
date | One day. YYYY-MM-DD and MM/DD/YYYY both worked; YYYYMMDD returned 400 | date=2026-10-28 |
startDate, endDate | A range, in the same format | startDate=2026-10-01&endDate=2026-10-31 |
sportId | The level, 1 for MLB. The schedule returns 400 without it | sportId=11 |
gameType | R regular season, F Wild Card, D Division Series, L League Championship Series, W World Series | season=2026&gameType=W (5 games) |
teamId | One team's games | teamId=158 (13 Brewers games in October) |
hydrate | Adds linked objects to each game or person | hydrate=probablePitcher,linescore |
fields | Keeps only the keys you name | fields=dates,date,games,gamePk,description |
hydrate=probablePitcher,linescore grew the 28 October schedule from 1.7 KB to 5.5 KB. On /api/v1/people/660271, hydrate=currentTeam added the Dodgers (id 119), which the plain call leaves out. A misspelled hydrate=notAField returned 200 and the plain response, with no warning. fields cut the October range from 46.9 KB to 2.4 KB.
A postponed game keeps its gamePk: ALCS Game 4 (849817) is listed on 16 October as "Postponed" and on 18 October as "Final".
Code Examples
Scoring plays from the live feed, in Python
import requests
# 2026 World Series, Game 5: Brewers at Rays
game_pk = 849799
url = f'https://statsapi.mlb.com/api/v1.1/game/{game_pk}/feed/live'
feed = requests.get(url, timeout=30).json()
plays = feed['liveData']['plays']
print(feed['gameData']['status']['detailedState'], len(plays['allPlays']), 'plays')
for i in plays['scoringPlays']:
play = plays['allPlays'][i]
about, result = play['about'], play['result']
print(about['inning'], about['halfInning'], result['event'], f"{result['awayScore']}-{result['homeScore']}")
It printed Final 70 plays, then five lines from 2 top Single 1-0 to 9 bottom Double 4-1.
One day's schedule with starters and line scores, in cURL
curl 'https://statsapi.mlb.com/api/v1/schedule?sportId=1&date=2026-10-28&hydrate=probablePitcher,linescore'
Division leaders, in JavaScript
const url = 'https://statsapi.mlb.com/api/v1/standings?leagueId=103,104&season=2026';
const data = await fetch(url).then(r => r.json());
for (const record of data.records) {
const leader = record.teamRecords[0];
console.log(record.division.id, leader.team.name, `${leader.wins}-${leader.losses}`);
}
We ran it as a .mjs file with Node 22. It printed six lines, from 201 Rays 98-64 to 203 Dodgers 100-62. The responses carry access-control-allow-origin: *, the header a browser needs before a web page on another host can read them.
Statcast CSV from Baseball Savant
For pitch-level data in one flat file, Baseball Savant, an MLB site, exports Statcast searches as CSV. This call returned the 255 pitches of World Series Game 5, one row each, with 119 columns:
https://baseballsavant.mlb.com/statcast_search/csv?all=true&hfGT=W%7C&player_type=batter&game_date_gt=2026-10-28&game_date_lt=2026-10-28&type=details
The columns include game_pk (the same 849799), pitch_name, release_speed, launch_speed, bat_speed and delta_home_win_exp. The hardest-hit ball was William Contreras at a launch_speed of 108.5. hfGT picks the game type: hfGT=R%7C for 30 and 31 October returned only the header row, because no regular-season games were played on those dates. The file starts with a byte order mark, so open it as utf-8-sig in Python.
Errors You Will See
400 Bad Request
A bad request gets a JSON body with a messageNumber and a message. The ones we caused:
- No
sportId:/api/v1/schedule?date=2026-10-28gave message 7, "Missing required parameter sportId or gamePk" - A bad date:
date=20261028,date=2026-13-45anddate=yesterdayeach gave message 11, "Invalid Request with value: 20261028" - A text gamePk:
/api/v1.1/game/abc/feed/livealso gave message 11 - No
statson the stats endpoint: message 5, "Required request parameter 'stats' for method parameter type List is not present"
404 Not Found
An unknown person id (/api/v1/people/1) returned message 10, "Object not found". A wrong path returns a different body, {"error":"Not Found","status":404} with the path. We got it for the feed under v1: /api/v1/game/849799/feed/live is a 404, while v1.1 works.
200 with an empty game
A numeric gamePk that matches no game still returns HTTP 200. /api/v1.1/game/999999999/feed/live gave a 1.3 KB skeleton with gamePk 0, detailedState "Unknown" and an empty allPlays, and the boxscore endpoint did the same. Check gameData.status.detailedState before you trust a feed.
An empty dates array on the schedule means no games that day: 31 October 2026 had none, because the World Series ended on the 28th.
Limitations and Terms
Personal, non-commercial use only
The copyright line in the responses links to MLB's notice at gdx.mlb.com/components/copyright.txt. It says: "Only individual, non-commercial, non-bulk use of the Materials is permitted." Any other use needs prior written permission from MLB Advanced Media.
In plain terms, the notice covers one person's non-commercial use, such as a personal project. It does not cover a commercial app or bulk downloads. MLB.com's Terms of Use go further: clause (xi) of their conduct rules bars automated scripts that collect information from MLB's digital properties. Read both before you build on this feed.
- No public docs: endpoints can change without notice. The community wiki is the nearest reference.
- No published rate limit: no response carried a rate-limit header, and 20 back-to-back requests all returned 200. Responses came from a cache with
max-age=10on the feed and 20 on the schedule, so polling faster than every 10 seconds gains little. - No support or uptime promise: MLB offers none for outside use.
- No odds: the feed has no betting lines. MLB odds APIs cover that.
When your app earns money, serves many users or needs an uptime promise, move to licensed sports data providers. They sell MLB data under a commercial license, with support.
Community Resources
MLB has no public docs, so these community projects are the best map of the API. We fetched each one in November 2026.
- toddrob99/MLB-StatsAPI on GitHub: a Python wrapper for this API, GPL-3.0, and not affiliated with MLB.
- The wrapper's Endpoints wiki page: 64 endpoints with their required and optional parameters. We tested only the ones on this page.
- MLB-StatsAPI on PyPI:
pip install MLB-StatsAPI, version 1.9.0 from April 2025.
Frequently Asked Questions
statsapi.mlb.com is a sign-in page, and MLB promises nothing about stability. The best endpoint list is a community wiki on GitHub.requests and Node fetch with no key, token or cookie, and every endpoint on this page returned 200. The responses also carry access-control-allow-origin: *, so browser code can read them./api/v1/sports: Triple-A (sportId 11) down to Rookie ball (16), Winter Leagues, college and high school baseball, international play, and Japan's NPB (31) and Korea's KBO (32). We tested MLB and Triple-A, and a Triple-A feed from September 2026 had full pitch data.max-age=10 on the live feed and 20 on the schedule, so polling the feed faster than every 10 seconds gains little. Cache what you fetch, and stay clear of bulk use, which the notice forbids.https://statsapi.mlb.com/api/v1/schedule?sportId=1&date=YYYY-MM-DD and read dates[0].games. Each game has a gamePk, both teams, the score and status.detailedState. Add hydrate=probablePitcher,linescore for the starters and inning scores. In the offseason dates is empty because no games are on.https://statsapi.mlb.com/api/v1.1/game/{gamePk}/feed/live. liveData.plays.allPlays holds every plate appearance, and each one's playEvents hold the pitches with speed and spin. Our World Series test game had 70 plays and 255 pitches. The path needs v1.1: under v1 it returned 404.Need Licensed MLB Data?
The MLB Stats API suits personal projects. A commercial app needs a license, support and an uptime promise. Compare licensed providers in our directory.