Skip to content

Public read API

The public read API provides canonical access to cricket reference and analytical data.

No sign-in or bearer token is required. A missing X-API-Key uses the bounded anonymous policy; supplying a valid active consumer key keeps the same URL and response contract while selecting that consumer's rate limit, UTC daily quota and safe usage telemetry. A supplied malformed, unknown or revoked key returns 401 API_KEY_UNAUTHORIZED rather than falling back to anonymous access. See Consumer API keys, rate limits and quotas.

All application data is served through the handwritten Express backend. Supabase-generated Data API or PostgREST endpoints are not used as the public Sport Analytics API.

Base path

/api/v1

Resources

The public API exposes:

  • competitions;
  • seasons;
  • fixtures;
  • accepted fixture events;
  • fixture statistics;
  • competitors; and
  • participants.

For cricket, competitors are teams and participants are people/players.

Competition endpoints

GET /api/v1/competitions
GET /api/v1/competitions/{competitionId}

Supported collection filter:

name

Deterministic ordering:

name ASC
competitionId ASC

Season endpoints

GET /api/v1/seasons
GET /api/v1/seasons/{seasonId}

Supported collection filter:

competitionId
name

A season resource is derived from a competition and the season value recorded on its fixtures. The name filter matches either the season label or its competition name.

Season resources include competitionName alongside competitionId, allowing consumers to present the associated competition without making a separate name-resolution request. The stable identifier remains available for routing and relationships.

seasonId is a stable opaque identifier derived by the backend. Consumers must not decode or derive meaning from its representation.

Deterministic ordering:

competitionId ASC
season ASC

Fixture endpoints

GET /api/v1/fixtures
GET /api/v1/fixtures/{fixtureId}

Supported collection filters:

competitionId
seasonId
competitorId
gender
startDateFrom
startDateTo

The date bounds are inclusive.

Example:

GET /api/v1/fixtures?competitionId=12&competitorId=20&limit=25

Example response:

{
  "data": [
    {
      "fixtureId": "481",
      "competitionId": "12",
      "competitionName": "Example Competition",
      "seasonId": "season_...",
      "season": "2026",
      "seasonLabel": "2026",
      "competitors": [
        {
          "competitorId": "20",
          "name": "Team One"
        },
        {
          "competitorId": "21",
          "name": "Team Two"
        }
      ],
      "matchType": "T20",
      "teamType": "international",
      "gender": "male",
      "ballsPerOver": 6,
      "scheduledOvers": 20,
      "startDate": "2026-08-09",
      "endDate": "2026-08-09"
    }
  ],
  "pagination": {
    "nextCursor": null
  }
}

competitionName, seasonLabel and the ordered competitors summaries provide the readable relationship context required to construct a fixture title such as Team One vs Team Two. Existing technical identifiers remain available for routing and machine consumers. competitionName is null when the fixture has no associated competition.

Deterministic fixture ordering:

startDate ASC
fixtureId ASC

Fixture event endpoints

GET /api/v1/fixtures/{fixtureId}/events
GET /api/v1/fixtures/{fixtureId}/events/{eventId}
GET /api/v1/fixtures/{fixtureId}/events/export.json
GET /api/v1/fixtures/{fixtureId}/events/export.csv
GET /api/v1/fixtures/{fixtureId}/statistics/{statisticId}/events/export.json
GET /api/v1/fixtures/{fixtureId}/statistics/{statisticId}/events/export.csv

These endpoints are public. They return only the current accepted revision of each cricket delivery event. Events from pending or rejected submissions, superseded accepted revisions, submitter accounts, submission identifiers, source event identifiers, revision numbers and audit timestamps are not exposed.

The collection supports these filters:

inningsId
competitorId
participantId
overNumber
wicketKind

competitorId selects innings in which that competitor bats. participantId matches any event in which the participant is the striker, non-striker, bowler, dismissed player or an identified fielder. overNumber is zero-based. wicketKind uses the cricket dismissal code stored by the event model, such as caught or run_out.

