StatPartyAPI
Computed baseball analysis. This is not a mirror of anyone's data: it serves numbers StatParty works out, and defines every one of them.
What you get back
Every response carries the answer and the provenance of the answer. The envelope never changes shape.
{
"data": { "value": 0.412, "name": "Weighted On-Base Average", "slug": "woba" },
"meta": {
"as_of": "2026-07-28T09:15:02Z", // watermark of the data behind it
"served_at": "2026-07-28T14:02:11Z", // when this response was built
"freshness": "current",
"health": "ok",
"completeness": "complete",
"surface": "public-derived",
"sources": [ ... ]
}
}
as_of is the age of the underlying data, not the clock. If the
inputs are stale the number says so rather than looking fresh.
Grades
The grade is the house measure. A player is ranked inside their own role
cohort, because grading a reliever against starters is not a measurement.
X and S additionally have to clear a league-wide
bar, so the top of four role cohorts cannot produce four X
seasons a year.
| Grade | Role percentile | Meaning |
|---|---|---|
| X | 99.9th | A season nobody has any business having. Best in baseball, not just best in role. |
| S | 99.5th | Superstar. Clears both the role bar and a league-wide bar. |
| A+ | 99th | Elite inside the role. |
| A | 90th | Clearly above the crowd. |
| B+ | 75th | Comfortably above average. |
| B | 60th | Above average. |
| C+ | 40th | Slightly above the middle. |
| C | 25th | The middle of the league. |
| D | 10th | Below average. |
| F | 0th | Replacement level or worse. |
Published statistics 81
Each of these is computed by StatParty from its inputs. The formula is served alongside the value, so a number can always be checked.
Batting 19
| Stat | How it is computed |
|---|---|
At-Bats per Home Runab-per-home-run |
AB/HR = atBats / homeRuns; NULL if homeRuns = 0 |
BABIP (Batting Average on Balls in Play)babip |
BABIP = (hits - homeRuns) / (atBats - strikeOuts - homeRuns + COALESCE(sacFlies,0)); NULL if denominator <= 0 |
Batting Averagebatting-average |
AVG = hits / atBats; NULL if atBats = 0 |
Career Batting Averagecareer-batting-average |
SUM(hits) / SUM(atBats) over all hitting season rows for the player; NULL if SUM(atBats) = 0; never the average of season AVGs |
Career OPScareer-ops |
(SUM(hits)+SUM(baseOnBalls)+SUM(COALESCE(hitByPitch,0))) / (SUM(atBats)+SUM(baseOnBalls)+SUM(COALESCE(hitByPitch,0))+SUM(COALESCE(sacFlies,0))) + SUM(totalBases)/SUM(atBats); NULL if SUM(atBats) = 0; built from summed components, never averaged season OPS |
Contact Rate (AB-based)contact-rate |
Contact% (approx) = (atBats - strikeOuts) / atBats; NULL if atBats = 0; approx - true per-swing contact rate requires statcast swing/description data (2026 only in statcast_pitches) |
Extra-Base Hit Rateextra-base-hit-rate |
XBH% = (doubles + triples + homeRuns) / plateAppearances; NULL if plateAppearances = 0; per-PA convention (some sources use /AB or /H - state which in API docs) |
GIDP per Plate Appearancegidp-per-pa |
GIDP/PA = groundIntoDoublePlay / plateAppearances; NULL if plateAppearances = 0 or GIDP untracked (pre-1933 NL / pre-1939 AL); approx - true opportunity rate needs runner-on-1B-under-2-outs base states |
Isolated Powerisolated-power |
ISO = (totalBases - hits) / atBats; NULL if atBats = 0; algebraically SLG - AVG |
On-Base Percentageon-base-percentage |
OBP = (hits + baseOnBalls + COALESCE(hitByPitch,0)) / (atBats + baseOnBalls + COALESCE(hitByPitch,0) + COALESCE(sacFlies,0)); NULL if denominator = 0; sac bunts excluded per official MLB definition |
OPS (On-Base Plus Slugging)ops |
OPS = OBP + SLG (per on-base-percentage and slugging-percentage definitions); NULL if either component is NULL |
Plate Appearances per Strikeoutpa-per-strikeout |
PA/SO = plateAppearances / strikeOuts; NULL if strikeOuts = 0 (a zero-K season is the interesting case - surface as NULL/infinite, not 0) |
Runs Created per 27 Outsruns-created-per-27 |
RC27 = 27 * RC / (atBats - hits + COALESCE(sacFlies,0) + COALESCE(sacBunts,0) + COALESCE(caughtStealing,0) + COALESCE(groundIntoDoublePlay,0)); NULL if outs consumed <= 0; RC per runs-created definition |
Secondary Averagesecondary-average |
SecA = (baseOnBalls + (totalBases - hits) + COALESCE(stolenBases,0) - COALESCE(caughtStealing,0)) / atBats; NULL if atBats = 0 (Bill James) |
Slugging Percentageslugging-percentage |
SLG = totalBases / atBats; NULL if atBats = 0; if totalBases NULL, derive (hits - doubles - triples - homeRuns) + 2*doubles + 3*triples + 4*homeRuns |
Strikeout Ratestrikeout-rate |
K% = strikeOuts / plateAppearances; NULL if plateAppearances = 0 |
Total Averagetotal-average |
TA = (totalBases + baseOnBalls + COALESCE(hitByPitch,0) + COALESCE(stolenBases,0) - COALESCE(caughtStealing,0)) / (atBats - hits + COALESCE(caughtStealing,0) + COALESCE(groundIntoDoublePlay,0)); NULL if denominator <= 0 (Tom Boswell's bases-gained per out) |
Walk-to-Strikeout Ratiowalk-per-strikeout |
BB/K = baseOnBalls / strikeOuts; NULL if strikeOuts = 0 |
Walk Ratewalk-rate |
BB% = baseOnBalls / plateAppearances; NULL if plateAppearances = 0; HBP excluded per convention |
Pitching 22
| Stat | How it is computed |
|---|---|
BABIP Againstbabip-against |
approx: BABIP = (hits - homeRuns) / (battersFaced - baseOnBalls - hitByPitch - strikeOuts - homeRuns); official formula uses AB - K - HR + SF, and SF-against is not in the pitching raw; null when denominator <= 0 |
Walks per 9bb-per-9 |
BB/9 = 9 * baseOnBalls / IP (includes intentional walks, per official convention); null when IP = 0 |
Walk Rate (BB%)bb-rate |
BB% = baseOnBalls / battersFaced; null when battersFaced is 0 or missing |
Earned Run Average (ERA)era |
ERA = 9 * earnedRuns / IP, where IP decodes statsapi 'X.Y' string as X + Y/3 (Y in 0-2); null when IP = 0 |
ERA+era-plus |
ERA+ = 100 * lgERA / ERA, park-adjusted in the official B-R version; read from era_adjusted JSON when present, else compute league-only approx (no park factor); null/capped when ERA = 0 |
FIPfip |
FIP = (13*homeRuns + 3*(baseOnBalls + hitByPitch) - 2*strikeOuts)/IP + cFIP, where cFIP is computed per season as lgERA - (13*sum(HR) + 3*(sum(BB)+sum(HBP)) - 2*sum(K))/sum(IP) over all pitching rows of that season, so league FIP = league ERA; null when IP = 0 |
First-Pitch Strike Ratefirst-pitch-strike-rate |
F-Strike% = PAs whose first pitch (min pitch_index per game + at_bat_index, i.e. balls = 0 AND strikes = 0) results in a called strike, swinging strike, foul, or ball in play, divided by total PAs; hit-by-pitch and balls are not first-pitch strikes; null when PAs = 0 |
Ground Out to Air Out Ratiogo-ao-ratio |
GO/AO = groundOuts / airOuts (official MLB stat: outs recorded on ground balls vs air balls, not all batted balls); null when airOuts = 0 |
Hits per 9h-per-9 |
H/9 = 9 * hits / IP; null when IP = 0 |
Home Runs per 9hr-per-9 |
HR/9 = 9 * homeRuns / IP; null when IP = 0 |
K-BB%k-minus-bb-rate |
K-BB% = (strikeOuts - baseOnBalls) / battersFaced; null when battersFaced is 0 or missing |
Strikeouts per 9k-per-9 |
K/9 = 9 * strikeOuts / IP; null when IP = 0 |
Strikeout-to-Walk Ratiok-per-bb |
K/BB = strikeOuts / baseOnBalls; null when baseOnBalls = 0 (undefined, do not report infinity) |
Strikeout Rate (K%)k-rate |
K% = strikeOuts / battersFaced; null when battersFaced is 0 or missing (BF is unpopulated for some pre-1900 seasons) |
Left On Base Percentage (LOB%)lob-rate |
FanGraphs definition: LOB% = (hits + baseOnBalls + hitByPitch - runs) / (hits + baseOnBalls + hitByPitch - 1.4*homeRuns); null when denominator <= 0; can exceed 1 or go negative in tiny samples, report raw value |
Opponent Batting Averageopponent-avg |
approx: OppAVG = hits / (battersFaced - baseOnBalls - hitByPitch); official AB-against also subtracts sac flies, sac bunts and catcher's interference, which are absent from the pitching raw JSON; null when denominator <= 0 |
Opponent On-Base Percentageopponent-obp |
approx: OppOBP = (hits + baseOnBalls + hitByPitch) / battersFaced; official denominator excludes sac bunts and catcher's interference (not in pitching raw); null when battersFaced is 0 or missing |
Pitches per Batter Facedpitches-per-batter |
P/BF = (strikes + balls) / battersFaced; null when battersFaced is 0/missing or pitch counts missing |
Pitches per Inningpitches-per-inning |
P/IP = (strikes + balls) / IP; statsapi convention: strikes includes balls-in-play, so strikes + balls = total pitches; null when IP = 0 or pitch counts missing (older seasons) |
Runs Allowed per 9 (RA9)ra9 |
RA9 = 9 * runs / IP (earned + unearned); null when IP = 0 |
Strike Percentagestrike-rate |
Strike% = strikes / (strikes + balls); balls-in-play count as strikes per statsapi convention; null when strikes + balls = 0 |
WHIPwhip |
WHIP = (hits + baseOnBalls) / IP; HBP excluded per official definition; null when IP = 0 |
Plate discipline 13
| Stat | How it is computed |
|---|---|
Called Strike Rate (CStr%)called-strike-rate |
count(description = 'called_strike') / total PITCHES; NULL if PITCHES < 250. |
Chase Induced Ratechase-induced-rate |
Opponent SWINGS on zone IN (11,12,13,14) / pitches thrown with zone IN (11,12,13,14), grouped by pitcher_id; NULL if out-of-zone pitches < 100. |
Chase Rate (O-Swing%)chase-rate |
SWINGS on zone IN (11,12,13,14) / pitches with zone IN (11,12,13,14); zone 10 does not exist in the Gameday scheme; NULL if out-of-zone pitches < 75. |
Pitches Per PApitches-per-pa |
COUNT(*) / NULLIF(COUNT(CASE WHEN events IS NOT NULL AND events <> '' THEN 1 END), 0) grouped by batter_id; PA count is the count of PA-ending pitches, avoiding any game-key join; NULL if completed PAs < 50. |
Pitches Per PA (Historical)pitches-per-pa-classic |
raw.numberOfPitches / raw.plateAppearances for stat_group = 'hitting'; NULL if plateAppearances < 100 OR numberOfPitches IS NULL OR numberOfPitches = 0. |
Strike Share (Historical)strike-share-classic |
raw.strikes / (raw.strikes + raw.balls) for stat_group = 'pitching'; NULL if either value IS NULL or (strikes + balls) < 200. |
Swing Decision Gapswing-decision-gap |
Z-Swing% minus O-Swing% (zone-swing-rate minus chase-rate, both as defined above); NULL unless both component minimums (100 in-zone, 75 out-of-zone pitches) are met. |
Swing Rateswing-rate |
SWINGS / PITCHES, where SWINGS = count(description IN ('foul','foul_tip','foul_bunt','bunt_foul_tip','hit_into_play','swinging_strike','swinging_strike_blocked','missed_bunt')); NULL if PITCHES < 250. |
Whiff Induced Ratewhiff-induced-rate |
Opponent WHIFFS / opponent SWINGS, grouped by pitcher_id; NULL if swings against < 125. |
Whiff Ratewhiff-rate |
WHIFFS / SWINGS, where WHIFFS = count(description IN ('swinging_strike','swinging_strike_blocked','missed_bunt')); foul_tip counts as contact, not whiff; NULL if SWINGS < 100. |
Zone Contact Rate (Z-Contact%)zone-contact-rate |
(in-zone SWINGS - in-zone WHIFFS) / in-zone SWINGS, in-zone = zone BETWEEN 1 AND 9; NULL if in-zone SWINGS < 75. |
Zone Ratezone-rate |
count(zone BETWEEN 1 AND 9) / total PITCHES thrown; NULL if PITCHES < 300; NULL zone rows excluded from both numerator and denominator. |
Zone Swing Rate (Z-Swing%)zone-swing-rate |
SWINGS on zone BETWEEN 1 AND 9 / pitches with zone BETWEEN 1 AND 9; NULL if in-zone pitches < 100. |
Batted ball 12
| Stat | How it is computed |
|---|---|
Barrel Rate (per BBE)barrel-rate |
barrels / BBE with launch_speed AND launch_angle NOT NULL; official barrel: launch_speed >= 98 with LA 26-30 at 98 mph, window widening ~2-3 deg per mph to 8-50 at 116+ (MLB lookup table); closed form low=MAX(8, 26-(launch_speed-98)), high=MIN(50, 30+2*(launch_speed-98)) is approx near the top end; NULL if BBE < 50 |
Barrel Rate Allowedbarrel-rate-against |
barrels (official definition per barrel-rate) / BBE with launch_speed AND launch_angle NOT NULL, grouped by pitcher_id; NULL if BBE < 50 |
Barrels per Plate Appearancebarrels-per-pa |
barrels / COUNT(rows WHERE events IS NOT NULL AND events NOT IN ('truncated_pa')) grouped by batter_id; NULL if PA < 100 |
Fly-Ball Ratefly-ball-rate |
COUNT(bb_type='fly_ball') / COUNT(BBE with bb_type NOT NULL); popups counted separately, not as fly balls; NULL if denominator < 50 |
Ground-Ball to Fly-Ball Ratiogb-fb-ratio |
COUNT(bb_type='ground_ball') / COUNT(bb_type='fly_ball'); NULL if fly-ball count < 10 |
Ground-Ball Rateground-ball-rate |
COUNT(bb_type='ground_ball') / COUNT(BBE with bb_type NOT NULL); NULL if denominator < 50 |
Ground-Ball Rate Inducedground-ball-rate-against |
COUNT(bb_type='ground_ball') / COUNT(BBE with bb_type NOT NULL) grouped by pitcher_id; NULL if denominator < 50 |
Hard-Hit Ratehard-hit-rate |
COUNT(launch_speed >= 95) / COUNT(BBE with launch_speed NOT NULL); official threshold is exactly 95.0 mph; NULL if denominator < 50 |
Hard-Hit Rate Allowedhard-hit-rate-against |
COUNT(launch_speed >= 95) / COUNT(BBE with launch_speed NOT NULL) grouped by pitcher_id; NULL if denominator < 50 |
Line-Drive Rateline-drive-rate |
COUNT(bb_type='line_drive') / COUNT(BBE with bb_type NOT NULL); NULL if denominator < 50 |
Popup Ratepopup-rate |
COUNT(bb_type='popup') / COUNT(BBE with bb_type NOT NULL); NULL if denominator < 50 |
Sweet-Spot Ratesweet-spot-rate |
COUNT(launch_angle BETWEEN 8 AND 32) / COUNT(BBE with launch_angle NOT NULL); official Statcast band is exactly 8-32 degrees inclusive; NULL if denominator < 50 |
Plain language 6
| Stat | How it is computed |
|---|---|
Ambush Rateambush-rate |
0-0 pitches = statcast_pitches WHERE balls = 0 AND strikes = 0; swing = description IN ('swinging_strike','swinging_strike_blocked','foul','foul_tip','hit_into_play','foul_bunt','missed_bunt'); rate = ROUND(100 * first-pitch swings / 0-0 pitches) with >= 100 0-0 pitches, else NULL; rendered 'swings at the first pitch N% of the time' |
Crush Ratecrush-rate |
official Statcast hard-hit definition: COUNT(description='hit_into_play' AND launch_speed >= 95.0) / COUNT(description='hit_into_play' AND launch_speed IS NOT NULL) per batter; NULL below 50 measured batted balls; rendered 'crushes the ball 95+ mph on N% of contact' |
Hits Per Ten Trieshits-per-ten-tries |
ROUND(10 * raw.hits / raw.atBats, 1) with atBats >= 50, else NULL; rendered 'gets a hit N times out of every 10 at-bats' |
Homer Pacehomer-pace |
approx: ROUND(162.0 * raw.homeRuns / raw.gamesPlayed) for the in-progress season, gamesPlayed >= 20 else NULL; rendered 'on pace for N homers' |
On Base Out of Tenon-base-out-of-ten |
OBP = (raw.hits + raw.baseOnBalls + raw.hitByPitch) / (raw.atBats + raw.baseOnBalls + raw.hitByPitch + COALESCE(raw.sacFlies,0)); soundbite = ROUND(OBP*10, 1) rendered 'gets on base N times out of 10'; NULL if denominator < 50 (COALESCE handles pre-1954 missing sacFlies) |
Punchouts Per Ten Batterspunchouts-per-ten |
ROUND(10 * raw.strikeOuts / raw.battersFaced, 1) with battersFaced >= 50, else NULL; rendered 'strikes out N of every 10 batters he faces' |
Critical measures 2
| Stat | How it is computed |
|---|---|
Seasons Playedseasons-played |
COUNT(DISTINCT season) where hitting plateAppearances > 0 OR pitching battersFaced > 0 |
Stolen Base Attempt Ratestolen-base-attempt-rate |
(SB + CS) / (H - 2B - 3B - HR + BB + HBP); denominator approximates times on first; NULL when denominator < 50 |
Situational 1
| Stat | How it is computed |
|---|---|
First-Pitch Swing Ratefirst-pitch-swing-rate |
COUNT(pitches with balls=0 AND strikes=0 AND description IN ('swinging_strike','swinging_strike_blocked','foul','foul_tip','foul_bunt','missed_bunt','bunt_foul_tip','hit_into_play','hit_into_play_no_out','hit_into_play_score')) / COUNT(pitches with balls=0 AND strikes=0) per batter_id; NULL if denominator=0. |
Era adjusted 1
| Stat | How it is computed |
|---|---|
OPS+ (league-relative, no park)ops-plus |
100*(OBP/lgOBP + SLG/lgSLG - 1) where OBP=(H+BB+HBP)/(AB+BB+HBP+SF), SLG=totalBases/AB, league rates from SUM of the same components over all hitting rows in (season, league_id); NULL HBP/SF treated as 0 (pre-1954 SF untracked); NULL if AB=0 or OBP denominator=0; approx - official OPS+ is park-adjusted and excludes pitcher batting from league rates. |
Win probability 1
| Stat | How it is computed |
|---|---|
Clutch Scoreclutch-score |
(late_close_wpa / late_close_PA) - (total play_wpa / total PA); NULL if late_close_PA < 20; approx of official Clutch, which weights by leverage index rather than a Late & Close filter |
Other 4
| Stat | How it is computed |
|---|---|
Average Leverage Indexaverage-leverage-index |
|
Average Leverage Index Facedaverage-leverage-index-faced |
|
Era Minusera-minus |
|
Obp Plusobp-plus |
What is not served, and why
66 of the 147 computed statistics are withheld, along with 10 routes. Two reasons, and the second is the interesting one. Some values are raw facts belonging to the upstreams that publish them, whose terms do not permit redistribution. Others are our own analysis but would let a caller work backwards to those facts, which amounts to the same thing.
raw count 26
This is a raw counting stat. Counts of hits, at-bats, home runs, innings, strikeouts, walks, runs, saves, games and pitches are MLB Advanced Media or Baseball Savant facts, and the terms of access to those sources do not permit StatParty to re-serve them.
barrel-count, career-hbp, career-hits, career-home-runs, career-stolen-bases, career-total-bases, complete-games, extra-base-hits, hits, hits-to-3000, home-runs, hr-to-500, k-to-3000-pitcher, runs-produced, sac-flies, saves-to-300, shutouts, single-game-hits, single-game-home-runs, single-game-rbi, single-game-strikeouts-pitched, singles, stolen-bases, times-on-base, total-bases, wins-to-300
score fact 10
Derived from stored game scores and win-loss outcomes, which are MLB Advanced Media facts. Team run totals, records and shutout counts are sums of those scores rather than analysis of them.
blowout-record, extra-innings-record, home-road-record, one-run-record, run-differential, run-differential-per-game, team-runs-allowed-per-game, team-runs-per-game, team-shutouts-suffered, team-shutouts-thrown
upstream measurement 8
This is a Statcast sensor reading, or the mean or maximum of one. Exit velocity, launch angle, hit distance, bat speed, swing length and release speed are Baseball Savant measurements published by Savant itself; summarising the measurement is not analysis of it.
avg-bat-speed, avg-exit-velocity, avg-exit-velocity-against, avg-hit-distance, avg-launch-angle, avg-swing-length, max-exit-velocity, max-velocity
invertible pair 8
Numerator and denominator are both raw upstream counts and the denominator is small enough that the integer pair can be recovered from the published ratio, which would hand back the counts themselves.
door-slam-rate, net-stolen-bases, power-speed-combo, power-speed-number, save-pct, stolen-base-percentage, weighted-stolen-base-runs, win-loss-pct
upstream passthrough 8
This is a thin wrapper over an upstream column: a sum, a difference or a rename of a value MLB Advanced Media or Baseball Savant computed. Win probability added and RE24 come from MLB's own win-expectancy and run-expectancy models, and xwOBA is Savant's model output, not StatParty's.
game-score, re24, re24-allowed, single-game-wpa, woba-xwoba-gap, wpa-li, wpa-li-allowed, xwoba
war value 4
This is a Baseball-Reference WAR figure, or WAR rescaled by a denominator that is public knowledge. Sports Reference grants a non-sublicensable licence and forbids building a data store that substitutes for its own, so no WAR number leaves this API. The letter grade computed from it is public; a letter cannot be inverted back to a WAR figure.
career-war-per-650-pa, position-war-leaders, war-per-180-ip, war-per-650-pa
denominator recovery 1
Serving this converts a widely known count (games started, games played) into the denominator of every other rate we publish, which would turn the public rate set into raw counting stats.
ip-per-start
count composite 1
This is a run-scale or point-scale magnitude assembled out of raw counts (runs created, game score). Dividing counts by nothing is not analysis of them: the number moves one-for-one with the hits, walks and total bases it was built from, so serving it serves those.
runs-created
Routes withheld entirely
/v1/slate/{date}/v1/games/{pk}/boxscore/v1/games/{pk}/winprob/v1/players/{ident}/seasons/v1/players/{ident}/gamelog/v1/standings/{date}/v1/leaders/v1/teams/{abbr}/computed/v1/records/v1/moments
Terms
Free for hobby, personal, research and fan use. The per-source position, including which upstreams permit redistribution and which do not, is recorded with the operative sentence quoted verbatim in TERMS.md. Commercial use of the derived numbers is not yet granted: ask first.