What's new

Recent improvements to the Big Balls Sports Data API, in reverse chronological order.

2026-08-31

  • The /v1/stored/* endpoints now take the canonical league code, and an unknown league says so instead of returning nothing. GET /v1/stored/players, /v1/stored/matches and /v1/stored/standings matched the league's DISPLAY NAME and nothing else, so ?league=La Liga returned rows while ?league=laliga returned an empty array with a 200 and no explanation — indistinguishable from a league we genuinely hold no rows for. Measured on all three endpoints on 2026-08-31. They now resolve through the same shared resolver /v1/standings, /v1/teams and /v1/leagues/{id}/* already use, which accepts the canonical key, the brand alias AND the exact display name, so nothing that worked before has stopped working. An unrecognised league is now a 400 naming the value you sent, with the list of valid codes, rather than a silent empty 200. The 2026-08-27 entry below said /v1/teams?league= takes the canonical key "like every other league-filtered endpoint" — these three were the endpoints that disagreed with that sentence, and now they don't. League codes are listed at /docs/league-codes.

  • ?season= now means the season, not the calendar year. Row counts will change on most sports, and the old numbers were wrong. A season query filtered on the calendar year of kickoff, which is correct only for leagues whose season starts and ends inside one year. For everything else it returned roughly half of one campaign and half of the next. Measured on 2026-08-31 for ?season=2025: NBA returned 1,310 matches of which 767 (59%) belonged to another season, EPL returned 378 of which 192 (51%) did, and Serie A, La Liga, Bundesliga and Ligue 1 all sat between 54% and 56% wrong. NHL failed differently and worse: it returned no wrong rows at all and 55% of the season missing — 626 of 1,391 — because a season starting in October only overlaps a calendar year for three months, so the response was complete-looking, internally consistent and half a season. There was no way for a caller to tell. ?season=2025 now means the campaign that STARTS in 2025: the 2025-26 season for the EPL, NBA and NHL, and the 2025 season for MLB, MLS and WNBA, whose seasons genuinely are calendar years and whose results are unchanged. Each league's own start month is resolved from the same registry the season labels come from, so a league that plays May to August is not forced into a rollover it does not have. This affects GET /v1/players/{id}/stats, GET /v1/teams/{id}/matches, GET /v1/teams/{id}/stats and GET /v1/matches wherever ?season= is sent. If you were compensating for the old behaviour — requesting two years and merging, or discarding rows outside a date range — you can stop, and you should, because that compensation now double-counts. The parameter is documented per endpoint in the OpenAPI spec; one route previously described the old behaviour in its own docs as "returns only the 2025 half. Documented as-is", and documenting it did not make it usable.

2026-08-28

  • NBA and NHL injury rows have stopped updating, and updated_at is how you tell. The upstream provider feeding these two stopped returning data. GET /v1/injuries?sport=basketball and ?sport=ice_hockey still return 200 with rows, but the newest NBA row carries an updated_at of 2026-07-26 and the newest NHL row 2026-07-01, so those responses describe a moment months back rather than today. Read updated_at on each row before treating a status as current. Soccer injuries are unaffected and still current. The 2026-06-02 entry below announced these two as live and refreshed every 30 minutes, and that is no longer true.

  • NFL injuries, depth charts and team stats are now served. Three endpoints, 64,425 rows, every one of which used to 404. The tables were being filled daily and nothing read them. GET /v1/nfl/injuries returns the league's mandated weekly injury and practice report (34,810 rows, seasons 2020 through 2025); call it bare and it resolves to the newest week, which it names in meta.resolved. GET /v1/nfl/depth-charts returns each team's open chart intervals, or the chart as it stood at a past instant with ?as_of=; rows carry open_interval rather than current, and meta.season_covered tells you which season the chart belongs to, because labelling a chart "current" is a claim about a season that has not started. GET /v1/nfl/team-stats returns team-week offensive totals including EPA and CPOE. All three carry meta.data_as_of from the row's own updated_at, and the writer only rewrites a row that actually changed, so that timestamp ages honestly when a feed goes quiet. Coverage reports these as off_season until Week 1 on 2026-09-09, not live. See /nfl-api and /injuries-api.

  • GET /v1/injuries?sport=american_football returns data instead of a 404 that contradicted itself. It answered "we do not serve injuries for 'american_football' yet" while the suggested_fix in the same payload listed american_football as live, with 34,810 rows sitting in the table. It now serves the NFL rows through the unified endpoint. Two nflverse team abbreviations disagreed with ours (LA and WAS), which served team: null for those two franchises; both are mapped. baseball has been removed from the supported list on the same endpoint, because no baseball injury table exists and it was passing the gate only to 404 with a suggested_fix that excluded it.

  • Soccer injuries are current again across six leagues, MLS included, and ?league= narrows to one competition. Two defects were stacking. The season the ingest asked for rolled over in September for every league, which is right for European clubs and wrong for MLS and Brasileirão, whose seasons are calendar years: on 2026-08-27 that fetched MLS season 2025, returning a complete, plausible, nine-month-stale set that looks exactly like success. And as_of was computed from fetched_at, which the ingest re-stamps on every row every 30 minutes, so the endpoint reported today's timestamp over fixtures that had ended on 2026-05-29, 91 days earlier. as_of now anchors to fixture_date, so it can report stale when the data is stale. Measured today, all six leagues hold absence records against fixtures through 2026-08-30: EPL 3,264, La Liga 3,010, Serie A 2,921, Ligue 1 2,743, Bundesliga 2,473, MLS 2,381. ?league= previously resolved and was then ignored by the query, so every league returned the same rows; each league now gets its own rows and its own as_of. Supported values are EPL, La Liga, Serie A, Bundesliga, Ligue 1, MLS and UEFA Champions League; anything else is a 400 that lists them rather than a silent widening to all of them.

  • New: a page per league for soccer injuries. /premier-league-injury-api, /la-liga-injury-api, /serie-a-injury-api, /bundesliga-injury-api and /ligue-1-injury-api. Each one reads its own league's record count and newest fixture date at render time and prints them, so a page states how current that league actually is instead of asserting a refresh rate. They count absence records, not injuries: the reason field holds 134 distinct strings in production and no pattern splits injury from suspension without leaking in both directions, so the pages say the thing that is true of every row.

  • Combat sports no longer serve summed judges' scorecards as a final score. The cards are now published as the result they always were. matches.linescore holds judges' totals for MMA and boxing, not period points, and the score was being summed anyway: a UFC bout scored 30-27 by all three judges was served as score: {"home": 90, "away": 81}. Nobody wins a fight 90-81. On 43 finished bouts in production, score is now null with score_status: "unavailable", and two new fields carry the real answer: judge_scores republishes the cards, and decision gives winner, method (unanimous_decision, split_decision, majority_decision or draw), rounds_scored and judges. A bout that has scorecards went the distance by definition, so this is a result we already held rather than an inference. decision is omitted when the cards are not readable, because suppressing a wrong score must not depend on being able to derive a right one. Cancelled bouts also stop serving a placeholder 0-0, which for a fight is indistinguishable from a real draw. The 2026-07-25 entry below said MMA serves fight cards, matchups and odds but not results; for these 43 decisions that is no longer true, and the fighter records and non-decision finishes it also named are still not available. See /mma-api, /ufc-api and /boxing-api.

  • New: /about, who builds this and why it is published the way it is. The signup, key and call counts on it are read live rather than typed, on the page whose argument is that we publish what we can measure. It is the human counterpart to /coverage and /attribution.

2026-08-27

  • GET /v1/teams?league= takes the canonical league key, like every other league-filtered endpoint. It matched the league's display name and nothing else, so ?league=La Liga returned 23 teams and ?league=laliga returned an empty array with a 200 and no explanation. It now resolves through the same shared resolver /v1/standings and /v1/leagues/{id}/* use, which accepts the key, the brand alias and the exact display name, so nothing that worked has stopped working. An unknown league now returns a 400 naming the value you sent instead of a silent empty 200. The 2026-07-16 entry below said sport and league naming resolves through one canonical registry everywhere; this endpoint was the exception, and now it isn't. League codes are listed at /docs/league-codes.

  • GET /v1/standings no longer returns clubs that are not in the league. La Liga served 23 rows for a 20-team league, with three second-division sides sitting at games_played: 0 and holding ranks that were already taken: rank 6 was both Celta Vigo, with 38 games played, and Deportivo, with none. Anyone rendering rank got a Segunda side in La Liga's sixth place. Every loaded European league had the same shape. A row is now dropped only when its rank is non-null, it has no games played, and another team holds that same rank in the same league and season with games played, so a genuine mid-season tie survives and a table at season start survives whole. Verified live today: La Liga, EPL and Serie A return 20 rows, Bundesliga and Ligue 1 return 18, ranks contiguous from 1 with no duplicates. Leagues that store every row at rank null, such as the NHL and NCAAF, are untouched.

  • La Liga, Serie A, Bundesliga, Ligue 1 and college football are now polled for live scores. Four of our six soccer competitions and both college football divisions were absent from the score worker's league list, so they were never polled: across 60 days of finished matches, the worker had settled zero La Liga, Serie A and Ligue 1 games, and zero NCAAF games ever. Two things follow from a league being polled. Matches in those leagues now report status: "live" while they are being played, where scheduled previously covered both "not started" and "in progress" and a healthy in-play match was indistinguishable from a stuck one. And they settle promptly rather than late. College football needed more than a list entry: ESPN serves FBS and FCS from one endpoint, separated by a groups parameter, and its default response is capped at 25 events regardless of limit, so both divisions are now requested explicitly. See /ncaaf-api and /soccer-api.

  • The four soccer league pages now render real per-league data: xG boards, top-scorer boards and the upcoming fixture list. /la-liga-api, /serie-a-api, /bundesliga-api and /ligue-1-api each publish that league's own standings table, seasons held, per-data-type coverage, endpoint examples with that league's real ids, and now a ten-row expected-goals board with npxg, xa and goals-above-xG, a ten-row scorer board, and the next fixtures we hold. Every figure is read from the API at render time. One thing to know if you are calling these yourself: /v1/leagues/{id}/xg-leaders and /v1/leagues/{id}/top-scorers key on the hyphenated league code where /v1/standings and /v1/matches take the concatenated one. serie-a and ligue-1 return 200 on the boards; seriea and ligue1 return 404. laliga and bundesliga are the same either way. Send ?season= explicitly on /v1/leagues/{id}/top-scorers: it defaults to 2025 and will keep serving 2025 after the rest of your data has moved on.

  • Cricket coverage now names 13 competitions instead of 2, and GET /v1/cricket/matches/{id}/scorecard appears in a coverage surface for the first time. Cricket is our deepest sport by competition count, 768 competitions and 23,484 matches from 2001 to 2027, and the coverage manifest named two leagues of it. The scorecard endpoint serves full ball-by-ball batting and bowling cards for 22,539 finished matches, 97.8% of the sport, and was documented nowhere at all. Both are now in every generated surface, including the ones assistants read. See /cricket-api.

  • /docs/odds now lists the sportsbooks we actually carry: 25 of them, 6 reaching us from two independent providers. Pinnacle, Matchbook, ProphetX, Novig, Kalshi, Polymarket and Bet365 are among them. The list is generated from live odds joined to the book reference table, so a book we hold a reference row for but no longer receive is not published as carriage, and a build check fails when the committed copy drifts. The six two-source books are the set the two_source_agreed verification described in the 2026-07-13 entry below can actually corroborate.

2026-08-11

  • NCAA FCS (Football Championship Subdivision) is now live — a separate league from NCAAF, sharing the same American Football sport. GET /v1/matches?sport=american_football&league=ncaaf-fcs covers 139 FCS teams and a decade of regular-season history (2016-2025, weeks 1-15 — the FCS postseason playoff is not loaded yet). GET /v1/players/:id/stats now returns real per-game box-score stats for FCS players too — strong for 2021-2025 (88-99% of games), sparse before that: the upstream source's own historical FCS box-score collection doesn't reach back past 2021, a source limit, not a gap on our end. End-to-end across the 7,113 games that touch an FCS team: about 58%, 1,087,323 player_stats rows across 42,761 players. No standings, Elo ratings, or odds for FCS yet.
  • FBS-vs-FCS "buy games" are no longer missing from NCAAF (FBS) — 1,024 of them now carried. These were previously excluded entirely (2026-08-08 entry below) because the FCS opponent had no resolvable roster. GET /v1/matches?sport=american_football&league=ncaaf now returns 9,204 games (up from 8,180), and shows up on either team's own record via GET /v1/teams/:id/form and GET /v1/teams/:id/h2h-intelligence. Per-game player-stat coverage for FBS rose from ~87% to 93% of finished games as a result (4,092,224 rows across 8,188 games).

2026-08-08

  • NCAAF history is now a full decade, not one season. GET /v1/matches?sport=american_football&league=ncaaf now returns 8,180 games spanning 2016 through the 2026 schedule, up from 2025-only. GET /v1/players/:id/stats now returns real per-game box-score stats for NCAAF, live for 2016-2025 and covering about 87% of FBS games played in that span — 3.5M player_stats rows across 7,166 games. The uncovered ~13% is a structural gap, not partial rollout: FBS-vs-FCS "buy games" aren't carried at all today, since FCS opponents are out of scope.

2026-08-04

  • NFL Elo ratings are live and queryable. GET /v1/teams/:id/elo now returns real ratings for all 32 NFL teams, backed by a full historical recompute (3,386 elo_history rows across the 2020-2025 seasons). This was blocked until today by a same-city team-linking bug: the 2025 season had every New York Giants game attributed to the Jets and every Los Angeles Chargers game attributed to the Rams, so an Elo computation would have rated the wrong participants. The bug is fixed (both in the ingest code and in the 34 already-wrong match rows), so ratings now reflect who actually played.
  • The dashboard shows your API key and first call before anything else. The key reveal and the ready-to-run first-call card now render as the first thing you see after signup, ahead of onboarding prompts and account setup. If you've felt like the key was buried under other UI on your first visit, it isn't anymore.
  • Signup now captures which sports you're building for, from more than one signal. The platform reads both an explicit intent you declare and attribution from how you arrived (landing page, referrer), and supports declaring interest in more than one sport. This powers more relevant onboarding and, going forward, more relevant product updates for the sports you actually use.
  • New: an agent-oriented quickstart on the dashboard. Alongside the existing curl-based first call, the dashboard now offers a copy-paste snippet built around the @bigballsdata/sdk npm package, aimed at developers wiring the API into an AI agent or IDE assistant rather than a terminal. Proof-of-concept, feedback welcome.
  • Homepage and landing pages no longer reference the 2026 World Cup as current. With the tournament over, the marketing pages that featured live/upcoming World Cup framing have been updated to reflect that it's concluded.

2026-08-03

  • Gateway errors now return a real 503 for transient database issues, not a raw 500. A momentary connection-pool exhaustion or statement timeout previously surfaced as an unhelpful generic 500. It now returns 503 with retry guidance, which is the honest signal for a transient condition worth retrying.
  • GET /v1/matches?status= accepts the status vocabulary from our other endpoints, not just its own. upcoming, not_started, in_progress, ongoing, final, and ft are now aliased to this endpoint's canonical scheduled/live/finished values, so a term that already works on /v1/wc2026/matches or live-stats no longer 400s here. The error message for a genuinely invalid value now names the exact value you sent.
  • Marketing pages now pull their sport and league counts from GET /v1/coverage, live, instead of a hardcoded number. Pages that advertise "N sports" no longer drift out of sync with what the API actually serves.
  • New: /tools/odds-calculator, a free, no-signup odds converter and payout calculator. Convert American, decimal, and fractional odds, see implied probability, and calculate payout on a stake, entirely client-side. A documented reference implementation of the exact conversion math, not a live odds feed.
  • Background workers no longer silently stall on a Redis hang. The hourly job that closes out stale live matches, and the live-stats and score-update workers, are now resilient to a dead or slow Redis connection. Previously a Redis hang could leave finished games showing as live far longer than expected; both are now isolated so one failing dependency doesn't block the others.

2026-07-27

  • New route_not_found error code, plus sport-prefixed route aliases. A request to a path that doesn't exist now gets a distinct route_not_found (404) instead of being confused with a valid-route-but-missing-resource error. Common URL-guessing mistakes for leagues, matches, and countries now resolve via alias instead of 404ing.
  • GET /v1/sports and GET /v1/coverage no longer require an API key. Both are now public (IP rate-limited), so you can explore what the API covers before you ever sign up.
  • Errors for a mismatched player ID now name the real cause. Passing a bdl_player_id or af_player_id from our own /v1/matches lineups or /v1/teams/{id}/squad responses into /v1/players/{id} now says so explicitly, and points you at /v1/players?name={name} to get the right Big Balls id first. The same clarity now extends to invalid, non-UUID ids passed to team, NBA lineup, and prediction endpoints, with a suggested_fix pointing at the right discovery endpoint.
  • /v1/matches/:id/odds and /v1/matches/:id/events now fall back to stored data instead of returning nothing. Previously these returned null if live odds or events weren't available, even when the data existed in storage. The odds endpoint now falls back to aggregated, vendor-free stored odds; the events endpoint serves stored match events for non-soccer sports before attempting a live fetch.
  • New: activation recovery emails. Signups with no API call in their first 24 hours, or a high error rate across their first several calls, now get an email with a working example tailored to their declared sport and common failure patterns.

2026-07-26

  • Sport entitlements are now enforced, not just modeled. Roughly 119 routes now gate access by your plan: request a sport your plan doesn't include and you get a 403 naming the fix. NBA play-by-play (GET /v1/nba/games/:gameId/plays) and shot coordinates (GET /v1/players/:id/shots) are gated as one Edge-tier unit, and /intelligence/value-finder plus WebSocket connections narrow to your entitled sports rather than blanket-403ing.
  • MLB scores now agree between list and detail views. Scheduled games no longer show a fabricated 0-0 in one view and null in the other; both now correctly show null before kickoff and the real score once the game starts.
  • Finished and live matches derive their score from linescore, not stale match_stats. A match with a complete linescore no longer risks showing 0-0 because a separate stats field hadn't caught up. /v1/matches and /v1/matches/:id now sum the per-period linescore when it's available.
  • /v1/odds/divergences no longer republishes verbatim vendor odds. The raw per-provider price fields are gone from the response, in line with vendor redistribution terms. The computed disagreement signals (category, secondary_categories, magnitude) are unaffected, still our own data.
  • Catalogue endpoints no longer claim confidence: 1 for an unknown source. /v1/sports and /v1/leagues now correctly report source: 'internal-catalog' for our own data; a genuinely unknown source now reports confidence: 0 instead of a misleadingly confident 1.
  • NCAAF coverage claims now match what's actually there, and every sport must declare its data types explicitly. The /ncaaf-api page states real figures (91% of finished games carry multi-book odds, no college football play-by-play, box scores for roughly 13% of games), and the coverage manifest now requires every sport to declare each data type rather than allowing silent gaps. A new ratings data type reports live for NBA and NHL, partial for soccer.
  • Cricket team lookups now search across every league in the sport, not just one. Previously a team lookup was scoped to a single league, which could mint a duplicate team when two data sources filed the same club under different league ids.
  • GET /v1/matches/:id no longer hangs for up to 30 seconds when live enrichment is slow. Each field fetch is now capped at 2 seconds; on a timeout or missing live coverage, the endpoint serves the stored row with an honest coverage note instead of leaving you waiting.
  • /v1/matches/:id/odds and /v1/matches/:id/events no longer require ?sport=. Both now infer the sport from the match id itself.
  • NFL team identity bug fixed at the source. The New York Jets/Giants and Los Angeles Rams/Chargers were being resolved to the same team by a same-city name match with no franchise qualifier. New 2025-season data now links to the correct team; see 2026-08-04 above for the historical data repair.
  • Boxing fighter bios now available: GET /v1/athletes?sport=boxing. Fight cards and bout results are still coming soon and are marked as such; profile data (name, nationality, physicals, weight class) is live today.

2026-07-25

  • MLB games no longer show a fabricated 0-0 before kickoff. A scheduled game's linescore now correctly returns null instead of a placeholder {"home":[0],"away":[0]} that read as a real, in-progress score.
  • Email sign-in accepts the full code length Supabase issues, not just 6 digits. The sign-in form previously hardcoded a 6-digit expectation and truncated or rejected longer codes before they reached Supabase. It now accepts the full 6-10 digit range and passes the code through unmodified, fixing sign-in for anyone who received a longer code.
  • MMA coverage now states what it actually serves: fight cards, bout matchups, and odds, not results. The /mma-api and /ufc-api pages previously implied bout results, fighter profiles, and win-loss records were available. They aren't yet; both pages and the coverage manifest now mark results as coming soon rather than claiming them.
  • Formula 1 coverage now states what it actually serves: the 2026 calendar, nothing else yet. No scores, stats, or historical data exist for F1 today; the /formula-1-api page and coverage manifest no longer imply otherwise.
  • New: a Nevada State Athletic Commission boxing results scraper (dry-run only). Parses NSAC boxing PDFs for bout results, method, round, and judges' scorecards, successfully extracting 13 events and 108 bouts from 2025 test data. No database writes yet; results are scoped to US state athletic commission public records (Nevada first), not bouts sanctioned outside the US.

2026-07-24

  • TypeScript SDK: install @bigballsdata/sdk (0.1.1). The SDK now ships under its correct name — npm install @bigballsdata/sdk (or pnpm install @bigballsdata/sdk). This release fixes the two bugs that made the earlier @bigballsports/[email protected] unusable: the client now defaults to the correct base URL https://api.bigballsdata.com (0.1.0 defaulted to a domain we don't own, which parked instead of answering), and the published package now resolves on install — its main/types/exports point at the shipped dist/, so import { BigBallSportsClient } from '@bigballsdata/sdk'; works from a clean project. One import, every sport. If you installed @bigballsports/sdk, switch to @bigballsdata/sdk — the old package is not maintained.

  • Sign in from a different device than you requested the link on. Email sign-in now also gives you a 6-digit code. Request the link on your laptop and you can finish signing in on your phone (or the other way round) by typing the code, instead of being forced to open the link on the exact device that requested it. The emailed link still works as before — the code is just an additional path for when the link lands on the wrong device.

  • New: the /attribution page — the data sources behind the API, credited. A public, footer-linked page now lists the licensed open datasets we build on and the exact credit each one's license requires. It's where source and license notices live as we bring more open data online.

  • The /hockey-api and /nhl-api pages now show real, working examples. Both pages were rebuilt so every endpoint and code sample is copy-paste-runnable against live data — 12 seasons of NHL coverage, xG leaders, and samples that actually return what they show. If a sample on those pages didn't work for you before, it does now.

2026-07-19

  • New: GET /v1/leagues/{id}/xg-leaders, expected-goals leaderboards for the big-five European leagues. One call returns a season xG leaderboard for epl, laliga, serie-a, bundesliga, or ligue-1 — the same league codes /v1/leagues/{id}/top-scorers already takes. Each row carries xg, xa, npxg, xg_chain, xg_buildup, goals, assists, shots, key_passes, matches, minutes, and a derived goals_above_xg (goals minus xG) so you can see finishing over- and under-performance directly. Sort with ?stat=xg|xa|npxg|goals|assists|shots|key_passes (default xg), and narrow with ?season=, ?min_minutes=, ?limit=. History runs back to season 2014, well before our match coverage starts, so this is usable for long-range model training. Refreshed daily.

    Two things to know before you build on it. Rows are keyed by player_name, not by a BigBalls player_id — the entity bridge between our xG source and our players table is not built yet, so we do not offer per-player or per-match xG endpoints and you should not join these rows to /v1/players/{id} on identity. And any season row we cannot attribute to a league is excluded from the board rather than bucketed somewhere convenient, so a leaderboard only ever contains players from the league you asked for.

2026-07-16

  • New: GET /v1/coverage, one call that tells you exactly what we have. A single request now returns a full, DB-derived coverage snapshot: every sport and league with data, the canonical name plus aliases, a copy-paste example_call, the seasons held, freshness (data_through, next_event, last_ingested), and a per-datatype breakdown (scores, player_stats, player_season_stats, odds, player_props, team_stats, and more) with a coverage percentage and a derived status (current, in_season, historical, thin, none) for each. It is generated from the actual rows, not a hand-maintained list, so it cannot drift from reality: a datatype we do not have reports an honest 0% rather than a false badge. Narrow to one sport with ?sport=, which accepts the canonical name or any alias. This is the fastest way to see what is worth integrating before you write a line of code.

  • NCAAF player season stats are now reachable. GET /v1/players/{id}/stats?sport=american_football now returns college football players' season stat lines (passing, rushing, receiving, and more), grouped by season with a level: "season" label so you always know you are reading season aggregates, not a per-game log. This surfaces a large body of NCAAF season data that was in the store but had no discoverable path to it.

  • Consistent sport and league names across the API. Sport and league naming is now resolved through one canonical registry, so the same name and aliases work everywhere. GET /v1/sports now returns all nine covered sports (cricket, MMA, boxing, and Formula 1 are now visible alongside the rest), and unknown values like ?sport=rugby now return a clean 400 instead of an empty result. If you were special-casing name variants on your side, you can rely on the canonical names and aliases now.

2026-07-13

  • verification is now evidence-based — the values changed. On GET /v1/odds/historical and GET /v1/odds/closing-lines, the per-line verification field previously returned two_source_verified whenever we had no divergence on record. That was wrong: no divergence row usually means we never compared that line, not that two providers agreed — so a badge could appear on a line we never actually checked. It now returns one of four honest statuses: two_source_agreed (we compared two providers and they matched — backed by a real comparison record), two_source_diverged (we compared and they disagreed, with divergence_categories; this replaces divergence_flagged), single_source (only one provider carries this book/side, or the book has no cross-provider match — not verified), and unverified (we have not compared this line — not verified). If you were treating two_source_verified as "trusted", note that most of those lines now correctly report single_source or unverified; only two_source_agreed is a positive cross-check. Documented at /docs/odds.

2026-07-09

  • Unknown player ids now return 404, not an empty 200. GET /v1/players/{id} used to answer with a 200 and meta.coverage set to false in two different situations: a player who exists but has no data for the requested sport yet, and an id that matches no player at all. The second case now returns a 404 with error code not_found and a suggested_fix that points you at /v1/players?name= to look the player up by name. A real player who simply has no data for the requested sport still returns the same 200 with meta.coverage false as before, because that is a legitimate answer and not an error. This is a small error-contract change: if you were treating an empty 200 on this endpoint as "not found", you can now rely on the status code instead.

  • World Cup knockout fixtures now consolidated to one row each. GET /v1/wc2026/matches could previously return two rows for a single knockout game, with odds and scores split across them. Each knockout fixture is now a single canonical row carrying its full odds history, correct teams, and final score, so match and bracket reads are complete and consistent from the Round of 16 onward.

  • GET /v1/stored-matches/:id now returns complete current odds. The endpoint reads current lines from a dedicated latest-odds table, removing the previous 500-row cap and reporting the correct bookmaker count. The head-to-head field is also corrected for h2h matchups.

  • Mobile layout fixes on the marketing site. The navbar and hero no longer cause horizontal scrolling on small screens, and code samples now scroll within their own blocks instead of stretching the page.

2026-06-28

  • World Cup bracket odds are faster and no longer time out under load. GET /v1/wc2026/matches reports the current moneyline for all 104 fixtures. It used to compute that from the full append-only odds history (tens of millions of rows), which during a burst of live writes in the Round of 32 could run past two minutes and fail with a 500. Current odds now read from a compact table that holds one row per match, market, book, and side, refreshed as each new line is written. The endpoint returns in well under 100ms and stays up while odds are streaming in fast. The response shape is unchanged, and the line-movement and historical odds endpoints still read the full history, so nothing there changed.
  • Paused API keys now return a clear 403. If we pause a key, which is different from revoking it, requests get a 403 with error code KEY_PAUSED and a plain-language message, so you can tell a temporary pause apart from a revoke or a quota 429. It takes effect within the 60-second key cache. Most keys will never see this; it exists so that on the rare occasion we pause a key, the response says exactly what happened instead of failing in a confusing way.

2026-06-27

  • Golden Boot leaderboard now returns real scorers. GET /v1/wc2026/golden-boot was returning an empty leaders[] even with matches played, because it read a topscorers blob whose ingest job was never scheduled. It now aggregates the top 20 scorers from per-match fixture player stats (the table that is actually refreshed daily). Players tied on goals share a rank (DENSE_RANK), and the list is ordered by goals, then assists, then fewest minutes. Each leader carries country (the national team's full name, for example "Argentina", not an ISO code), team_id/team_name, a nullable player_id, and goals/assists/minutes. See /docs/wc2026.

2026-06-26

  • World Cup knockout rounds now read from the result, not the calendar. GET /v1/wc2026/matches was labeling each knockout match's bracket.round by inferring it from the kickoff date. That broke whenever the schedule shifted. The handler now reads the authoritative FIFA stage stored on the match, so the round value is correct regardless of date. Possible values: R32, R16, QF, SF, THIRD (third-place playoff), FINAL. Same change also filters out a small number of phantom placeholder rows that could appear before the group stage resolves. If you were mapping rounds yourself from dates, you can stop and trust bracket.round.
  • Dashboard quota card v2. The free-tier usage card on your dashboard now shows a circular gauge of today's percentage of the daily cap, with four bands (under 50% is just the number, 50-79% a soft notice, 80-99% a warning, and at/over the cap a clear "throttled until 00:00 UTC" state). Every band shows your real usage against the next tier's real cap and price, so the upgrade math is explicit, not a nag. Dashboard only; no API change.
  • World Cup 2026 player data is flowing reliably. The WC2026 ingest worker (the pipeline behind tournament player and team data) is running stable ahead of the Round of 32. No endpoint or shape change; this is an operational note that the WC2026 player surfaces are kept current as the tournament progresses.

2026-06-21

  • Abuse protection: per-key 4xx circuit breaker. Each API key now has a circuit breaker that watches its client-error (4xx) rate, separate from the request quota. Over a rolling 5-minute window it trips if a key sends 500 client-error responses, or if 80% or more of at least 50 requests are 4xx. On a trip the key gets 429 for a 120-second cooldown, even if it has quota left. This is abuse protection, not a punitive throttle: successful traffic never counts toward it, so normal usage is unaffected, and it clears itself once the broken requests stop. A breaker 429 carries Retry-After, X-RateLimit-4xx-Cooldown, and X-RateLimit-4xx-Reset headers so you can tell it apart from a quota 429 (which carries neither). If you see it, read the error.message and suggested_fix on your 4xx responses and fix the request rather than blindly retrying. Documented at /docs/rate-limits.

2026-06-20

  • Soccer team match statistics. Every finished match now carries a full per-team stat line: possession, shots (on/off target, total, blocked, inside/outside box), fouls, corners, offsides, cards, goalkeeper saves, passes (total, accurate, pass %), and expected goals (xG) where the competition provides it. Fetched from the upstream feed within ~30 minutes of full-time and stored, so reads are fast with no upstream wait. Available on GET /v1/matches/{id}/statistics (Pro plan) for the top-five European leagues, MLS, and the Champions League. The linescore stays authoritative for goals; this fills the team stat line that match previews, recaps, and models need.
  • Odds now tell you how verified they are. Every line on the historical and closing-line endpoints (GET /v1/odds/historical, GET /v1/odds/closing-lines) now carries a verification field. two_source_verified means both of our odds providers priced that book, market, and side and they agree. single_source means only one provider priced it (or the book has no cross-provider match). divergence_flagged means the two providers disagree, and a divergence_categories list tells you why (PRICE_DIFF, LINE_DIFF, MISSING_FROM_ONE, STALE). It is the two-source cross-check made visible on every line, so you can see at a glance which numbers earned two independent confirmations and which to treat with caution. The verification field is live on both endpoints, and GET /v1/odds/divergences (Pro plan) returns the current disagreements directly. Documented at /docs/odds.

2026-06-19

  • Two-source odds verification. We now run a cross-source check every 15 minutes, comparing our two independent odds providers for the same match, market, and book across the verified overlap books (BetMGM, DraftKings, FanDuel, Bovada, BetOnline, Lowvig) and flagging where they disagree. Four categories: PRICE_DIFF (the juice differs), LINE_DIFF (the line itself differs), MISSING_FROM_ONE (only one provider carries it), and STALE (one feed is lagging). Surfaced via GET /v1/odds/divergences (Pro plan). Game lines only. This is the Honesty Architecture principle as a feature: it catches stale lines, upstream errors, and sharp-money asymmetries before they reach you.

2026-06-18

  • Historical odds, opening line to closing line. We are now capturing historical odds snapshots for completed matches: the opening line (about a week before kickoff), the 24-hour line, and the closing line, each stamped with the board's as-of time (snapshot_type and snapshot_at on odds data). Coverage reaches back to 2020 and is rolling out across the major sports (NBA, NHL, MLB, NFL, and the top soccer competitions). This is the raw material for closing-line value, line-movement analysis, and sharp-money signals.
  • Closing lines as games start. Phase 2 captures the closing line for upcoming games as they near kickoff: a 5-minute cron snapshots the current board in the roughly 10-minute window before the start as the closing line (snapshot_type closing, snapshot_at = capture time). Closing-line coverage grows continuously from here, not only through backfill, which is exactly what closing-line value (CLV), line-movement intelligence, and sharp-money detection depend on.
  • Current odds across 17 sports and leagues. Phase 3 polls the live odds board every 30 minutes for NFL, NCAAF, NBA, NCAAB, WNBA, MLB, NHL, the top European soccer leagues (EPL, UEFA Champions League, La Liga, Bundesliga, Serie A, Ligue 1), MLS, MMA, and ATP/WTA tennis. This broadens current-odds coverage well beyond the World Cup, on a roughly 30-minute refresh.
  • Query historical odds and closing lines. Two endpoints are now live: GET /v1/odds/historical (Edge plan) returns every snapshot for a match (opening, 24-hour, closing) for closing-line-value work, and GET /v1/odds/closing-lines (Edge plan) returns closing lines across a sport and date range. Each odds row carries the snapshot type, the as-of time, and the sportsbook.

2026-06-17

  • World Cup matches endpoint stays lean under load. GET /v1/wc2026/matches now loads only the latest line per book and participant when it refreshes (about 3,200 rows instead of the full multi-million-row odds history), so a burst of traffic no longer spikes memory during the recompute. Same response shape, same odds.
  • Clearer errors on bad input. A malformed ?date= on GET /v1/wc2026/matches now returns a clear 400 that names the expected format instead of failing in a confusing way, and every sport-filter validation error across the API now carries a suggested_fix telling you exactly what to change.
  • We measure before we claim. Last week we added a database index to speed up that matches query. Under real production load the planner ignored it, so it carried a write cost without the promised speedup. We removed it and reframed the work as the memory fix above, which is what actually held up. We would rather correct a claim than keep a number that does not survive a real query plan.

2026-06-16

  • World Cup event data is complete. GET /v1/matches/{id}/events now reads across both the soccer and World Cup event tables in a single query, so goals, cards, and substitutions come back for World Cup 2026 matches (including the Stanley Cup and knockout fixtures that previously returned an empty list), not just the league tables it covered before.
  • Champions League and MLS scores route correctly. GET /v1/matches?league=ucl and ?league=mls now resolve to the right upstream instead of returning nothing or Premier League data. UCL and MLS fixtures are queryable through the unified matches endpoint, the same way the European leagues already are.
  • Playoff series state on NHL and NBA matches. Match responses now carry a series block for postseason games (game number in the series, per-side wins, and the leader), so you can render "Game 6, Carolina leads 3-2" straight from the match payload without stitching the series together yourself. Live and upcoming games are accurate now; a fix for finished-game series state on the match-detail route is in progress.
  • Self-service errors on 36 more endpoints. The suggested_fix field, which lands on a 4xx when the fix is unambiguous, now covers 36 additional response sites across the API. More malformed requests now tell you exactly what to change, no support ticket required.
  • No more slow World Cup cold starts. GET /v1/wc2026/matches and GET /v1/wc2026/standings now serve from cache instantly and refresh in the background (stale-while-revalidate). A cache expiry no longer makes one unlucky caller wait several seconds for a recompute, so tail latency on these endpoints is gone.
  • Endpoint response contracts documented. A new reference spells out the exact response shapes for GET /v1/wc2026/matches and GET /v1/predictions: the { total, matches } envelope (not a bare array), the knockout placeholder rows and how to detect them, the Solo-plan requirement on predictions, and the client timeout we recommend. Read it before you parse either response.
  • World Cup per-player tournament stats. GET /v1/wc2026/players/{id}/tournament returns a player's aggregated tournament totals (goals, assists, cards, minutes) with a per-match event breakdown.

2026-06-12

  • Top scorers by league. GET /v1/leagues/{id}/top-scorers returns the scoring leaders for the Premier League, La Liga, Bundesliga, Serie A, and Ligue 1, with goals, assists, minutes, and matches per player. Sort with ?category=goals|assists|minutes|matches. Team names resolve through a direct join, so every row carries a clean club name.
  • Kickoff time on predictions. GET /v1/predictions responses now include kickoff_utc. You no longer need a second call to the schedule endpoint just to get the match time.
  • World Cup matches are cache-hardened. GET /v1/wc2026/matches no longer returns an empty list during a deploy or a brief upstream hiccup. It never caches an empty response, serves the last known-good snapshot when a refresh comes back empty, and warms on boot. The intermittent empty results some callers saw around launch-day deploys are gone.

2026-06-11

  • FIFA World Cup 2026 is live. Every fixture across groups A through L, with the schedule, live group standings, match predictions, value picks, odds, and Elo ratings. Start at /world-cup-2026-api.
  • World Cup scores land at full-time. Final scores now populate within a cron tick of the final whistle, usually a minute or two after the match ends. The scores worker now reads the upstream soccer linescore shape correctly.
  • Consistent post-match data. The post-match pipeline (events, Elo, standings, grading, and group advancement) now reads scores from a single source of truth, matches.linescore. Those surfaces no longer disagree in the minutes right after a result lands.
  • Errors that help you fix them. Every 4xx and 5xx now returns a suggested_fix (when the fix is unambiguous), a docs_url, and a request_id, so you can self-serve most integration mistakes without opening a ticket.

2026-06-10

  • Soccer player club form. GET /v1/players/{id}/club-form returns a player's club season stats (appearances, goals, assists, minutes, rating) across the top 5 European leagues (EPL, La Liga, Bundesliga, Ligue 1, Serie A) for 2025-26. 3,853 players covered, and WC2026 squad players link automatically.
  • Multi-season club form. Add ?season=2024 to scope club form to a single prior season for career context. Omit it for every captured season.
  • NHL 2025-26 season complete. All 1,389 NHL matches this season, including all four Stanley Cup Final games, are now fully linked and queryable end to end.

2026-06-09

  • NBA shot chart API. The shot chart page is live at /nba-shot-chart-api. Pull any player's 2025-26 shots with court X/Y coordinates, shot type, and make/miss via GET /v1/players/{id}/shots (235k+ shots and counting).
  • NFL and soccer API pages enriched. Added query-targeted FAQs to the NFL and soccer API landing pages, answering the integration questions developers search for most.
  • Security hardening. Authentication redirect validation tightened.

2026-06-08

  • WC2026 live group standings. GET /v1/wc2026/groups returns all 12 groups (A-L), 4 teams each, updated within 5 minutes of each matchday result. Group advancement probabilities (Monte Carlo, Elo-driven Poisson model, 10k sims) populate from June 11. The Monte Carlo model is calibrated against 301 WC matches (2002-2022): G=2.73 goals/match, C=186 Elo/goal.
  • Player trophy history. GET /v1/players/{id}/trophies returns a football player's career honours from the upstream football database, with a top-level total_trophies win count. Cristiano Ronaldo has 54 titles, Lionel Messi 65. Football-only; non-soccer players return an empty list.
  • Team playoff records. GET /v1/teams/{id}/playoffs returns an NBA team's playoff record by season (the Boston Celtics went 16-3 in 2023-24).
  • Player transfer history. GET /v1/players/{id}/transfers returns a football player's club moves with fees. Kylian Mbappé: Monaco to PSG for €180M, then to Real Madrid on a free.
  • NBA shot coordinates. GET /v1/players/{id}/shots returns a player's shot log with court coordinate_x/coordinate_y, shot_type, and made/miss. 235k+ shots from 2025-26 including the Finals. Free throws are excluded by default (?include_ft=true to keep them). Build a shot chart or a shooting heat map straight from the JSON.

2026-06-07

  • NBA player game log. GET /v1/players/{id}/gamelog returns flat per-game NBA stat lines, most recent first (last 20 games by default, up to 50). Filter by postseason and season.
  • WC2026 squads. GET /v1/wc2026/teams/{id}/squad returns a nation's full 26-player squad, ordered by position group (goalkeepers, defenders, midfielders, forwards) then shirt number.
  • National team World Cup history. GET /v1/teams/{id}/world-cup-history returns a nation's tournament-by-tournament record from 2002 through 2022: finish, games played, win/draw/loss, and goals for/against.
  • Team Elo history. GET /v1/teams/{id}/elo/history returns a team's Elo trend line, a baseline seed plus one point per settled match, so the rating grows as matches play out. Optional from / to date filters.
  • NBA player career averages. GET /v1/players/{id}/career returns season-by-season career averages (7 NBA seasons, 2019-20 through 2025-26), one row per season and team. NHL players get the hockey equivalent.
  • WC2026 line movement. GET /v1/wc2026/matches/{id}/line-movement returns the chronological odds history per market and book, so you can chart how a line moved.

2026-06-06

  • TypeScript SDK published. @bigballsports/[email protected] is live on npm: pnpm install @bigballsports/sdk or npm install @bigballsports/sdk. One import, every sport. Superseded by @bigballsdata/[email protected] (2026-07-24) — install @bigballsdata/sdk instead. @bigballsports/[email protected] had a wrong default host and a broken install and is no longer maintained.
  • Developer blog live. The WC2026 API launch guide is at /blog/wc2026-api-launch: what's live, what's coming, and 3 example calls to get started.
  • NHL historical standings captured. Past-season NHL standings for 2021 through 2024 (128 rows) now load into the canonical store, the groundwork for season-over-season standings comparisons.
  • NHL season averages from raw box scores. Per-player NHL season averages (4,861 rows) are now computed directly from the captured raw box scores, so the matchup's per-player season numbers stay current without leaning on a stale upstream stats feed.
  • WC2026 prediction calibration and value-finder persistence. A grader now scores the Elo match predictions against actual results to track calibration, and value-finder opportunities are written to storage (57 predictions persisted so far) instead of recomputed on every request.
  • WC2026 knockout bracket fully structured. All 32 knockout fixtures from GET /v1/wc2026/matches now carry a bracket block with round, home_feeder, and away_feeder. Projected team names stay suppressed until the groups resolve; the feeder description shows instead (for example "Winner Group A").
  • WC2026 squad data ingested. All 48 World Cup squads (1,248 players) are loaded, the data behind the upcoming GET /v1/wc2026/teams/{id}/squad endpoint.
  • WC2026 team tournament history computed. 141 tournament records across the 2002 through 2022 World Cups are now in the store (France champions in 2018, Argentina in 2022), for historical context on each nation.
  • Historical season guard. Requesting a past season the API does not hold (for example ?season=2023) now returns an explicit error instead of silently falling back to current-season data.

2026-06-05

  • World Cup 2026 odds are live. GET /v1/odds?tournament=wc2026 returns moneyline, handicap, and totals for all 104 WC2026 fixtures. Mexico vs South Africa (the June 11 opener) has lines now; the remaining 103 matches populate as sportsbooks open their markets.
  • NHL goalie stats split by game type. The matchup now exposes separate regular-season and playoff save percentages for each goalie. Frederik Andersen sits at .931 across 12 Stanley Cup Final games, Carter Hart at .924 across 16 Western Conference Final games.
  • NBA active rosters live. All 30 NBA teams are seeded in roster_memberships (530 players, 2025-26), so the NBA matchup builds lineups from the authoritative active roster instead of the historical player pool.
  • Games endpoint accepts date ranges. GET /v1/games?sport=nba&date=recent returns the last 7 days of finished games in one call, and date=YYYY-MM-DD:YYYY-MM-DD range queries are supported up to 31 days.

2026-06-04

  • NBA starting lineups live. Starters and positions for all 1,291 games of the 2025-26 NBA season, including playoffs, via GET /v1/nba/games/{id}/lineups.
  • NBA playoff player data fixed. Lineup and box score gaps for postseason games (Conference Finals, NBA Finals) are resolved via direct BDL game bridging. All playoff games are now queryable via the lineups and stats endpoints.
  • Matchup endpoints shielded from cache stampede. GET /v1/nba/games/{id}/matchup and GET /v1/nhl/games/{id}/matchup now collapse concurrent cache-miss recomputes into a single database query, so a burst of requests at the cache-expiry boundary no longer piles onto Postgres.
  • Play-by-play groundwork. NBA and NHL play-by-play events (including shot coordinates) now ingest into the canonical store, the foundation for upcoming shot-quality and possession analytics. No public endpoint yet.

2026-06-03

  • World Cup 2026 knockout bracket is queryable. GET /v1/wc2026/matches now returns all 104 fixtures, the 72 group matches plus the full Round of 32 through Final bracket. Knockout matches carry null teams until the group stage assigns them after June 28.
  • NHL matchup rosters come from the roster store. GET /v1/nhl/games/{id}/matchup now builds active rosters from the new roster_memberships table instead of inferring from recent box scores, so full squads render (the Vegas Golden Knights roster is complete again).
  • Player-prop value finder. GET /v1/intelligence/value-props flags player props where the empirical hit rate beats the de-vigged market line. window=upcoming (default) covers the next 24 hours; window=all runs a historical backtest with record, ROI, and average edge. Markets: points, rebounds, assists, points_rebounds_assists, threes.

2026-06-02

  • NBA Finals ingested. New York Knicks vs San Antonio Spurs, with the full series schedule loaded and Game 1 on June 3. Scores and GET /v1/nba/games/{id}/matchup resolve for every game of the series.
  • NHL playoff series object. GET /v1/nhl/games/{id}/matchup now returns match.stage plus a series block: the game number in the series, per-side wins from finished games, and a human-readable series_record (for example "NYR leads 2-1" or "Series tied 1-1"). It is null on regular-season and preseason matchups.
  • Postseason games staged correctly. Every NBA and NHL postseason game now carries the right stage tag (playoffs vs regular), so playoff fixtures are no longer mislabeled as regular season on live pages or in the matchup series block.
  • NHL player props live, with hit rates. 5,168 prop rows loaded across 5 sportsbooks and 6 markets: shots_on_goal, goals, assists, points, blocked_shots, and power_play_points. GET /v1/nhl/players/{id}/props?market=... returns the current line across books (best book per side) plus a rolling last-N-games hit rate for that market. Hit rates resolve across both player-id twins, so they fill in regardless of which id you start from.
  • NBA player props backfilled. 14,373 historical prop rows loaded across 9 sportsbooks, giving GET /v1/nba/players/{id}/props deeper line history behind its hit rates.
  • Player injuries live for NBA and NHL. 127 NBA and 102 NHL players currently on the report, refreshed every 30 minutes. GET /v1/injuries?sport=basketball|ice_hockey returns the full league list (one row per player, deduplicated) and GET /v1/players/{id}/injury returns a single player's status, injury type, expected return date, and note. Both join on the same player_id you use everywhere else.
  • Matchup responses now carry injury status. GET /v1/nba/games/{id}/matchup and GET /v1/nhl/games/{id}/matchup surface each player's current injury status inline, so the preview and the health check arrive in one call.
  • NHL box scores, captured in full. Per-game NHL skater logs keep backfilling into the canonical store (this is what powers the new prop hit rates), and the ingest now captures the complete raw box-score payload (all 37 fields the upstream returns), not just the subset surfaced today. Storage is cheap; missing data is permanent.
  • NBA advanced, general, and clutch stats captured. Per-season NBA general, advanced, and clutch stat splits are now loading into the canonical store, the groundwork for richer NBA player and team analytics.
  • Standings refresh within 60 seconds of a final whistle, across all 11 leagues. An event-driven worker picks up the match:finished signal and re-ingests standings on the next 30-second tick, down from a daily cron that could lag up to 24 hours.
  • World Cup 2026 knockout bracket loaded. All 32 knockout fixtures (Round of 32 through the Final) are in the schedule alongside the 72 group matches, and GET /v1/wc2026/matches now returns the full 104-match bracket. Knockout matches carry null teams until the group stage assigns them.
  • World Cup 2026 tournament leaders. GET /v1/wc2026/leaders returns the top scorers (rank, goals, assists, and games played per player) with an explicit tournament_state: pre_tournament before the June 11 opener, then in_progress, then finished.
  • World Cup 2026 weather live for all 16 stadiums. GET /v1/matches/{id}/weather returns the match-time forecast (temperature, precipitation, wind, condition) for every WC2026 venue.
  • NHL trending players. GET /v1/intelligence/trending?sport=nhl now surfaces hot and cold NHL players by goals, assists, and shots, mirroring the NBA trending signal.
  • Three more sportsbooks. Rebet, BetParx, and Betway are now in the books reference, so their lines surface in odds and props alongside the existing books.
  • Provenance labels corrected. The meta.source field on the weather and golden-boot endpoints now reports accurate provenance.
  • Seven new sport-level API pages. Dedicated landing pages for basketball, hockey, baseball, American football, cricket, boxing, and Formula 1, each documenting the sport's endpoints and cross-linking to the underlying league pages.
  • Data-health monitoring. An hourly job now checks that marquee data is present and correctly tagged: a championship game exists during each title window, no game is mislabeled regular-season inside a postseason window, and high-stakes games are not single-sourced. Gaps surface to us before they reach a live page.
  • Roster history groundwork. A new roster_memberships table records player-to-team membership over time, the foundation for accurate historical rosters and lineups.

2026-06-01

  • NHL Nerd-view reaches NBA parity. /v1/nhl/games/{id}/matchup now surfaces the same depth as the NBA composite: multi-book moneyline odds per sportsbook, team Elo ratings, goalie save percentage, penalty kill percentage, season statistical leaders via the sibling /v1/nhl/leaders endpoint, and a goalie depth chart. The underlying Elo seed runs across every NHL team, so /v1/teams/{id}/elo and the value-finder answer for ice_hockey today.
  • NHL standings fix. Vegas Golden Knights now returns the correct VGK short_name across /v1/standings?league=nhl, team profiles, and every downstream intelligence endpoint that joins on team identity. Corrected at the database so consumers see consistent Vegas data without a client-side patch.
  • NHL standings deduplicated. /v1/standings?league=nhl no longer surfaces duplicate team rows. Ten team rows that carried stale duplicates were corrected at the database; downstream consumers see one row per franchise.
  • Homepage coverage matrix caught up. The Ice Hockey Predictions cell on the homepage matrix flipped from "Coming soon" to "Live" to reflect today's NHL Elo seed shipment. Five other stale cells were audited and filed for a separate accuracy sweep.
  • WC2026 hero countdown. /world-cup-2026-api shows a live days/hours/minutes ticker counting down to the June 11 opener, refreshes every minute, collapses to "Tournament live now" once kickoff lands.
  • WC2026 Golden Boot endpoint. GET /v1/wc2026/golden-boot returns the tournament-wide top-scorers leaderboard with explicit lifecycle state: pre_tournament before June 11 (leaders empty), in_progress during the tournament, finished after the final.
  • Live win probability endpoint. GET /v1/wc2026/matches/{id}/live-probability returns pre-match Elo baseline, live-adjusted probability, and percentage-point delta per outcome. 15-second cache.
  • Per-sport coverage index at /docs/sports. New page listing every supported sport with its coverage level (Live, Historical, Partial), three to five key endpoints, and a link to the per-sport landing page. Useful for picking a starting point when integrating a new sport.
  • NHL Player Props teaser at /nhl-props-api. Endpoint shipping for the Stanley Cup Finals. The page documents the planned shape (five markets: shots_on_goal, goals, assists, points, blocked_shots), the cross-book aggregation contract, and lets agents integrate against the schema before launch.
  • WC2026 Soccer Props teaser at /soccer-props-api. Player props for the World Cup 2026 opener on June 11. Four markets at launch (anytime_goalscorer, to_score_2plus, assists, shots_on_target), books post these 24-48 hours before each fixture, and the page uses competition-scoped hit rates rather than misleading season denominators.
  • MLB Statcast teaser at /mlb-statcast-api. 2.1 million pitches loaded across the 2023, 2024, and 2025 seasons. Barrel rate, xwOBA, exit velocity, launch angle, and bat tracking (2024+) live in the database today; sprint speed and OAA pending. Public endpoint in development.
  • The competitor calculator now reflects real tier pricing. The Compare/calculator page (/compare/api-request-calculator) models a competitor's actual seven-tier plan structure (Free through Max, $0 through $2,499/mo) with per-second rate, monthly data-points quota, real-time vs delay, and WebSocket availability shown as chips on the competitor card. Calculator picks the cheapest fit against both peak rate and monthly cap.
  • NHL landing page now links to the props teaser via a new FAQ entry, and /v1/nhl/leaders joins the staticEndpoints list to match the EndpointExplorer entry.
  • MLB landing page retracts the stale "pitch-by-pitch granularity is not currently exposed" claim. The updated FAQ now references the 2.1M-pitch Statcast load and points at the /mlb-statcast-api teaser. Added a Statcast research card to buildIdeas.
  • NHL EndpointExplorer caught up. Added /v1/nhl/leaders entry (full field tree with rank, player, team, ice time, goals/assists/points/shots, xG, goals above xG). Expanded the matchup field tree with the three new fields shipped today: books[] for multi-book moneyline, season_stats.pk_pct for penalty kill percentage, and players[].season_avg.save_pct for goalie save percentage.
  • This page is live. /whats-new is the granular feed of recent improvements. Bookmark it. New entries land at the top whenever something ships.

2026-05-31

  • UCL + MLS live data shipped. /v1/standings?league=cl|mls and /v1/matches?sport=football&league=cl|mls now return real data via the upstream live-data partner (were misrouting to Premier League or returning empty).
  • Champions League and MLS API pages live. /champions-league-api and /mls-api now surface 6 entries each: DB-backed fixture archive plus live standings plus live matches.
  • Pre-tournament form populated. /v1/wc2026/matches/{id}.pre_match_form is now filled in BEFORE kickoff (was empty until ~2h pre-kickoff). Last 5 fixtures per team computed from the upstream football data partner.
  • /v1/matches season auto-defaults per league family. European-season leagues (cl, epl, laliga, bundesliga, serie_a, ligue1) and calendar-year leagues (mls) get sensible defaults. Override with ?season=YYYY.
  • /v1/matches pagination now works. ?page and ?limit apply as a post-filter slice. Previously silently ignored.
  • /v1/matches/{id}/events shape fix. Response now correctly keyed data.events (was data.stats). 16-second coverage:false latency also resolved.
  • Footer API Status badge. Green health dot plus "API Status" label in the marketing footer, linking to the live UptimeRobot status page.
  • Task #80 closed. CPV vs KSA kickoff time confirmed stable at 2026-06-27T00:00:00.000Z.
  • Auth resilience. Malformed API keys fast-reject before I/O. Legacy bbs_live_<48char> key format accepted alongside the modern <slug>_<hex> format.

2026-05-30

  • NFL EndpointExplorer. /nfl-api now ships an interactive 7-entry explorer covering schedules, plays (with EPA + Win Probability), and weekly player stats.
  • MLB EndpointExplorer. /mlb-api ships a 10-entry interactive explorer.
  • WC2026 group advancement. /v1/wc2026/group/{A-L}/advancement (per-team 1st/2nd/3rd/4th probability) surfaced in the WC2026 explorer.
  • Match weather endpoint covers all 16 WC2026 stadiums via Open-Meteo. Kickoff temperature, precipitation, wind, and normalised condition.
  • NHL xG loaded. Per-skater season summary loaded (Dataset #36). Goalie GSAx tier branch added.
  • Single-flight recompute prevents Postgres stampede at the cache TTL boundary on /v1/wc2026/standings and /v1/wc2026/matches.

2026-05-29

  • Standalone team Elo endpoint. GET /v1/teams/{id}/elo returns current Elo rating + league rank. Bets Chat Intel tab integration ready.
  • Value-finder shipped. GET /v1/intelligence/value-finder returns model-edge value opportunities across odds markets (Elo-implied probability vs market-implied, configurable edge threshold).
  • Quality tier endpoint. GET /v1/players/{id}/tier (1-6 scale with overall + position percentile). NBA + NHL coverage today.
  • EndpointExplorer rolled out to /nba-api, /nhl-api, and the five European soccer league pages.
  • Soccer xG ingest. Soccer player tiers now wired off xG90.
  • /status redirect. Internal /status now redirects to the public UptimeRobot page.
  • X-Cache header on four composite endpoints so consumers can verify cache hits.

2026-05-28

  • WC2026 group advancement endpoint shipped. GET /v1/wc2026/group/{group}/advancement — per-team finishing-position probability via 10,000-iteration Monte Carlo.
  • NBA leaders. GET /v1/nba/leaders returns statistical leaders with team affiliation.
  • NBA hot/cold movers. GET /v1/intelligence/trending ranks players by last-5 vs season average.
  • NBA player situational splits. GET /v1/nba/players/{id}/splits.
  • Player game-log + rolling-stats. GET /v1/players/{id}/game-log and /v1/players/{id}/rolling-stats.
  • Player season projection. GET /v1/players/{id}/season-projection (82-game pace).
  • Player vs-team head-to-head. GET /v1/players/{id}/vs-team/{team_id}.
  • Player streak. GET /v1/players/{id}/streak (hot/cold form).
  • NHL matchup composite endpoint. GET /v1/nhl/games/{id}/matchup.
  • 17 new league + sport API landing pages.
  • API Request Calculator on the homepage and at /compare/api-request-calculator.
  • WC2026 match weather. GET /v1/matches/{id}/weather via Open-Meteo.
  • NBA team Elo from box scores. Internal Elo store now driven from box-score totals (Task #28).