Example request:

GET /api/v1/fixtures/481/events?participantId=30&overNumber=4&limit=2

Example response:

{
  "data": [
    {
      "eventId": "7021",
      "fixtureId": "481",
      "competitionId": "12",
      "competitionName": "World Twenty20",
      "inningsId": "900",
      "inningsOrdinal": 0,
      "sequenceNumber": 25,
      "overNumber": 4,
      "positionInOver": 0,
      "ballNumber": "4.1",
      "battingCompetitorId": "20",
      "battingCompetitorName": "India",
      "bowlingCompetitorId": "21",
      "bowlingCompetitorName": "Pakistan",
      "strikerParticipantId": "30",
      "strikerParticipantName": "Opening Batter",
      "nonStrikerParticipantId": "31",
      "nonStrikerParticipantName": "Non-striker",
      "bowlerParticipantId": "42",
      "bowlerParticipantName": "Opening Bowler",
      "runs": {
        "offBat": 4,
        "extras": 0,
        "total": 4,
        "nonBoundary": false
      },
      "extras": {
        "wides": null,
        "noBalls": null,
        "byes": null,
        "legByes": null,
        "penalty": null
      },
      "wickets": []
    }
  ],
  "pagination": {
    "nextCursor": "opaque-next-cursor"
  }
}

The occurrence order is fixed and cannot be overridden:

inningsOrdinal ASC
sequenceNumber ASC
eventId ASC

The event identifier is the stable API identifier for that accepted delivery revision and can be used with the detail endpoint. ballNumber is display-only and never controls identity or order. An event cursor is bound to its fixture; using it for another fixture returns INVALID_CURSOR. A known fixture with no accepted events returns an empty collection, while an unknown fixture or event returns HTTP 404.

Fixture event exports

The two export endpoints provide the Basic-tier dataset slice for the tabular accepted fixture event resource:

GET /api/v1/fixtures/{fixtureId}/events/export.json
GET /api/v1/fixtures/{fixtureId}/events/export.csv

They are public and derive exclusively from the same accepted-event public-read model as the event collection. Consequently, they preserve stable event, fixture, innings, competitor and participant identifiers and add readable competition, team and participant names. They also retain delivery fields such as ballNumber, runs, extras and wicket kinds, while never exposing account data, submission administration fields, revision history, audit data or secrets.

Both formats accept the same filters as GET /fixtures/{fixtureId}/events: inningsId, competitorId, participantId, overNumber and wicketKind. They do not accept cursor or limit, and passing either is a validation error: an export is the whole filtered result set in the collection's fixed occurrence order. The server reads it by following the event collection's nextCursor at the maximum page size of 100 until the cursor is exhausted.

Issue #467 replaced an earlier design in which each export was capped at the first 100 events and the cursor was discarded. That cap was silent: 23,832 of the 28,021 imported innings have more than 100 accepted events, so most innings exports were short with no indication, and the calculation trace beside the export control displayed events the file did not contain.

A synchronous export is still bounded, at 5,000 events. The largest fixture in the imported corpus has 346 accepted events. An export that would exceed the bound fails as a whole with HTTP 422 and error code EXPORT_TOO_LARGE, naming the filters that narrow it; a short file is never returned. A failure while reading any page, including after earlier pages have been read, returns an error response rather than the rows read so far. Versioned snapshots and background jobs remain outside this scope.

The JSON endpoint returns { "data": [...] } without pagination metadata. The CSV endpoint returns text/csv; charset=utf-8 with a trace-specific attachment name such as fixture-481-player-30-over-4-events.csv. The filename includes every active innings, team, player, over and wicket-kind filter; an unfiltered export uses all. Its columns and their order are stable: event, fixture and competition identity; zero-based innings and delivery position; competitor and participant identifiers paired with names; run and extras fields; then flattened wicket identifiers, kinds, dismissed participants and fielders paired with names. Multiple wicket values use | within their escaped CSV field. Both formats represent an absent extras category as numeric 0.

