StatsHub Docs
APIAPI v2

API v2

Canonical football resources, signed cursor pagination, Problem Details, and authenticated account writes.

API v2 is the canonical StatsHub contract for new integrations. It groups data around domain resources, uses stable response envelopes, and documents every operation with closed OpenAPI schemas.

https://statshub.com/api/v2

The legacy /api contract remains available unchanged.

Two tiers, one surface

Every capability a StatsHub client can reach on /api is reachable on /api/v2. They are not all served the same way, and the difference decides how much work a migration is.

Designed resources are the ones this page describes: cursor pagination, {data, page, links}, opaque string ids, RFC 3339 timestamps, closed schemas. They were built for v2 rather than ported, so their payloads differ from the legacy route they replace, and moving to one means updating response parsing.

Bridged capabilities are the rest. Each is the legacy handler reached at a canonical v2 URL — /api/team/{id}/roster answers at /api/v2/teams/{teamId}/roster — with the payload unchanged, byte for byte. Errors are still v2 errors: the whole /api/v2 subtree normalizes an error body into Problem Details on the way out. Moving to one of these is a URL change and nothing else.

Of 215 operations, 81 are designed and 134 are bridged.

Nothing marks the tiers apart in the OpenAPI document. Tell them apart by the response: a designed resource returns {data} or {data, page, links} with string ids and RFC 3339 timestamps, and a bridged one returns the legacy shape.

A bridged route can graduate to the designed tier. Its URL is already canonical, so that is a payload change under a stable path. Graduating is not only a reformatting — the designed resource is free to answer a better question, and several have:

CapabilityWhat changed on the way across
Fixture formationsConfirmed, projected and last-known resolve in one request, and one statement. Clients used to ask three endpoints and fall through.
Card rankings/rankings/{player,team,referee}-cards take the competition as a parameter. The legacy boards were pinned to one tournament by a season id and a hard-coded referee list.
Player season statisticsSeventy flat keys, counts as numbers and rates as strings, become typed groups with a source saying which feed answered.
Team player statisticsA stat map keyed by fixture id becomes one flat row per player and fixture, which is what makes it pageable.
Bet trackerThe account-wide P&L splits out of the list envelope it was recomputed in on every page turn.
Commentary statisticsA GET. It was a POST because the fixture ids are a list.
Betting historyRepeated bracket=min:max:label instead of a JSON-encoded string parameter.

What is on /api and not on /api/v2 is the operational surface: the admin and ingestion consoles, Stripe, the Telegram webhook, the MCP transport, the XML sitemaps and the tweets proxy. None of those is a resource, and several do not answer JSON at all.

Both tiers are listed in the v2 endpoint reference.

Resource vocabulary

Legacy termV2 resource
unique tournamentcompetition
eventfixture
categoryregion
tournamentseason phase

Nested routes express ownership. Examples include /competitions/{competitionId}/seasons, /seasons/{seasonId}/standings, and /fixtures/{fixtureId}/incidents.

Response envelopes

A single resource is returned under data:

{
  "data": {
    "id": 17,
    "name": "Premier League"
  }
}

Cursor-paged collections add navigation state under page and resolvable URLs under links:

{
  "data": [],
  "page": {
    "limit": 25,
    "nextCursor": "eyJ2IjoxLCJrIjp...",
    "previousCursor": null,
    "hasNext": true,
    "hasPrevious": false
  },
  "links": {
    "self": "/api/v2/fixtures?limit=25",
    "next": "/api/v2/fixtures?after=eyJ2IjoxLCJrIjp...&limit=25",
    "previous": null
  }
}

Cursor pagination

Paged collections accept:

ParameterMeaning
limitPage size from 1 to 100.
afterOpaque cursor for the next page.
beforeOpaque cursor for the previous page.

after and before are mutually exclusive. Cursors are signed and bound to the collection's filters and sort order; clients should store and replay them without decoding or editing them.

Ranked analytical read models expose conventional page and pageSize parameters when users need stable page numbers across arbitrary sort columns. They live under /rankings; their response schemas define the accompanying page metadata precisely.

Analytical resources

PrefixPurpose
/rankingsPrice outliers, prop trends, hit rates, referee performance, and substitute impact.
/predictionsModel-generated team statistic projections.
/data-coverageFixture-level ingestion coverage and aggregate coverage summaries.
/lineup-projectionsPredicted or confirmed starters across upcoming fixtures.
/matchday-fixturesThe matchday board's fixture read model and competition facets.
/value-betsModel-backed market selections whose offered prices exceed fair value.

Errors

V2 failures use RFC 9457 Problem Details with application/problem+json:

{
  "type": "https://statshub.com/problems/invalid-request",
  "title": "Invalid request",
  "status": 400,
  "detail": "limit must be between 1 and 100",
  "instance": "/api/v2/fixtures",
  "code": "INVALID_PAGINATION",
  "requestId": "2d8bd660f45b"
}

Treat type or code as the machine-readable error category and detail as diagnostic text. Do not branch on the prose in detail.

Authentication and writes

Public football data is readable without a session. /me and favorite resources require either a Supabase bearer token or the existing Supabase session cookie. Authenticated clients can create, read, update, and delete favorite teams and players with POST, GET, PATCH, and DELETE.

Send JSON writes with Content-Type: application/json. Unknown body fields are rejected, ownership is derived from the authenticated user, and a client cannot read or mutate another user's favorites.

OpenAPI

Use the focused document for SDK generation:

https://statshub.com/api/openapi-v2.json
https://docs.statshub.com/openapi-v2.json

The OpenAPI contracts page lists the combined document for the narrower case where one client intentionally calls both versions.

On this page