matches

Play-by-play / match events

Scoring events are included on every plan through the default view or ?type=goals. The full soccer minute-by-minute stream (?type=all, including cards, substitutions, shots and VAR) requires Pro, matching the NBA, NHL and NFL play-by-play floor. Where stored soccer event coverage answers the request, ?type=goals excludes penalties that were missed or saved instead of mislabelling them as scored goals. Those events, and VAR-disallowed goals, stay visible with their minute and player under the Pro ?type=all view. In the measured stored-event corpus, this improves agreement with the free score / linescore fields from 22,482 of 22,755 matches (98.80%) to 22,685 of 22,755 (99.69%); 70 of 22,755 matches still require separate reconciliation. For NBA, NHL and NFL matches, ?type=all returns the full play log, which is the same product as each league's dedicated plays route and likewise requires Pro. Lower tiers receive a 403 naming the tier. The match's sport must also be in your plan.

On ?type=all only, NFL rows are projected from the stored NFL play log through the match's canonical UUID. They include down, distance, field position, yards gained, EPA and win probability when the feed supplies them. player_id remains null on these rows: the upstream player identifiers do not yet map one-to-one onto canonical player UUIDs, so the API does not guess. The default scope and ?type=goals return the scoring summary without those play-level fields, which is what keeps them available below Pro.

Soccer shot coordinates and xG. Where we hold shot-level data for a match, ?type=all also returns shot events carrying coordinate_x / coordinate_y (the shooter's position, 0-100 normalised), xg and xgot. Coverage is narrow and documented rather than implied: these fields are populated for FIFA World Cup 2026 matches only, and are null everywhere else. They are absent, not zero, when unknown. The same rows are also served by GET /v1/stored/matches/{id}/plays.

data is ALWAYS an array, including when we hold nothing. Until 2026-09-20 the live-router fallback arm broke that promise: a match we do not cover returned data: {"events": null}, and a match served from the live fan-out returned data: {"events": {"value": [...]}}, so data.map(...) threw on two of the three success bodies this one path could produce. Both now return the array, and the no-coverage signal moved into meta rather than being dropped:

  • meta.coverage: false plus a human-readable meta.message mean we hold no events for this match. These are the same keys GET /v1/nfl/games/{gameId}/plays uses for its empty page.
  • meta.coverage absent with data: [] means the match IS covered and the feed reports no events (for example ?type=goals on a goalless match).
  • meta.count is the length of data, derived from the rows sent.

An empty array and an uncovered match are different answers, so read meta.coverage before concluding a match has no events.

get/v1/matches/{id}/events

Request

  • Base URL: https://api.bigballsdata.com
  • URL: https://api.bigballsdata.com/v1/matches/{id}/events
  • Auth: one of:
    • API key in header x-api-key
    • HTTP bearer

Path parameters

idstring uuid required

A stored BigBalls id. This is the identifier every match, team, player and league endpoint returns, and the one every {id} path segment accepts. Take it from the id field of the corresponding list endpoint and use it as-is.

Example:cf2d81c8-8e7c-4eaf-a515-c51be3962e57

Query parameters

sportstring

Optional and NON-FILTERING. The match id alone identifies the match, so this only resolves the plan gate, the same role it has on /v1/teams/{id}. A value that disagrees with the match's real sport does not change the response: measured 2026-09-20, ?sport=football on an ice_hockey match id returned the ice_hockey match, byte-identical to ?sport=ice_hockey and to no sport at all.

type'goals' | 'all'

goals is the Free scoring-event summary. all is the Pro minute-by-minute/full-play-log product.

Response

ok. data is an array on every 200, empty when we hold no events; see meta.coverage / meta.message for why it is empty.

Example response

{
  "data": [
    {
      "period_display": "2nd Inning",
      "type": "Play Result",
      "team_id": "cf2d81c8-8e7c-4eaf-a515-c51be3962e57",
      "related_player_id": "cf2d81c8-8e7c-4eaf-a515-c51be3962e57",
      "defending_team_id": "cf2d81c8-8e7c-4eaf-a515-c51be3962e57"
    }
  ],
  "meta": {
    "date_window": {
      "timezone": "UTC",
      "start_utc": "2026-09-13T00:00:00.000Z",
      "end_utc": "2026-09-14T00:00:00.000Z"
    },
    "source": "official-league",
    "confidence": 0.95,
    "cache_age_ms": 4200,
    "request_id": "req_abc123",
    "note": "match_id is omitted on 380 of 380 rows in this response. The live feed behind them does not carry an id we can resolve to a stored match, so we return no id rather than one that will not work. For rows with ids you can call back into, use /v1/stored/matches with the same sport or league, for example /v1/stored/matches?league=epl."
  },
  "error": {
    "code": "NOT_FOUND"
  }
}

Changes

No recorded changes to this operation across all 3 revisions of this API.