Example:

GET /api/v1/fixtures/481/events/export.csv?participantId=30&overNumber=4

Calculation-trace exports

A calculation trace exports exactly the events it displays:

GET /api/v1/fixtures/{fixtureId}/statistics/{statisticId}/events/export.json
GET /api/v1/fixtures/{fixtureId}/statistics/{statisticId}/events/export.csv

The event set is the statistic's own contributing events, taken from the same derivation as GET /fixtures/{fixtureId}/statistics/{statisticId}?includeContributors=true, and read in full through the same paging and bound as the filtered export. The rows, columns, escaping and filename convention are identical to the filtered export. These endpoints accept no query parameters.

This is deliberately not the same set as the filtered export with participantId. That filter matches every delivery involving the participant, including those where they were the non-striker, were dismissed or fielded, and it does not exclude super-over innings. A player's calculation trace contains only the standard-innings deliveries they faced or bowled, because those are the deliveries their batting and bowling figures are calculated from. For participant 14255 in fixture 8936 the trace holds 6 deliveries and the participant filter 11. The filtered export remains available for that wider involvement.

Because the trace and the event rows are read separately, a correction accepted between the two reads could make them differ. The export then returns HTTP 409 with EXPORT_TRACE_CHANGED instead of a file that disagrees with the trace it is named after.

Fixture statistics endpoints

GET /api/v1/fixtures/{fixtureId}/statistics
GET /api/v1/fixtures/{fixtureId}/statistics/{statisticId}

Both endpoints are public. Only fixtures and delivery revisions belonging to accepted submissions are eligible for publication. The collection returns stable, opaque statistic identifiers for each innings team-total resource and each participant fixture-statistics resource. A resource identifier can be used on the detail endpoint and remains stable when the same accepted event state is replayed.

The default response is compact: it gives sourceEventCount but omits the delivery records. Add the following query only when a trace is required:

includeContributors=true

When requested, contributingEvents contains the accepted delivery records in innings and inningsSequence order. Superseded or pending/rejected delivery revisions are never exposed.

Example compact response:

{
  "data": {
    "fixtureId": "481",
    "status": "complete",
    "scope": {
      "superOversIncluded": false
    },
    "outcome": {
      "kind": "won",
      "winnerCompetitorId": "20",
      "winnerCompetitorName": "Team One",
      "eliminatorCompetitorId": null,
      "eliminatorCompetitorName": null,
      "margin": {
        "type": "wickets",
        "value": 8
      },
      "method": null,
      "decidedByBowlOut": false
    },
    "warnings": [],
    "statistics": [
      {
        "statisticId": "stat_opaque-value",
        "fixtureId": "481",
        "scope": "innings",
        "statisticCode": "team_total",
        "inningsId": "900",
        "inningsOrdinal": 0,
        "competitorId": "20",
        "competitorName": "Team One",
        "sourceEventCount": 120,
        "metrics": {
          "deliveryRuns": 154,
          "penaltyRuns": 5,
          "totalRuns": 159,
          "wicketsLost": 6,
          "legalBalls": 120,
          "overs": "20.0",
          "runRate": 7.95,
          "extras": {
            "total": 9,
            "wides": 2,
            "noBalls": 1,
            "byes": 0,
            "legByes": 1,
            "penaltyRuns": 5
          }
        }
      }
    ]
  }
}

Team and player statistic resources expose readable names alongside their stable identifiers. Participant statistics include participantName and, where known, competitorName. Fixture outcomes include the readable winning-team name where applicable.

When includeContributors=true is requested, each contributing event retains the striker and bowler participant identifiers and also includes strikerParticipantName and bowlerParticipantName. This allows user-facing calculation traces to identify the players without extra lookup requests. The innings metrics directly support a scorecard summary such as Team One 159/6 (20.0 overs) ยท RR 7.95; clients do not need to page through delivery events or reproduce dismissal, extras, or miscounted-over rules.

