FanGraphs APIfangraphs.com ↗
Access FanGraphs MLB season batting leaderboards via API. Filter by season, league, min PA, and page size. Returns WAR, HR, PA, age, position, and more.
What is the FanGraphs API?
The FanGraphs MLB Batting API exposes one endpoint — get_batting_leaderboard — that returns single-season batting leaderboard data for MLB position players, with 15+ fields per player row including WAR, plate appearances, home runs, age, and position. Results are sorted by WAR descending, support filtering by league and minimum plate appearances, and paginate across large result sets.
curl -X GET 'https://api.parse.bot/scraper/2afec5da-9ff2-40a1-9c3c-58e9df32624a/get_batting_leaderboard?league=all&season=2026' \ -H 'X-API-Key: $PARSE_API_KEY'
Typed, relational, agent-ready
A generated client with real types, enums, and the links between objects — the structure a flat JSON response can't carry. Autocompletes in your editor and reads cleanly to coding agents.
- Fully typed · autocompletes
- Objects link to objects
- Typed errors & pagination
Typed Python client. Set up the SDK in your uv project, then pull this API’s typed client:
uv add parse-sdk uv run parse init uv run parse add --marketplace fangraphs-com-api
uv run parse add --marketplace pulls a pinned snapshot of this canonical API — it won’t change underneath you. To customize it, subscribe and swap to your own copy.
"""Walkthrough: FanGraphs batting leaderboard — bounded, re-runnable."""
from parse_apis.fangraphs_com_api import FanGraphs, League, InputFormatInvalid
client = FanGraphs()
# Top WAR leaders for the current season, qualified batters only (omit min_pa).
for player in client.players.list(season=2025, league=League.ALL, limit=5):
print(f"{player.name:25s} {player.team} WAR {player.war:.1f} AVG {player.avg:.3f}")
# Filter to AL with a custom PA threshold; drill into the first result.
top_al = client.players.list(season=2025, league=League.AL, min_pa=400, limit=1).first()
if top_al is not None:
print(f"\nTop AL player: {top_al.name}, age {top_al.age}, {top_al.position}")
print(f" PA {top_al.pa} HR {top_al.hr} SB {top_al.sb} wRC+ {top_al.wrc_plus:.1f}")
# Demonstrate typed error handling for an invalid season.
try:
client.players.list(season=9999, limit=1).first()
except InputFormatInvalid as e:
print(f"\nCaught expected error: {e.message}")
print("\nexercised: players.list (all, AL filter, min_pa filter, error handling)")
Returns one page of the MLB single-season batting leaderboard (one row per player-season, players who changed teams are collapsed into one row), sorted by WAR descending. Each row carries the player's name, team abbreviation, season, plate appearances, WAR and a set of standard rate/counting stats (rates such as avg, obp, bb_pct are decimals, e.g. 0.331). When min_pa is omitted the site's own 'qualified' threshold (3.1 PA per team game) applies; passing min_pa (including 0) replaces it with an explicit minimum PA filter. Pagination is caller-controlled through page (1-based, default 1) and page_size (default 100, clamped to 300); total_count is the number of rows matching the filters across all pages and has_more tells whether a further page exists. A season with no data (e.g. a future year) returns total_count 0 and an empty players array. One upstream request per call.
| Param | Type | Description |
|---|---|---|
| page | integer | 1-based page number of the leaderboard. |
| league | string | Restrict to one league or return both. |
| min_pa | integer | Minimum plate appearances a player must have to be included. Omitted = the site's qualified-batters threshold; 0 = every player with at least one PA. |
| seasonrequired | integer | MLB season year, e.g. 2025. Values outside 1871-2100 are rejected. |
| page_size | integer | Rows per page; values above 300 are clamped to 300. |
{
"type": "object",
"fields": {
"page": "integer page returned",
"league": "league filter applied (all, al or nl)",
"min_pa": "integer minimum PA filter applied, or null when the qualified threshold was used",
"season": "integer season requested",
"players": "array of player-season rows: player_id (FanGraphs id, string), name, team (abbreviation), season, age, position, games, pa, ab, hr, r, rbi, sb, bb_pct, k_pct, avg, obp, slg, woba, wrc_plus, war (WAR, decimal)",
"has_more": "boolean, true when a later page exists",
"page_size": "integer page size applied after clamping",
"total_count": "integer number of matching player rows across all pages",
"qualified_only": "boolean, true when the site's qualified-batters threshold was applied instead of min_pa"
},
"sample": {
"data": {
"page": 2,
"league": "all",
"min_pa": 300,
"season": 2025,
"players": [
{
"r": 75,
"ab": 571,
"hr": 32,
"pa": 651,
"sb": 5,
"age": 30,
"avg": 0.245183887,
"obp": 0.325652841,
"rbi": 103,
"slg": 0.478108581,
"war": 2.6157618460678647,
"name": "Seiya Suzuki",
"team": "CHC",
"woba": 0.34336035976043117,
"games": 151,
"k_pct": 0.251920122,
"bb_pct": 0.10906298,
"season": 2025,
"position": "DH/OF",
"wrc_plus": 122.9476587822367,
"player_id": "30116"
}
],
"has_more": true,
"page_size": 100,
"total_count": 277,
"qualified_only": false
},
"status": "success"
}
}About the FanGraphs API
What the API Returns
The get_batting_leaderboard endpoint returns one page of FanGraphs' MLB single-season batting leaderboard, where each row corresponds to one player-season. Players who were traded mid-season appear as a single combined row. Each row includes the player's FanGraphs player_id, name, team abbreviation, season, age, position, games, pa (plate appearances), ab (at-bats), and hr (home runs), alongside WAR. The response envelope includes total_count, has_more, page, page_size, league, and min_pa so callers can paginate and reconstruct filtering context.
Filtering and Pagination
The required season parameter accepts any MLB year from 1871 through 2100. Use the optional league parameter to restrict results to al or nl, or omit it to return both leagues. min_pa sets a minimum plate appearances threshold; omitting it applies FanGraphs' standard qualified-batters threshold, and the boolean qualified_only in the response tells you which mode was used. page_size controls rows per page and is clamped to a maximum of 300, while page is 1-based for stepping through multi-page results.
Coverage and Limitations
This API covers major-league position player batting stats for a single requested season at a time. Historical seasons are accessible by changing the season parameter. The leaderboard reflects standard rate stats (games, PA, AB, HR) plus WAR, as exposed by FanGraphs' batting leaderboard view. Pitching statistics, minor-league data, advanced splits (e.g. vs. LHP/RHP, home/away), and multi-season aggregations are not part of this endpoint's response.
Official FanGraphs API
FanGraphs does not offer a documented public developer API. Access to FanGraphs data programmatically has historically required scraping or a private arrangement.
The FanGraphs API is a managed, monitored endpoint for fangraphs.com — not a raw scraper you maintain. Every endpoint is automatically health-checked on a schedule, and when fangraphs.com changes and a check fails, the API is automatically queued for repair and re-verified. It is built to keep working as the site underneath it changes.
This isn't an official fangraphs.com API — it's an independent, maintained REST wrapper over public data. Where the source has no official API (or only a limited one), Parse gives you a stable contract over a source that never promised one, and keeps it current. Need a new endpoint or field? You can revise it yourself in plain English and the agent rebuilds it against the live site in minutes — contributing the change back to the shared API is free.
Will this API break when the source site changes?+
Is this an official API from the source site?+
Can I fix or extend this API myself if I need a new endpoint or field?+
What happens if I call an endpoint that has an issue?+
- Rank MLB position players by WAR for a given season to build fantasy baseball tiers
- Compare plate appearances across qualifying batters to identify roster depth by team
- Track year-over-year HR leaders by querying the leaderboard for multiple seasons
- Filter to a single league (AL or NL) to build league-specific award candidate lists
- Pull age and position fields to analyze performance curves across player age buckets
- Feed total_count and has_more into a pipeline that paginates through all qualifying batters
- Identify breakout players by querying low-min_pa thresholds and sorting by WAR
| Tier | Price | Credits/month | Rate limit |
|---|---|---|---|
| Free | $0/mo | 200 | 5 req/min |
| Hobby | $30/mo | 1,000 | 20 req/min |
| Developer | $100/mo | 5,000 | 100 req/min |
| Team | $300/mo | 20,000 | 300 req/min |
| Company | $1,000/mo | 100,000 | 500 req/min |
Each endpoint has a fixed posted price per successful call — most fall between 1 and 10 credits — shown on this API's page before you run it. Exceeding the rate limit returns a 429 response. Authenticate with the X-API-Key header.
Does FanGraphs have an official public developer API?+
How does the API handle players who changed teams mid-season?+
team field will reflect one team abbreviation rather than listing every team the player appeared for.What does the qualified_only field tell me, and how does min_pa interact with it?+
min_pa, the endpoint applies FanGraphs' own qualified-batters threshold (typically 3.1 PA per team game) and sets qualified_only to true in the response. If you supply an explicit min_pa value, qualified_only is false and min_pa reflects the integer you passed.