Data model
FirstBuzzer normalizes every sport — team, head-to-head, and field — into one structure. Whatever the source (ESPN, MLB StatsAPI, the NHL API, a licensed feed), the data you receive has the same shape. The model is the standard dimensions + facts pattern: stable entities you reference, and timestamped events that point at them.
Entities (dimensions)
Master records that exist independently and change slowly. Each carries an external_ids map so the same team or player resolves across every source.
| Entity | What it is |
|---|---|
sport | baseball, basketball, soccer, tennis, golf, hockey, cricket, football, rugby, mma, racing. |
league | A competition under a sport — many per sport (nba, wnba, ncaab under basketball; eng.1 … +350 under soccer). |
season | A league's year/edition (e.g. mlb 2026). |
team | A club/franchise. Has a season-scoped roster of players. |
player | First-class. Plays via a team (roster) or directly as a competitor (tennis, golf, MMA, racing). |
roster | The season-scoped link between a team and a player (so trades and transfers stay historically correct). |
venue | Where a competition is played. |
Competitions & competitors
A competition is the canonical contest — one game, one tennis match, one golf round, one MMA bout. The piece that makes every sport fit one schema is the competitor: a competition has 1..N competitors, each of which is either a team or a player.
| Shape | Competitors | Examples |
|---|---|---|
| Team | 2 teams (home / away) | NBA, MLB, NHL, NFL, soccer, cricket, rugby |
| Head-to-head | 2 players (side_a / side_b) | Tennis, MMA / boxing |
| Field | N players (entry, ranked by position) | Golf, racing (F1 / NASCAR / horse) |
{
"schema": "fb.competition.v1",
"id": "baseball_mlb_401766",
"sport": "baseball", "league": "mlb", "season": "2026",
"date": "2026-06-28", "start_ts": "2026-06-28T23:20:00Z",
"status": "final", "venue": "fenway-park",
"competitors": [
{ "entity_type": "team", "entity_id": "team:mlb:bos", "role": "home", "score": 9 },
{ "entity_type": "team", "entity_id": "team:mlb:nyy", "role": "away", "score": 4 }
]
} A field sport uses the same record — just more competitors, ranked by position:
{
"schema": "fb.competition.v1",
"id": "golf_pga_2026_open_r4", "parent_id": "golf_pga_2026_open",
"sport": "golf", "league": "pga", "season": "2026", "round": 4, "status": "final",
"competitors": [
{ "entity_type": "player", "entity_id": "player:pga:scheffler", "role": "entry", "position": 1, "score": -19 },
{ "entity_type": "player", "entity_id": "player:pga:mcilroy", "role": "entry", "position": 2, "score": -17 }
]
} Plays (the events)
The play-by-play record. Every play is attributed to a competitor and, where the source provides it, to the player who made it. event_type + points are the sport-native scoring taxonomy; period gives reconciliation-grade granularity; state carries the running score or leaderboard delta.
{
"schema": "fb.play.v1",
"competition_id": "baseball_mlb_401766",
"seq": 142,
"period": { "unit": "inning", "num": 6, "label": "Top 6" },
"competitor_id": "team:mlb:nyy",
"player_id": "player:mlb:judge",
"event_type": "run", "points": 1,
"detail": "Judge scores on a single to center",
"state": { "home": 4, "away": 5 },
"source": "mlb_statsapi"
} The live feed's event is a projection of this play — the same fields, plus the crowd-consensus tier and confidence. The archive and the live stream share one schema.
Players
Players are addressable entities, not strings — so you can join attribution across games and sources.
{
"schema": "fb.player.v1",
"id": "player:mlb:judge",
"sport": "baseball", "name": "Aaron Judge", "position": "RF",
"external_ids": { "espn": "33192", "mlb_statsapi": "592450" }
} One identity across sources
Every entity has a single canonical FirstBuzzer id mapped to each source's id via external_ids. A game pulled from one source and its play-by-play enriched from another resolve to the same teams and players — so the data stays unified no matter how many feeds back it.
Archive & bulk export
History is stored append-only and versioned (fb.<entity>.v1) and partitioned by sport / league / season. Bulk exports are newline-delimited JSON by league and season, with the same schema as the live feed — so a backtest and production read identically. See FB Archive.