status is partial rather than failing the request when accepted source data is incomplete. The warnings array then gives stable warning codes, and rate metrics with a zero denominator are null. See Fixture statistic calculations for the complete mapping and trace rules.

Competitor endpoints

GET /api/v1/competitors
GET /api/v1/competitors/{competitorId}

Supported filters:

competitionId
seasonId
name

A competitor represents a cricket team.

Deterministic ordering:

name ASC
competitorId ASC

Participant endpoints

GET /api/v1/participants
GET /api/v1/participants/{participantId}
GET /api/v1/participants/{participantId}/fixtures

Supported filters:

fixtureId
competitorId
name

Only public participant fields are returned.

Internal source references, authentication data, submission records and audit information are not exposed.

The fixture-history endpoint returns the fixtures in which the player was selected, newest first. Each entry includes the readable competition name, season, date, match type, both named teams, the player's team and squad role, and that player's available fixture-level batting and bowling figures. Selection is participation: a selected player remains in the history even when they did not bat or bowl, in which case the corresponding figure is null.

Example:

GET /api/v1/participants/30/fixtures?limit=25
{
  "data": [
    {
      "fixture": {
        "fixtureId": "481",
        "competitionId": "12",
        "competitionName": "Example Competition",
        "seasonId": "season_...",
        "season": "2026",
        "seasonLabel": "2026",
        "competitors": [
          { "competitorId": "20", "name": "Team One" },
          { "competitorId": "21", "name": "Team Two" }
        ],
        "matchType": "T20",
        "teamType": "international",
        "gender": "male",
        "ballsPerOver": 6,
        "scheduledOvers": 20,
        "startDate": "2026-08-09",
        "endDate": "2026-08-09"
      },
      "competitionName": "Example Competition",
      "competitors": [
        { "competitorId": "20", "name": "Team One" },
        { "competitorId": "21", "name": "Team Two" }
      ],
      "competitor": { "competitorId": "20", "name": "Team One" },
      "role": "player",
      "statisticsStatus": "complete",
      "statisticsWarnings": [],
      "batting": {
        "runsScored": 55,
        "ballsFaced": 40,
        "fours": 4,
        "sixes": 2,
        "strikeRate": 137.5
      },
      "bowling": null
    }
  ],
  "pagination": {
    "nextCursor": null
  }
}

statisticsStatus and statisticsWarnings preserve the publication state used by the fixture statistics API. A fixture with incomplete accepted source data or no accepted delivery events is returned with partial status and stable warnings rather than silently omitted. The endpoint does not calculate season or career aggregates.

Deterministic ordering:

displayName ASC
participantId ASC

Deterministic participant fixture-history ordering:

startDate DESC
fixtureId DESC

Pagination

Collection endpoints use cursor pagination.

The default page size is:

50

The maximum page size is:

100

Example:

GET /api/v1/competitions?limit=25

A response may contain:

{
  "pagination": {
    "nextCursor": "opaque-next-cursor"
  }
}

The next request can use:

GET /api/v1/competitions?limit=25&cursor=opaque-next-cursor

Cursors and resource identifiers are opaque. Consumers must not derive meaning from their representation.

Not-found responses

Unknown resource identifiers return HTTP 404.

Example:

{
  "error": {
    "code": "NOT_FOUND",
    "message": "Competition not found."
  }
}

Invalid requests

Invalid filters, page sizes or cursors return HTTP 400 using the shared machine-readable API error format.

OpenAPI

The machine-readable specification is documented in the OpenAPI specification.

AI Declaration

The preceding document was planned, generated, reviewed and edited with the assistance of ChatGPT-Web[GPT-5.6 Sol] and Codex[GPT-5]. The complete export and calculation-trace export documentation for issue #467 was updated with the assistance of Claude Code[Claude Opus 5]. The issue #821 optional consumer identification boundary was documented with the assistance of Codex[GPT-5].