Methodology
How the data is fetched, what the metrics mean, and where to be skeptical.
What is the ABS Challenge System?
Starting in 2026, MLB deployed the Automated Ball-Strike (ABS) system in all 30 ballparks. Hawk-Eye cameras track every pitch in 3D and determine whether it passed through the rulebook strike zone.
Each team gets two challenges per game. A batter can challenge a called strike; a catcher can challenge a called ball. If the ABS system disagrees with the umpire, the call is overturned on the spot. If the challenge fails, the team loses that challenge for the rest of the game.
The result: umpires are now accountable to a machine. And teams have a strategic decision every time they disagree — is this call worth burning a challenge?
Where the Data Comes From
- 1. Baseball Savant — ABS Leaderboard
Primary source. We scrape the ABS Challenge leaderboard pages (batting-team, catching-team, individual batter, individual catcher views). Data is embedded as inline JavaScript on the page and parsed via regex. Updated daily at 11:00 UTC.
- 2. MLB Stats API — Standings
Team records (wins, losses, win%) come from
statsapi.mlb.com/api/v1/standings. Used to correlate challenge performance with actual winning. - 3. MLB Stats API — League Hitting
Strikeout and walk rates at the league level, for year-over-year deltas on the homepage.
Last refreshed: May 16, 2026. Pipeline version: 0.1.0.
How We Calculate Each Metric
Challenge WPA
Sum of Win Probability Added across all successful ABS challenges for a team or player.
Σ delta_home_win_exp for overturned pitches (adjusted for team perspective)
Unit: win probability units (0–1 scale)
- ⚠ WPA is context-dependent — a challenge in a blowout contributes nearly zero.
- ⚠ Based on Statcast WPA model, which may differ from ESPN or Baseball Reference.
- ⚠ Small samples (< 5 successful challenges) are highly volatile.
Challenge Success Rate
Proportion of ABS challenges that resulted in an overturned call.
successful_challenges / total_challenges
Unit: proportion (0–1)
- ⚠ Does not weight by leverage — a meaningless challenge counts the same as a game-changing one.
- ⚠ Samples below 10 challenges are flagged as unreliable.
Net Overturns
Overturns in a team's favor minus overturns against them.
overturns_for - overturns_against
Unit: integer
- ⚠ Does not capture leverage — a team can have positive net overturns but negative WPA.
Challenge Usage Rate
Challenges used per available challenge opportunity.
total_challenges / total_challenge_opportunities
Unit: proportion (0–1)
- ⚠ Total opportunities is estimated (2 per game per team) — actual availability varies.
Expected Win%
Pythagorean win expectancy based on runs scored and allowed.
RS^1.83 / (RS^1.83 + RA^1.83)
Unit: proportion (0–1)
- ⚠ Exponent 1.83 is the MLB-calibrated Pythagorean exponent (James, 1980).
- ⚠ Deviations from actual W% suggest luck or bullpen performance.
Known Limitations
- ⚠ Small samples. Through mid-season, most teams have fewer than 100 challenges. Rates are volatile. We show sample size warnings throughout the site.
- ⚠ No true WPA. Baseball Savant's ABS leaderboard provides
net_net_runs(net run value vs. expected), not pitch-level Win Probability Added. We label this "Net RV" to be precise. It doesn't weight by game leverage. - ⚠ Data lag. Challenge data may lag 24–48 hours after late-night games. The footer shows the last refresh timestamp.
- ⚠ Selection bias. Teams that challenge more often may have a higher success rate because they're more selective — or a lower rate because they challenge marginal pitches. We don't adjust for challenge selectivity.
- ⚠ Correlation ≠ causation. If good challenge teams win more, it could be that better organizations are better at everything. We compute Pearson r but never claim a causal relationship.
- ⚠ Division labels. Team division may show as "Unknown" when the MLB Stats API doesn't fully hydrate division metadata. Cosmetic only.
Verify the Data Yourself
This project is fully open source. You can verify every number.
- → Primary source: Baseball Savant ABS Leaderboard ↗
- → Source code:
GitHub repository ↗
— see
scripts/for the full pipeline. - → Run locally:
python scripts/run.pyfetches fresh data and regenerates all JSON files. - → Raw JSON: All computed data lives in
src/data/*.jsonand is committed to the repo. You can diff any refresh.