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.

The surface below is settled and enforced in code. The public endpoint is not open yet, so treat this as the contract rather than a live base URL. Generated 2026-09-12 from the surface manifest, so it cannot promise a stat the manifest does not permit.

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.

GradeRole percentileMeaning
X99.9th A season nobody has any business having. Best in baseball, not just best in role.
S99.5th Superstar. Clears both the role bar and a league-wide bar.
A+99th Elite inside the role.
A90th Clearly above the crowd.
B+75th Comfortably above average.
B60th Above average.
C+40th Slightly above the middle.
C25th The middle of the league.
D10th Below average.
F0th 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

StatHow 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

StatHow 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

StatHow 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

StatHow 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

StatHow 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

StatHow 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

StatHow 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

StatHow 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

StatHow 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

StatHow 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

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.