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.

EntityWhat it is
sportbaseball, basketball, soccer, tennis, golf, hockey, cricket, football, rugby, mma, racing.
leagueA competition under a sport — many per sport (nba, wnba, ncaab under basketball; eng.1 … +350 under soccer).
seasonA league's year/edition (e.g. mlb 2026).
teamA club/franchise. Has a season-scoped roster of players.
playerFirst-class. Plays via a team (roster) or directly as a competitor (tennis, golf, MMA, racing).
rosterThe season-scoped link between a team and a player (so trades and transfers stay historically correct).
venueWhere 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.

ShapeCompetitorsExamples
Team2 teams (home / away)NBA, MLB, NHL, NFL, soccer, cricket, rugby
Head-to-head2 players (side_a / side_b)Tennis, MMA / boxing
FieldN 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.

Questions? Talk to us. Pre-launch — endpoints illustrate the shape of the API.