Tutorial · 11 min · endpoints verified 2026-09-11
NFL live game center API tutorial
Build a game center that shows more than a score: current drive, win probability by quarter, and the play-level EPA that explains why the number moved. About fifteen minutes; match lookup is free and NFL play-by-play requires Pro.
API coverage and endpoint overview: Explore the NFL API
Why this is hard
The problem with live NFL data
A live score is table stakes. What makes a game center worth opening mid-game is context: which quarter, whose ball, how the win probability got to where it is — and that needs both a matches feed for the score and a play-by-play feed for the drive underneath it, kept in sync.
- Score and play-by-play usually come from different endpoints with different keys: a match id here, a text game id there. This build uses both and shows exactly where they connect.
- Live coverage is real but polled, not pushed: the match response says plainly when a game is being served from the stored tier rather than a live adapter, so a client can show a correct "last updated" instead of implying second-by-second push.
- Win probability is a per-play field, not a separate model call — it is already computed on every row of play-by-play, so a chart is a filter and a line, not a machine-learning project.
Path 1 · recommended
Have your AI agent build it
get_matches covers the score half of this build well. Play-by-play — the drives, the down and distance, the per-play win probability — has no MCP tool, so an agent needs one direct REST call to get the rest of a game center.
1. Connect the MCP server
Add the server in your MCP client and sign in with your Big Balls account. Your client registers itself and handles the token exchange — there is no key to copy.
{
"mcpServers": {
"bigballs-sports-data": {
"url": "https://mcp.bigballsdata.com/mcp"
}
}
}2. Tools your agent gets
get_matchesLive, upcoming or historical matches for a sport or league.
get_coverageMachine-readable map of what we hold and what we do not.
3. Ask for what you want
Paste this at your agent. It is written to make the model check coverage before it designs anything, which is what stops it inventing a field we do not serve.
Build me an NFL live game center for one game.
Use the Big Balls Sports Data MCP server's get_matches to find the game and
its current score and status.
The MCP server has no play-by-play tool, so for drives and win probability
call GET /v1/nfl/games/{game_id}/plays directly with my key — note this uses
the game's text id (e.g. "2025_01_DAL_PHI"), not the match UUID
get_matches returned; both point at the same game.
From the plays, build: the current drive (posteam, down, distance, field
position from the latest row) and a win-probability series across the game
(wp field, one point per play). Tell me which fields came from which call.What the agent cannot reach
The two endpoints use two different identifiers for the same game (a match UUID from get_matches, a text game_id ("2025_01_DAL_PHI") for /plays), and nothing joins them automatically today. An agent has to carry both ids rather than assuming one resolves to the other.
Path 2 · hand-coded
Build it yourself
Every call below ran against production on 2026-09-11. Match lookup is free; NFL play-by-play requires Pro.
- 01
Find a live or upcoming game
Filter matches by status. A live game center only needs status=live; status=scheduled finds what is coming up.
GET /v1/matchesbashcurl -s "https://api.bigballsdata.com/v1/matches?sport=american_football&league=nfl&status=live" \ -H "x-api-key: $BBS_API_KEY"Real response, trimmed
jsonjson{ "data": [ { "id": "5b2e8a2c-9c4f-4f0f-b035-fa23b0311668", "home": { "name": "Los Angeles Rams", "short_name": "LAR" }, "away": { "name": "San Francisco 49ers", "short_name": "SF" }, "kickoff_utc": "2026-09-11T00:35:00.000Z", "status": "live", "score": { "home": 7, "away": 10 }, "linescore": { "away": [3, 7], "home": [0, 7] }, "broadcast": "Netflix" } ], "meta": { "source": "stored" } } - 02
Poll the match for score changes
GET /v1/matches/:id refreshes score and linescore for one game. Read meta.note before assuming push-frequency: this game was served "from the stored matches table (no live adapter currently lists this match)" at the moment above, which is an honest statement of polling cadence, not a defect.
GET /v1/matches/:idbashcurl -s "https://api.bigballsdata.com/v1/matches/5b2e8a2c-9c4f-4f0f-b035-fa23b0311668" \ -H "x-api-key: $BBS_API_KEY"Real response, trimmed
jsonjson{ "data": { "id": "5b2e8a2c-9c4f-4f0f-b035-fa23b0311668", "status": "live", "score": { "home": 7, "away": 10 } }, "meta": { "source": "stored", "note": "Served from the stored matches table (no live adapter currently lists this match)." } } - 03
Pull the drive from play-by-play
This needs the game_id, not the match UUID: a text id like "2025_01_DAL_PHI". Every row carries down, distance, field position, the possessing team and a running win-probability value.
GET /v1/nfl/games/:game_id/playsbashcurl -s "https://api.bigballsdata.com/v1/nfl/games/2025_01_DAL_PHI/plays?limit=5" \ -H "x-api-key: $BBS_API_KEY"Real response, trimmed
jsonjson{ "data": [ { "play_id": "39", "game_id": "2025_01_DAL_PHI", "posteam": "SEA", "defteam": "SF", "play_type": "kickoff", "yards_gained": 0, "yardline_100": 35, "quarter_seconds_remaining": 900, "score_differential": 0, "epa": 0.562, "wp": 0.546 } ], "pagination": { "total": 166 } } - 04
Build the win-probability chart
wp is already computed per play — just sort by play_id and plot it. No model to run, no EPA-to-probability conversion to write.
Win probability seriespythondef wp_series(plays): return [ {"play_id": p["play_id"], "wp": p["wp"], "clock": p["quarter_seconds_remaining"]} for p in sorted(plays, key=lambda p: int(p["play_id"])) if p["wp"] is not None ] - 05
Show the current drive
The latest play with a non-null posteam and down tells you who has the ball, on what down, and where. Filter out kickoffs and administrative rows (play_type is null on the opening snap in this data) before picking "latest".
Current drive from the last real snappythondef current_drive(plays): live = [p for p in plays if p.get("down") is not None and p.get("posteam")] if not live: return None p = max(live, key=lambda p: int(p["play_id"])) return { "team": p["posteam"], "down": p["down"], "field_position": p["yardline_100"], "epa_last_play": p["epa"], }
Reference
Every endpoint this tutorial uses
All on the gateway at api.bigballsdata.com. Verified against production on 2026-09-11.
| Method | Path | Returns | Why you need it | Plan |
|---|---|---|---|---|
| GET | /v1/matches?sport=american_football&league=nfl&status= | Match id, teams, kickoff, status, score and linescore | The scoreboard half of a game center — find live games, show the running score. | Free |
| GET | /v1/matches/:id | One match at a time: score, linescore, status, broadcast | Poll this to refresh a single open game rather than re-fetching the whole live list.meta.note discloses when a game is served from the stored tier with no live adapter listing it — read it rather than assuming second-by-second push on every game. | Free |
| GET | /v1/nfl/games/:game_id/plays | Every play: down, distance, field position, EPA, win probability, possession | Everything a score feed cannot show — the drive underneath the number, and why the win probability moved. | Pro |
Pricing, honestly
Where the free tier stops
Match lookup is free, while the full NFL play-by-play feed with EPA and win probability requires Pro. Bookmaker odds and deeper historical analysis are additional paid capabilities.
Free key
{ "data": { "status": "live", "score": { "home": 7, "away": 10 } } }Solo
{ "data": { "status": "live", "score": { "home": 7, "away": 10 }, "has_odds": true } }- Score and linescore are free; Pro unlocks full NFL play-by-play with EPA and win probability.
- has_odds on a match response tells you whether a betting layer is available for that game; the odds themselves are Edge-plan (see the betting-model tutorial).
More tutorials
Other build guides
- How to build a fantasy hockey app
- How to build a fantasy basketball app
- How to build a soccer xG app
- How to build a cricket app
- NFL fantasy football API tutorial
- How to build an NFL scouting tool
- How to build an NFL conditions analytics tool
- NFL betting model API tutorial
- How to build an NCAAF scoreboard and live game center
- How to build college football rankings and conference standings
- How to build an NCAAF fantasy football app
- How to build a soccer fantasy app
- How to build a soccer betting model
- How to build a soccer live match center
- How to build a soccer scouting tool
- How to build an NCAAF betting model
- How to build an NCAAF conditions analytics tool
- How to build a WNBA live scoreboard
- How to build a WNBA quarter tracker
- How to build a WNBA odds board
- How to build a WNBA schedule browser
- How to build a WNBA team-form tool
- How to build an NCAAB scoreboard
- How to build a March Madness archive
- How to build an NCAAB game-detail view
- How to build an NCAAB team-form dashboard
- How to build an NCAAB head-to-head tool
Questions
- Is this actually live, or polled?
- Polled, and the API says so rather than implying otherwise. /v1/matches/:id carries a meta.note stating when a game has no live adapter listing it and is being served from the stored tier instead. Build your polling interval around that disclosure rather than assuming push.
- Why do matches and play-by-play use different ids for the same game?
- get_matches (and every /v1/matches route) returns a canonical match UUID; /v1/nfl/games/:game_id/plays is keyed on a text game id, a string like "2025_01_DAL_PHI". Nothing joins them today, so carry both if your app needs both feeds for one game.
- Do I need to compute win probability myself?
- No. wp arrives precomputed on every play-by-play row, alongside epa. A chart is a sort and a filter, not a model.
- Can an agent build this end to end with no direct HTTP calls?
- Not quite. get_matches covers score and status. Drives, down-and-distance and win probability have no MCP tool yet — an agent has to call /v1/nfl/games/:game_id/plays directly with the same API key.
Related APIs
Other APIs you can build with: