What's new
Recent improvements to the Big Balls Sports Data API, in reverse chronological order.
2026-10-14
GET /v1/leagues/:id/leadersnow serves soccer league leaders. The leaders data was loaded but no route could reach it, so the view behind it could never have answered a request. The route is now wired and returns the stored leader rows.- The NHL projected starting goalie now publishes its accuracy next to its coverage. The agreement rate and the number of sides it was scored over are returned as a scoped pair, so you can see how far the projection has been tested rather than reading a bare percentage.
2026-10-13
GET /v1/football/matches/:id/shotsserves shot-level soccer data, Edge and above. Each shot carries pitch coordinates, expected goals, minute, result, situation, body part and the preceding action.GET /v1/nhl/projected-goaliesreturns a projected starting goalie with a measured agreement rate of 84.0% across 1,585 scored sides. The projection is derived from our own saves market. It is labelled projected, not confirmed, and the response metadata carries the accuracy figures.- NHL power-play units are derived and served per team per game.
GET /v1/stored/matches/:id/power-play-unitsreturns the top five skaters on each team's first and second unit, ranked by ice time on the man advantage. - NHL skater and goalie expected-goals season splits are served at Edge.
GET /v1/nhl/skaters/xg-splitsandGET /v1/nhl/goalies/xg-splitsreturn season-level expected-goals detail. GET /v1/nba/players/{id}/season-splitsreaches 57,185 rows that no route could fetch before. Filter by season, season type, measure and stat type.- Kickoff weather now serves the stored archive before it tries upstream. Historical conditions stay available for a match even when the live lookup cannot answer, so the same request stops flipping between a value and nothing.
- NBA game kickoff evidence is now servable, and only where sources agree. A kickoff time is returned when two or more independent records agree on it, so a single disputed timestamp is withheld rather than published as settled.
- Team profile pages are live for 32 NHL teams, 30 MLB teams, five European soccer leagues and MLS. Each profile is captured from the data we already serve.
- The terms now state that showing the data is not redistributing it, and cover storage, model use and derived outputs. Displaying our data in your product was always allowed; the clause says so explicitly, and the usage-rights section now addresses caching, training and derived figures directly.
- Two stale claims on
GET /v1/matches/:id/statisticsare corrected in the docs. The documented coverage and plan for that route no longer overstate what it returns. - The dashboard resolves your daily request cap in one place. The quota card and the usage statistics panel read the same resolver, so the two surfaces can no longer show different limits for the same account.
2026-10-12
- Point-in-time reads land across odds, player props and availability. Pass
as_ofwith a timestamp and you get the state as it was recorded then. A timestamp outside the observed window returns a 422 that names the coverage gap or floor it fell outside, rather than quietly serving the nearest row it could find. GET /v1/nfl/surface-snapshotsmakes 572,842 stored rows reachable. The data was held and had no route; it now has one, gated to paid plans.- Per-player special-teams time on ice is served at Edge.
GET /v1/stored/matches/:id/statsnow carries power-play and penalty-kill ice time per player. - 59,514 NHL play participants are recovered, and the column can no longer go empty. Giveaways, takeaways and similar plays had dropped the athlete they belonged to. They now attribute correctly, and the participant column is
NOT NULLso the same gap cannot reopen. GET /v1/leagues/{id}answers for all 820 league display names it publishes. Names that onlyGET /v1/matcheswould accept now resolve here too.- NBA player props are actually being written. The writer had never fetched, and 275 runs recorded a clean zero instead of reporting that nothing arrived. The fetch is fixed and an empty run now fails loudly.
- Team profile pages are live for 30 NBA teams.
2026-10-11
- NHL history reaches back to 1997-98, the earliest season with complete fields. Seasons before that are not served rather than served thin.
- A league depth request refuses instead of silently handing you a shallower archive. When the full span cannot be assembled, the route says so rather than returning a partial history that looks complete.
- The discovery surface publishes the deep NBA archive, and every figure names the population it came from. Each count now says what it counted, so a number cannot be read against the wrong denominator.
2026-10-10
GET /v1/matches/:id/eventsdistinguishes a second yellow from a straight red. Both used to arrive as the same dismissal event.- Historical NHL archive spelling aliases resolve. Older team spellings in the archive now map to the canonical team instead of missing.
- Historical NHL box-score rows are joinable to a player. The rows carried no usable player link, so per-player history could not be assembled from them.
- The published NHL history claim is now 29 seasons, back to 1997-98. The figure matches what is loaded.
- Point-in-time NHL player props are now written on a schedule. The writer did not exist, so no prop history accumulated.
- Canadian football odds are no longer filed as boxing. One league id was mapped to the wrong sport, which put CFL odds under boxing. Measured availability for boxing and Formula 1 odds is recorded alongside the fix.
2026-10-09
- 26 seasons of per-game NHL player box scores are in production: 1,296,918 rows, verified.
?sport=cricketonGET /v1/playersreturned 0 against 13,337 cricket players. The filter now returns them.?league=onGET /v1/playersis an actual filter. It was accepted and silently ignored, so a league request returned the unfiltered set.- Three customer-visible wrong-data defects are fixed: cross-sport shot charts, league fixture precedence, and rejected league ids.
- Predictions serve the 570 value-prop rows' own probabilities, and stop leading with rows that expired 124 days ago.
- Standings refuse to derive a table their inputs cannot support. A league without the rows needed to order a table now says so rather than publishing an order that the data does not justify.
2026-10-08
- Cricket ball-by-ball is served on Pro, with historical deliveries backfilled.
- The NHL post-game lineup writer runs on a schedule. Post-game lineups now accumulate instead of depending on an ad hoc run.
2026-10-07
- NCAA men's basketball and WNBA player stats are written and served. Both leagues were held with no player stats behind them.
?league=resolves on both/v1/intelligenceroutes.- Invoice persistence survives a parse error. A single unreadable payload used to stop the whole write, so later invoices went unrecorded.
- Sport hub pages honour sport-specific tiers. A hub no longer quotes a plan that does not apply to that sport.
- The NBA player stats guide names the right route and a working field example.
2026-10-06
- Baseball starting lineups attribute each starter to the team they played for at match time. They had been attributed by current roster, so a traded player appeared on the wrong side of an older game.
- Stale live matches settle on a schedule, and the job reports whether it ran. The worker had recorded a healthy result while never running, so matches could sit frozen in a live state.
- An image-processing advisory is patched (GHSA-wq5f-xc86-pv6w).
2026-10-05
- Live odds movements stream over WebSocket on Pro.
/liveand/graphql/wsrefuse with the same shape, and both enforce the realtime plan gate. One transport enforced the gate and the other did not, so the same account could be refused on one and admitted on the other. Both now check, and both return the same refusal body.- Stored lineups elect one source per match. Two sources for one match could interleave, so a lineup could mix rows that belonged to different records of the same game.
- NFL canonical matches are backfilled for 2017, 2018 and 2019.
- The sports grace window is 7 days, up from 24 hours. A payment problem no longer cuts access the next day.
- Seven betting calculators are live: arbitrage, parlay, round robin, hedge, Kelly, no-vig and implied probability.
- The odds embed is available as an iframe.
- Three dependency advisories are patched.
2026-10-04
- Every 403 names the plan the account holds. A refusal used to leave you guessing which tier you were on, and the gate behind it now fails on an unknown plan rather than assuming the lowest.
- UEFA Nations League 2026 and 2026 international friendlies are ingested and served. Two serving paths had been hiding internationals that were already loaded.
- Both WC2026 team form facts are preserved. One was overwriting the other.
- The welcome offer triggers on real sport usage.
- Free-tier and live-edition claims are corrected on five tournament pages.
- The comparison calculator returns accurate figures.
2026-10-03
- Active MLB player headshot coverage is now 96.7% complete. We reconciled 42 of 47 missing headshots from existing identities, serving all images through our
/v1/assetsendpoint. - xG writes now report row counts and fail on partial fetches. The writer now wraps in
withCronHeartbeat, reports changed, unchanged, and deleted counts, and guards conflict updates withIS DISTINCT FROMto exclude timestamps. Partial fetch failures, including dry runs, will now fail the run. - A new
/dashboard/signup-trendspage provides aggregate signup data. This read-only dashboard includes daily history, cumulative totals, rolling 7-day averages, and breakdowns by sport interest, acquisition channel, and landing page. Access is controlled by an exact server-only allowlist, independent of administrator status. - Stripe invoice state is now synchronized with a new
stripe_invoicestable. This table stores invoice details, subscription and customer joins, status, amounts, and hosted URLs. Delayed webhook payloads will not regress invoice state, and theattempt_countis protected with a database-sideGREATESTfunction. - PFL fighter identities are now preserved when multiple fighters share the same name. This change ensures that distinct fighters, such as the two "Justin Clarke" entries, are correctly identified and displayed with unique athlete IDs, preventing data corruption and maintaining accurate fight card information.
- PFL history now includes 82 events from 2005-12-09 through 2026-12-19. A deterministic batch process loads these events, failing closed on unreviewed same-name fighter identities to preserve data integrity. Odds coverage is now available for 16 of these events.
- Redis round trips for authed requests are reduced by up to two. The
recordResponseForBreakerandbreakerCooldownStateoperations now use a single Luaevalcommand each, decreasing latency and Redis command usage. Limits and semantics remain unchanged. - PFL opening odds now load correctly. A parameter type mismatch prevented historical odds from being ingested for PFL events. This has been corrected by explicitly casting the sportsbook parameter to
varchar(50)in SQL. GET /v1/live-stats/{sport}/{matchId}/playersno longer returns 502 errors for WNBA, NCAAB, or MLB placeholder matches. Previously, these requests were incorrectly routed, leading to upstream 404s and subsequent 502s. The gateway now correctly identifies and routes requests for WNBA, NCAAB, and MLB, returning appropriate 404s for placeholder events instead of 502s.
2026-10-02
- PFL results and opening odds are now available. This includes leagues, event-identity cards, bouts, fighter profiles and records, completed results, and historical opening odds, ingested through the existing low-cron worker. Bout finality now overrides stale scheduled event status.
- MMA ingest now preflights Node database reachability before fetching PFL data. This prevents wasted API calls when the database is unavailable, as seen in prior runs where 166 of 202 calls failed due to certificate issues.
- The capability probe now inspects response bodies to differentiate between routes that refuse access entirely and fields that are omitted or null within a successful response. This change allows for precise measurement of field-level tier access, addressing a previous blind spot where entitlement claims were unverified for specific data points like
expected_goalson/v1/matches/{id}/statistics. New manifest keys,fields_by_tier,field_tier_floors, andfield_populated_tier_floors, detail these granular access levels.
2026-10-01
- MMA fight cards now group by event identity instead of calendar date. This change prevents UFC and PFL events held on the same day from merging into a single card, and adds
promotion=ufc|pflto fight-card and bout routes. Stored MMA result fields are now surfaced without inventing missing results.
2026-09-29
- NBA history now has one preferred surface:
GET /v1/matches?league=nba. It reaches 72,014 of 72,014 unique games, including all 70,611 of 70,611 archive games across 80 seasons and 1,403 canonical matches not present in the archive. The depth panel reports that one route and its 70,745 finished games instead of presenting two overlapping spans. No plan loses access and no reachable row or field was removed. - Unknown historical tip-off times are now explicit instead of fabricated. A four-source sweep recovered real timestamps for 25,708 of 69,303 previously date-only games. The remaining 43,595 of 69,303 return
kickoff_utc: nulland additivetime_precision: "date"; timestamped rows returntime_precision: "timestamp". Exact-source disagreements remain unresolved and reported rather than being settled by row order or a fake midnight. GET /v1/nba/gamesremains unchanged through 2027-03-31 and sunsets on 2027-04-01. Migrate during that window toGET /v1/matches?league=nba. We measured 51,621 calls from 118 keys across the affected routes from 2026-09-01 through 2026-09-29; query strings are not retained, so that is a conservative upper bound and we cannot identify in advance which clients assumedkickoff_utcwas always non-null.- WC2026 group-stage predictions are now written within a single database transaction. This prevents data loss in the event of a failure between the delete and insert operations, ensuring that 56% of the
predictionstable is not left empty. The previous process involved two independent queries, which could result in deleted rows not being re-inserted. - The
/v1/matches/:idendpoint now records the requested match ID on 404 responses. This allows for better attribution of errors and debugging of issues where a specific match ID could not be found. - New accounts receive a welcome email after their first sign-in. This email explains the platform's purpose, guides users through initial setup, and provides links to the dashboard and Discord. It is sent using existing email infrastructure and frequency controls, and is folded into the offer activation email when eligible to prevent duplicates.
2026-09-28
- The pitch-level MLB dataset now lives on /mlb-api, and the standalone page it used to have 301s there. One section,
Pitch-level data, with every field and its measured row count: 3,000,720 pitches across the 2023, 2024, 2025 and 2026 seasons, 10,184 games, 2,273 pitchers, 2,162 batters, through 2026-09-27. Pitch velocity on 2,988,834, spin rate on 2,976,804, pitch type and plate location on 2,988,186, arm angle on 2,705,971, exit velocity on 990,006 batted balls, launch angle on 991,469, barrel classification on 42,098, expected wOBA on 725,227, bat speed and swing length on 1,129,209, run-expectancy delta on 2,990,453. Measured against production on 2026-09-28. Nothing the old page documented was dropped. - Three corrections came with it. The figure on the old page read 2.89 million and its hero read 2.1 million; both were stale, and both were lower than what is actually loaded. The old page also promised "Free Tier, No Card" in its title while saying "Public endpoint in development" twice in its body — we checked production on 2026-09-28 and the route returns 404, so the free-tier promise is gone and the section states plainly that the data is held and not served. And sprint speed and outs above average were listed as coming; neither has ever been ingested, so they are now named as absent instead of promised.
- Correction to the 2026-06-21 entry below: the 4xx circuit breaker numbers were wrong. The window is a rolling 1 hour, not 5 minutes; the absolute gate is 500 4xx and the ratio gate's floor is 200 requests on Free (a paid plan gets 10x both: 5,000 and 2,000; the 80% ratio itself is unchanged for every plan); and a trip's cooldown is 600 seconds (10 minutes), not 120. These are the numbers the gateway has enforced since the 2026-06-25 retune (
docs/4xx-breaker-tuning-2026-06-25.md); the changelog entry below was never updated to match and has read wrong for three months./docs/rate-limitsandllms-full.txtare corrected; this entry is left as originally published, per our policy of correcting forward rather than editing a dated record.
2026-09-24
-
rankon standings is now a real table position, computed by us, for every league. It was the ingest's running counter over the vendor's conference/division tree — a position in the vendor's JSON, not in a table. Measured over the rows production actually served on 2026-09-24: 19 of 29 adjacent NBA pairs had the lower-ranked team holding the better record, 10 of 31 for NHL, 11 of 29 for MLS, 4 of 29 for MLB, 1 of 31 for NFL, and 2 of 137 for NCAAF.GET /v1/standings,GET /v1/stored/standingsandGET /v1/teams/{id}/seasonnow compute it at read time over exactly the rows in the response, so it is contiguous 1..N and therowsarray is returned in that order. Affected served values in 10 of 12 loaded league-seasons; 336 API keys called one of those three endpoints successfully in the preceding 30 days. -
NHL and NCAA football standings carry a rank for the first time. Their sources publish no league-wide rank — the NHL feed writes a conference rank and the NCAAF feed writes none — so
rankwas null on all 32 NHL rows and all 138 NCAAF rows. It is now populated on both. Nothing was removed to do this: the row count, the team set and every other field are identical, and the stored column every ingest writes is untouched. -
The ordering and the tiebreak are stated, not implied. Association football leagues (including MLS and the Champions League) order on league points — 3 for a win, 1 for a draw — then goal difference, then goals for. Every other sport orders on
win_pctas that league defines it, which for the NHL is points percentage rather than wins over games. Ties on the ordering metric are broken by more wins, then fewer losses, then team name, then team id, so the same rows always produce the same table and level teams get adjacent distinct positions rather than a shared one. Full description in therankfield of the OpenAPI spec. -
The five big European leagues did not change at all, and that is the check. Their vendor rank was already correct on league points, and 0 of 96 rows across the Premier League, La Liga, Serie A, the Bundesliga and Ligue 1 moved. The first measurement of this defect read those leagues as clean on win percentage — which they could not help but be, since
win_pctis null on every football row, so the scan compared 0 pairs. Re-run on the metric those tables are actually ordered by, the control both fires and passes.
2026-09-21
-
Match events now have one dependable response shape.
GET /v1/matches/{id}/eventsalways returnsdataas an array, including valid empty states, so integrations no longer need separate parsing branches for the same endpoint. -
Documented query parameters behave more predictably. Boolean play filters now distinguish
falsefromtrue;GET /v1/injuries?team=accepts a team id, name, or code consistently; and parameters that are intentionally non-filtering are identified in response metadata or documentation instead of silently implying that they changed the result. -
GET /v1/gamesreports the sport and filter scope it actually used. Game rows round-trip the canonical sport rather than a formatting label,filters_applied.leaguedistinguishes a league alias from an all-sport request, and unsupported?season=requests return a clear400with the supported date-filter alternative instead of silently returning the current window. -
Date-filtered history stays inside the requested sport's season boundary. Routes that derive historical access from
?date=now use each sport's real season calendar, including January rollover cases, so a date request cannot accidentally reach a different season through a calendar-year shortcut. -
The TypeScript SDK can call the complete documented API surface. The generated operations module covers all 134 of 134 documented operations with typed request parameters, path validation, canonical query encoding, and caller-controlled cancellation. 68 of 134 operations also have contract-derived response payload types today; the remaining response schemas are reported honestly rather than asserted as complete.
-
API-key rotation preserves paid access more reliably. A newly issued key uses the stronger of the account's active subscription and its inherited key entitlement, preventing a rotation or temporary subscription-read failure from silently downgrading an otherwise valid paid account. This change only protects access; it does not reduce anyone's existing entitlement.
-
New implementation guides cover more real sports-data products. The tutorials now include soccer betting models, fantasy applications, live match centers and scouting workflows, plus NCAA football betting and game-condition analysis.
-
NCAA football fixture identity is cleaner without breaking saved links. Fifty-four duplicate scheduled records were consolidated into their completed fixtures, historical odds remained attached to the canonical games, and all 54 retired match UUIDs continue to resolve through aliases.
-
Coverage and operational visibility are more precise. Priority NFL, NCAA football, NHL and soccer leagues are separated in the admin visibility view, while refreshed all-sports, image, capability-freshness and workflow-health measurements make missing, stale and unavailable data easier to distinguish.
-
Platform security and legal discoverability improved. The web application stack was updated past its outstanding security advisories, and the licence-notice page is now indexable, included in the sitemap and linked from the shared footer. No API entitlement or response payload changed as part of that work.
2026-09-18
-
GET /v1/stored/players?league=now returns the league's current participants, and the count goes down. The league filter joined throughteams.league_id, which is timeless identity metadata rather than seasonal competition membership: a club keeps that pointer after it leaves the competition. Measured in prod on 2026-09-18,?league=eplreturned 775 player records across 23 clubs while the latest stored2026-27standings contain 20 clubs — 102 of those 775 records were players whose clubs are not in the current season. Asking for Premier League players and being handed players who are not in the Premier League is wrong data, not extra data, so the filter is now bounded to the clubs in the league's latest standings season: 673 records across 20 clubs. The season that counts as current is chosen by played rows and parsed start year, never by string order on the season label. A league we hold no standings for is unchanged rather than becoming empty.?team=and unfiltered requests are unchanged, and no roster row was rewritten. -
This narrows a result the 2026-08-31 entry said would not change, and we cannot tell you whether it affected you. That entry stated that the
/v1/stored/*league resolver meant "nothing that worked before has stopped working". That was true of the resolver and is no longer true of the result: a?league=request that worked on 2026-08-31 now returns a smaller set. We are not able to say whether anyone depended on the removed clubs —usage_eventsrecords the route and the key but records noleagueand noseason, so the question is not determinable from our own logs, and we would rather say that than imply the impact was measured. -
The earlier population is still reachable: name the season.
GET /v1/stored/players?league=epl&season=2025returns the 2025-26 participants — 775 records across 23 clubs, the exact set the unqualified filter used to return.?season=here is the same parameter, with the same meaning, as everywhere else in the API: the year the season STARTS. We did not invent a new spelling for it. It requires?league=, because without a league there is no standings table to bound by, and a season we hold no standings for is a400that names the seasons we do hold rather than an empty200you cannot interpret. The other route to a historical directory is unchanged:GET /v1/teams?league=<code>still resolves throughteams.league_idand still returns all 23 clubs, andGET /v1/stored/players?team=<team id>per club is untouched. -
The response now states the scope it was computed under.
GET /v1/stored/playersreturnsmeta.filters_appliedwith thesport,team,league,seasonandqactually applied, plusleague_scopeon a league-filtered request:{ "mode": "latest_standings_season", "season": "2026-27", "leagues": [{ "league_id": "…", "seasons": ["2026-27"] }] }.modeisrequested_seasonwhen you sent?season=. The labels are reported per league, because a filter resolving to more than one league — or a season carrying two labels, as the NHL's does — cannot be described by one string;seasonis populated only when exactly one league resolved to exactly one label. An emptyseasonsarray is a league we hold no standings for, which is not narrowed rather than returned empty. You can tell those cases apart from the response instead of inferring them from a count.meta.current_participantsandmeta.historical_directoryname the other direction, whichever one you are not in. -
A Premier League headshot that
502'd now falls back instead of failing. One stored upstream image URL began returning403at its origin, which our own asset proxy surfaced as a502. A player whose headshot is on that retired host is now served a licensed image derived from their existing vendor identity, ornullwhen no safe fallback exists. This is asset-proxy resilience, not an identity repair — the affected player record was verified correct against production, and no identity was merged, split or rewritten. -
NHL game detail is deeper and its leaderboards are more reliable.
GET /v1/nhl/games/{gameId}/plays?shooting_only=truereturns shot events with distance, angle, computed xG, rebound, rush and empty-net fields when available.GET /v1/nhl/games/{gameId}/box-scoresadds goalie lines and even-strength, power-play and short-handed splits. Counting-stat requests toGET /v1/nhl/leadersno longer fail on a missing query alias, and explicit historical seasons onGET /v1/nhl/team-leadersfollow the existing historical-data entitlement. -
Retired match UUIDs continue to resolve without bypassing access rules. Match, odds, statistics, play, prediction and weather reads resolve a known retired UUID to its canonical match before querying, while keeping each route's existing plan and sport gates. The consolidated Pittsburgh-Syracuse fixture now uses
c21d6de7-6e30-439b-9f29-9767d7d4a224; requests using its retired UUID,c90b7dc6-0b3e-46cc-9619-62fcc941a56e, resolve to that record. -
Cricket match lists accept an inclusive date range.
GET /v1/cricket/matches?date=YYYY-MM-DD:YYYY-MM-DDfilters by kickoff across both calendar days, uses UTC unless?tz=supplies an IANA timezone, and echoes the exact applied window inmeta.date_window. Backwards ranges return a400, while pagination continues through the existinglimit,offsetandtotalfields. -
The coverage page renders again and describes NCAAF season statistics accurately.
/coverageno longer fails when loading its refresh-cadence declarations, and it now labels NCAAF player-season statistics as partial instead of omitting that data type. -
La Liga event previews handle unavailable play-by-play without crashing.
/la-liga-apiaccepts both stored event arrays and live availability envelopes. A fixture with no captured events now shows an explicit unavailable state instead of throwing or presenting an unexplained empty play-by-play panel.
2026-09-17
- Soccer leaderboards no longer choose different default seasons. When
?season=is omitted,GET /v1/leagues/{id}/top-scorersandGET /v1/leagues/{id}/xg-leadersnow use the newest season present in both datasets. Pass?season=YYYYexplicitly whenever you need to reproduce an older result.
2026-09-16
-
World Cup tournament odds return reliably under load.
GET /v1/odds?tournament=wc2026now reads the compact current-lines store instead of reducing the full historical ledger on every request. The response contract is unchanged, while the query no longer runs into the gateway timeout. -
Weather coverage gaps are now data, not misleading
404errors.GET /v1/matches/{id}/weatherreturns200for a valid match even when its venue or forecast is unavailable, withforecast: null, a stableunavailable_reason, and a human-readablenote. A404now means only that the match itself does not exist. -
Date filters can follow the caller's local calendar. Add an IANA timezone such as
?tz=America/New_Yorkto date-filtered match, score, odds, cricket, fight-card, game, and schedule-context routes. UTC remains the default, andmeta.date_windowshows the exact bounds applied. -
GET /v1/teams/{id}returns a real team profile without the long empty wait. The route now serves the stored team and record directly, shares its statistics logic with/v1/teams/{id}/stats, and returns a genuine404for an unknown team id. -
Live NFL plays are selected by kickoff window instead of stale match status.
GET /v1/live-stats/american_football/{matchId}/playscan follow an in-progress game even when its stored status has not caught up, and a live game with no captured plays reports that failure instead of looking like a legitimate empty result. -
NFL team-stat rows now include a derived
defenseobject.GET /v1/nfl/team-statsadds sacks, sack yards, interceptions, forced fumbles, blocked kicks, points allowed, and passing, rushing, and total yards allowed, derived from the opponent and defender records already held for the game. -
Live box scores preserve the team a player represented in that game.
GET /v1/live-stats/ice_hockey/{matchId}/playersgroups historical players by the fixture side they played for instead of their current club. Empty football and hockey box scores also includeplayer_count,team_count, and status-aware guidance about whether polling again can help. -
Public player and odds responses use canonical identities consistently. Live player ids, World Cup player ids, weather source labels, and sportsbook values are normalized before they reach customers.
GET /v1/playersalso excludes aggregate placeholder rows and trims player names. -
League pages deliver complete visual context again. NCAAF leaders can include player headshots, soccer events include player and assist identities with headshots, and repaired league crests remain present across leaderboards and event views.
2026-09-15
-
Webhook registration now has a secure delivery contract.
POST /v1/webhookscreates a subscription and returns its signing secret once, deliveries carry an HMAC signature, failed attempts retry, andPOST /v1/webhooks/{id}/unparkre-enables a destination after it is repaired. -
Scheduled plan changes are visible to both people and integrations. During the final seven days before a scheduled change, responses include
X-BBS-Access-Ends-At,X-BBS-Access-Ends-In-Days, andmeta.access_ending, so an application can react without depending on email. -
Every applied
?date=filter explains its UTC window. Stored match, score, odds, cricket, fight-card, and schedule responses includemeta.date_windowwith the inclusive start and exclusive end used by the query. This makes midnight-boundary behavior explicit instead of making an evening game look missing. -
Match and player responses stay scoped to the requested record.
GET /v1/matches/{id}uses that game's live score,/v1/playersreturns named athlete rows rather than aggregates, and cricket scorecards attribute each innings by batting identity rather than a name prefix. -
Soccer batch refreshes preserve newer match state and UTC kickoffs. Live and finished states take precedence over an older scheduled snapshot, and Bundesliga kickoff times are stored consistently in UTC.
-
Public media and identifiers are safer to embed. Team crests and player headshots can be served through the platform's asset route, top-scorer responses project canonical player ids, and sportsbook values use canonical public names.
2026-09-14
-
Live football plays carry more of the action. The live play response now includes field-goal distance and outcome, plus the canonical player id for the kicker when one can be resolved.
-
GraphQL match details now reach their statistics, events, and lineups.
Matchresolvers pass the fixture identifier expected by those loaders, fixing calls that previously declined every request even when the match data existed. -
Live box scores name only the teams in the fixture. Team attribution is constrained to the match's home and away sides, preventing unrelated clubs from appearing in a two-team box score.
-
NCAAF leader rows link cleanly to player resources.
GET /v1/ncaaf/leadersexposes the canonicalplayer.idwhen the athlete is resolved, and live ingestion reuses existing player identities instead of creating duplicates. -
API discovery now follows the contract automatically. The
/v1/endpoint index is generated from the OpenAPI operations, bringing previously undiscoverable documented routes into the index./v1/coveragealso reports play-by-play using only rows that can actually be served. -
Seasonal subscription pauses are self-service. The dashboard can schedule a pause for an upcoming offseason and shows the effective dates before confirmation, while the API continues to report the account's current state accurately.
2026-09-13
-
Live sports now have a dedicated score resource and a reliable stream.
GET /v1/scoresexposes the live-score payload directly, while/livesupports raw WebSocket clients without a second upgrade handler corrupting the connection. Stream messages includepolled_atandemitted_atso clients can measure delivery latency themselves. -
GET /v1/matcheshas one response envelope for every sport. Baseball, boxing, soccer, and the other supported sports now return the documented match array instead of switching to a score-shaped object according to runtime conditions.GET /v1/matches/{id}?sport=...also falls back to the stored match rather than returning404simply because the game is not on today's live board. -
NFL and NCAAF play-by-play can be consumed while the game is live.
GET /v1/live-stats/american_football/{matchId}/playsserves the in-game feed, whileGET /v1/nfl/games/{gameId}/playsgives a clear empty-datametaresponse when no stored plays exist. -
NFL live player stats are available through the football live-stats surface.
GET /v1/live-stats/american_football/{matchId}/playersreturns group-qualified player fields, avoiding collisions between values such as passing and rushing yards. -
NFL data is deeper across offense, defense, special teams, and game context. Public player and team statistics add defensive, kicking, return, fumble, and interception fields, while live updates add down, distance, possession, and the current play. Venue, roof, and kickoff-condition data provide the context needed for weather-aware analysis.
-
NCAA basketball tournament brackets are queryable end to end.
GET /v1/ncaab/tournamentreliably serves the full bracket with canonical school identities, seed, region, round, and a truthful season hint. -
NCAAF fantasy leaders have a dedicated endpoint and product surface.
GET /v1/ncaaf/leadersreturns current leaderboards derived from player statistics, alongside standings and schedule examples on the NCAAF API page. -
NHL play-by-play and raw box scores are now served.
GET /v1/nhl/games/{gameId}/playsand/v1/nhl/games/{gameId}/box-scoresexpose the stored game detail through documented routes. -
GET /v1/stored/matchesreturns again instead of failing during its count query. The count path now joins the same bout data referenced by its filters, restoring the endpoint without changing the response shape. -
Current MLB box scores are available through the canonical stats routes. Player and team batting and pitching lines now flow through the same public match and statistics surfaces used by the rest of the API.
-
Injury responses for basketball, hockey, and football are more complete and reliable. Scheduled refreshes were restored, football roster availability is presented alongside the weekly injury report, and snapshots fail closed instead of silently replacing a newer view with an incomplete one.
-
Pagination and empty-result behavior are predictable.
?page=now either paginates or returns a clear validation error instead of being ignored,?offset=0is accepted as the first page, and valid empty play responses explain whether the game is unknown or simply has no captured data. -
Customer-facing identifiers are canonical and source-neutral. Match, player, team, sportsbook, event-narrative, and documentation responses translate internal source keys and branded values to platform identities before delivery.
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/matchesand/v1/stored/standingsmatched the league's DISPLAY NAME and nothing else, so?league=La Ligareturned rows while?league=laligareturned an empty array with a200and 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/teamsand/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 a400naming the value you sent, with the list of valid codes, rather than a silent empty200. 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.- Added 2026-09-18; the text above is unchanged. The sentence "nothing that worked before has stopped working" described the league RESOLVER and remains true of it, but it no longer describes the RESULT of
GET /v1/stored/players?league=. See the 2026-09-18 entry at the top of this page for what changed and how to request the previous population.
- Added 2026-09-18; the text above is unchanged. The sentence "nothing that worked before has stopped working" described the league RESOLVER and remains true of it, but it no longer describes the RESULT of
-
?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=2025now 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 affectsGET /v1/players/{id}/stats,GET /v1/teams/{id}/matches,GET /v1/teams/{id}/statsandGET /v1/matcheswherever?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_atis how you tell. The upstream provider feeding these two stopped returning data.GET /v1/injuries?sport=basketballand?sport=ice_hockeystill return 200 with rows, but the newest NBA row carries anupdated_atof 2026-07-26 and the newest NHL row 2026-07-01, so those responses describe a moment months back rather than today. Readupdated_aton 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/injuriesreturns 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 inmeta.resolved.GET /v1/nfl/depth-chartsreturns each team's open chart intervals, or the chart as it stood at a past instant with?as_of=; rows carryopen_intervalrather thancurrent, andmeta.season_coveredtells 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-statsreturns team-week offensive totals including EPA and CPOE. All three carrymeta.data_as_offrom the row's ownupdated_at, and the writer only rewrites a row that actually changed, so that timestamp ages honestly when a feed goes quiet. Coverage reports these asoff_seasonuntil Week 1 on 2026-09-09, notlive. See/nfl-apiand/injuries-api. -
GET /v1/injuries?sport=american_footballreturns data instead of a404that contradicted itself. It answered "we do not serve injuries for 'american_football' yet" while thesuggested_fixin 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 upstream team abbreviations disagreed with ours (LAandWAS), which servedteam: nullfor those two franchises; both are mapped.baseballhas been removed from the supported list on the same endpoint, because no baseball injury table exists and it was passing the gate only to404with asuggested_fixthat 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. Andas_ofwas computed fromfetched_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_ofnow anchors tofixture_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 ownas_of. Supported values areEPL,La Liga,Serie A,Bundesliga,Ligue 1,MLSandUEFA Champions League; anything else is a400that 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-apiand/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.linescoreholds 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 asscore: {"home": 90, "away": 81}. Nobody wins a fight 90-81. On 43 finished bouts in production,scoreis nownullwithscore_status: "unavailable", and two new fields carry the real answer:judge_scoresrepublishes the cards, anddecisiongiveswinner,method(unanimous_decision,split_decision,majority_decisionordraw),rounds_scoredandjudges. A bout that has scorecards went the distance by definition, so this is a result we already held rather than an inference.decisionis 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 placeholder0-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-apiand/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/coverageand/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 Ligareturned 23 teams and?league=laligareturned an empty array with a200and no explanation. It now resolves through the same shared resolver/v1/standingsand/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 a400naming the value you sent instead of a silent empty200. 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/standingsno 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 atgames_played: 0and holding ranks that were already taken: rank 6 was both Celta Vigo, with 38 games played, and Deportivo, with none. Anyone renderingrankgot 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 ranknull, 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, wherescheduledpreviously 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: FBS and FCS arrive from one feed whose default response is capped at 25 events, so both divisions are now requested explicitly. See/ncaaf-apiand/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-apiand/ligue-1-apieach 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 withnpxg,xaand 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-leadersand/v1/leagues/{id}/top-scorerskey on the hyphenated league code where/v1/standingsand/v1/matchestake the concatenated one.serie-aandligue-1return200on the boards;serieaandligue1return404.laligaandbundesligaare 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}/scorecardappears 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/oddsnow 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 thetwo_source_agreedverification 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-fcscovers 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/statsnow 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,323player_statsrows 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=ncaafnow returns 9,204 games (up from 8,180), and shows up on either team's own record viaGET /v1/teams/:id/formandGET /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=ncaafnow returns 8,180 games spanning 2016 through the 2026 schedule, up from 2025-only.GET /v1/players/:id/statsnow 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.5Mplayer_statsrows 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/elonow returns real ratings for all 32 NFL teams, backed by a full historical recompute (3,386elo_historyrows 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/sdknpm 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
503for transient database issues, not a raw500. A momentary connection-pool exhaustion or statement timeout previously surfaced as an unhelpful generic500. It now returns503with 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, andftare now aliased to this endpoint's canonicalscheduled/live/finishedvalues, so a term that already works on/v1/wc2026/matchesorlive-statsno 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
livematches, 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 aslivefar longer than expected; both are now isolated so one failing dependency doesn't block the others.
2026-07-27
- New
route_not_founderror code, plus sport-prefixed route aliases. A request to a path that doesn't exist now gets a distinctroute_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/sportsandGET /v1/coverageno 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_idoraf_player_idfrom our own/v1/matcheslineups or/v1/teams/{id}/squadresponses 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 asuggested_fixpointing at the right discovery endpoint. /v1/matches/:id/oddsand/v1/matches/:id/eventsnow fall back to stored data instead of returning nothing. Previously these returnednullif 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
403naming 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-finderplus 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-0in one view andnullin the other; both now correctly shownullbefore kickoff and the real score once the game starts. - Finished and live matches derive their score from
linescore, not stalematch_stats. A match with a completelinescoreno longer risks showing0-0because a separate stats field hadn't caught up./v1/matchesand/v1/matches/:idnow sum the per-periodlinescorewhen it's available. /v1/odds/divergencesno 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: 1for an unknown source./v1/sportsand/v1/leaguesnow correctly reportsource: 'internal-catalog'for our own data; a genuinely unknown source now reportsconfidence: 0instead of a misleadingly confident1. - NCAAF coverage claims now match what's actually there, and every sport must declare its data types explicitly. The
/ncaaf-apipage 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 newratingsdata 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/:idno 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/oddsand/v1/matches/:id/eventsno 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-0before kickoff. A scheduled game's linescore now correctly returnsnullinstead 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-apiand/ufc-apipages 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-apipage and coverage manifest no longer imply otherwise. - New: a boxing results parser (dry-run only). Parses official commission result documents 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-sanctioned bouts (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(orpnpm 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 URLhttps://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 — itsmain/types/exportspoint at the shippeddist/, soimport { 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: a licence notices page. It carries the exact credit notices our data licences require.
-
The
/hockey-apiand/nhl-apipages 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 forepl,laliga,serie-a,bundesliga, orligue-1— the same league codes/v1/leagues/{id}/top-scorersalready takes. Each row carriesxg,xa,npxg,xg_chain,xg_buildup,goals,assists,shots,key_passes,matches,minutes, and a derivedgoals_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(defaultxg), 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 BigBallsplayer_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-pasteexample_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 honest0%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_footballnow returns college football players' season stat lines (passing, rushing, receiving, and more), grouped by season with alevel: "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/sportsnow returns all nine covered sports (cricket, MMA, boxing, and Formula 1 are now visible alongside the rest), and unknown values like?sport=rugbynow return a clean400instead 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
verificationis now evidence-based — the values changed. OnGET /v1/odds/historicalandGET /v1/odds/closing-lines, the per-lineverificationfield previously returnedtwo_source_verifiedwhenever 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, withdivergence_categories; this replacesdivergence_flagged),single_source(only one provider carries this book/side, or the book has no cross-provider match — not verified), andunverified(we have not compared this line — not verified). If you were treatingtwo_source_verifiedas "trusted", note that most of those lines now correctly reportsingle_sourceorunverified; onlytwo_source_agreedis a positive cross-check. Documented at/docs/odds.
2026-07-09
-
Unknown player ids now return
404, not an empty200.GET /v1/players/{id}used to answer with a200andmeta.coverageset 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 a404with error codenot_foundand asuggested_fixthat 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 same200withmeta.coveragefalse as before, because that is a legitimate answer and not an error. This is a small error-contract change: if you were treating an empty200on 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/matchescould 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/:idnow 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/matchesreports 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 a500. 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 a403with error codeKEY_PAUSEDand a plain-language message, so you can tell a temporary pause apart from a revoke or a quota429. 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-bootwas returning an emptyleaders[]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 carriescountry(the national team's full name, for example "Argentina", not an ISO code),team_id/team_name, a nullableplayer_id, andgoals/assists/minutes. See/docs/wc2026.
2026-06-26
- World Cup knockout rounds now read from the result, not the calendar.
GET /v1/wc2026/matcheswas labeling each knockout match'sbracket.roundby 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 theroundvalue 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 trustbracket.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 are4xx. On a trip the key gets429for 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 breaker429carriesRetry-After,X-RateLimit-4xx-Cooldown, andX-RateLimit-4xx-Resetheaders so you can tell it apart from a quota429(which carries neither). If you see it, read theerror.messageandsuggested_fixon your4xxresponses 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 averificationfield.two_source_verifiedmeans both of our odds providers priced that book, market, and side and they agree.single_sourcemeans only one provider priced it (or the book has no cross-provider match).divergence_flaggedmeans the two providers disagree, and adivergence_categorieslist 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. Theverificationfield is live on both endpoints, andGET /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), andSTALE(one feed is lagging). Surfaced viaGET /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_typeandsnapshot_aton 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_typeclosing,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, andGET /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/matchesnow 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=onGET /v1/wc2026/matchesnow 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 asuggested_fixtelling 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}/eventsnow 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=ucland?league=mlsnow 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
seriesblock 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_fixfield, 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/matchesandGET /v1/wc2026/standingsnow 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/matchesandGET /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}/tournamentreturns 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-scorersreturns 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/predictionsresponses now includekickoff_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/matchesno 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), adocs_url, and arequest_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-formreturns 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=2024to 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/groupsreturns 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}/trophiesreturns a football player's career honours from the upstream football database, with a top-leveltotal_trophieswin 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}/playoffsreturns an NBA team's playoff record by season (the Boston Celtics went 16-3 in 2023-24). - Player transfer history.
GET /v1/players/{id}/transfersreturns 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}/shotsreturns a player's shot log with courtcoordinate_x/coordinate_y,shot_type, and made/miss. 235k+ shots from 2025-26 including the Finals. Free throws are excluded by default (?include_ft=trueto 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}/gamelogreturns flat per-game NBA stat lines, most recent first (last 20 games by default, up to 50). Filter bypostseasonandseason. - WC2026 squads.
GET /v1/wc2026/teams/{id}/squadreturns 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-historyreturns 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/historyreturns a team's Elo trend line, a baseline seed plus one point per settled match, so the rating grows as matches play out. Optionalfrom/todate filters. - NBA player career averages.
GET /v1/players/{id}/careerreturns 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-movementreturns 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/sdkornpm install @bigballsports/sdk. One import, every sport. Superseded by@bigballsdata/[email protected](2026-07-24) — install@bigballsdata/sdkinstead.@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/matchesnow carry abracketblock withround,home_feeder, andaway_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}/squadendpoint. - 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=wc2026returns 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=recentreturns the last 7 days of finished games in one call, anddate=YYYY-MM-DD:YYYY-MM-DDrange 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 upstream 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}/matchupandGET /v1/nhl/games/{id}/matchupnow 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/matchesnow 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}/matchupnow builds active rosters from the newroster_membershipstable 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-propsflags player props where the empirical hit rate beats the de-vigged market line.window=upcoming(default) covers the next 24 hours;window=allruns 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}/matchupresolve for every game of the series. - NHL playoff series object.
GET /v1/nhl/games/{id}/matchupnow returnsmatch.stageplus aseriesblock: the game number in the series, per-side wins from finished games, and a human-readableseries_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
stagetag (playoffsvsregular), 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}/propsdeeper 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_hockeyreturns the full league list (one row per player, deduplicated) andGET /v1/players/{id}/injuryreturns a single player's status, injury type, expected return date, and note. Both join on the sameplayer_idyou use everywhere else. - Matchup responses now carry injury status.
GET /v1/nba/games/{id}/matchupandGET /v1/nhl/games/{id}/matchupsurface 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:finishedsignal 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/matchesnow 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/leadersreturns the top scorers (rank, goals, assists, and games played per player) with an explicittournament_state:pre_tournamentbefore the June 11 opener, thenin_progress, thenfinished. - World Cup 2026 weather live for all 16 stadiums.
GET /v1/matches/{id}/weatherreturns the match-time forecast (temperature, precipitation, wind, condition) for every WC2026 venue. - NHL trending players.
GET /v1/intelligence/trending?sport=nhlnow 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.sourcefield 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_membershipstable 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}/matchupnow 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/leadersendpoint, and a goalie depth chart. The underlying Elo seed runs across every NHL team, so/v1/teams/{id}/eloand the value-finder answer for ice_hockey today. - NHL standings fix. Vegas Golden Knights now returns the correct
VGKshort_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=nhlno 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-bootreturns the tournament-wide top-scorers leaderboard with explicit lifecycle state:pre_tournamentbefore June 11 (leaders empty),in_progressduring the tournament,finishedafter the final. - Live win probability endpoint.
GET /v1/wc2026/matches/{id}/live-probabilityreturns 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 (historical). This was announced for the World Cup 2026 opener but did not ship. The review page at
/soccer-props-apiis retained without a launch date while product decides whether to repurpose or retire the premise in #2326; it is not an active API offering. - MLB pitch-level teaser at /mlb-api#pitch-data. 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. (This entry originally pointed at a standalone page of its own. Only the name and the link were changed, on 2026-09-28, when that page was folded into
/mlb-api— see the 2026-09-28 entry. Every figure is as first published.) - 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/leadersjoins 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 pitch-level load and points at the pitch-level teaser. Added a pitch-level research card to buildIdeas.
- NHL EndpointExplorer caught up. Added
/v1/nhl/leadersentry (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_pctfor penalty kill percentage, andplayers[].season_avg.save_pctfor 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|mlsand/v1/matches?sport=football&league=cl|mlsnow 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_formis now filled in BEFORE kickoff (was empty until ~2h pre-kickoff). Last 5 fixtures per team computed from the upstream football data partner. /v1/matchesseason 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/matchespagination now works.?pageand?limitapply as a post-filter slice. Previously silently ignored./v1/matches/{id}/eventsshape fix. Response now correctly keyeddata.events(wasdata.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. 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/standingsand/v1/wc2026/matches.
2026-05-29
- Standalone team Elo endpoint.
GET /v1/teams/{id}/eloreturns current Elo rating + league rank. Bets Chat Intel tab integration ready. - Value-finder shipped.
GET /v1/intelligence/value-finderreturns 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
/statusnow 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/leadersreturns statistical leaders with team affiliation. - NBA hot/cold movers.
GET /v1/intelligence/trendingranks 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-logand/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. - NBA team Elo from box scores. Internal Elo store now driven from box-score totals (Task #28).