---
title: "League standings table"
method: GET
path: "/v1/standings"
tags: ["standings"]
---

# League standings table

`GET /v1/standings`

## Request

- Base URL: `https://api.bigballsdata.com`
- URL: `https://api.bigballsdata.com/v1/standings`
- Auth: one of:
  - API key in header `x-api-key`
  - HTTP bearer

## Query parameters

- `league` string, required: A short league code, not an id. `GET /v1/leagues` returns the authoritative list in its `id` field; that is the value to pass to every `?league=` parameter. Some codes accept an alias (`serie_a` and `seriea` both resolve).
- `season` string
- `sport` 'football' | 'basketball' | 'baseball' | 'ice_hockey' | 'cricket' | 'mma' | 'boxing' | 'formula1' | 'american_football'
- `conference` string

## Response `200`

ok

- object: The standard response wrapper. Only `data` is guaranteed. `error` is absent (not null) on 16 of 40 live 200 responses measured 2026-09-18, so test for the KEY before reading it; this block declared it required until then. On a non-2xx, `error` is present and `data` is absent.
  - `data` object, required
    - `standings` Standings[], required
      - `league_id` string, 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.
      - `league_name` string, required
      - `sport` 'football' | 'basketball' | 'baseball' | 'ice_hockey' | 'cricket' | 'mma' | 'boxing' | 'formula1' | 'american_football', required
      - `season` string, required
      - `rows` StandingRow[], required
        - `rank` integer, nullable, required: 1-indexed position in the table AS WE SERVE IT, computed by us at read time over exactly the rows in this response, so it is always contiguous 1..N with no gaps and never null while any row in the group carries an ordering metric. `rows` are returned in this order, so the array order and this field cannot disagree. ORDERING: by the competition's own metric. For association football (sport `football`, including MLS and the Champions League) that is league points, 3 per win and 1 per draw, then goal difference (points_for minus points_against), then goals for. For every other sport it is `win_pct` as the league itself defines it. For NHL that is points percentage, (2W+OTL)/(2*GP), not W/(W+L). It falls back to wins/(wins+losses+ties) when the source supplies no win_pct. TIEBREAK, identical for every sport and applied in this order: more wins, then fewer losses, then team_name ascending, then team_id ascending. team_id makes it a total order, so the same rows always produce the same table. Teams that are genuinely level therefore get ADJACENT DISTINCT positions rather than a shared one: `rank` is a position, not a ranking class. BEFORE 2026-09-24 this field was the ingest's running counter over the vendor's conference/division tree and was NOT a table position: measured over the served rows that day, 19 of 29 adjacent NBA pairs had the lower-ranked team with the better record, and NHL (32 rows) and NCAAF (138 rows) carried null on every row. It is null now only when no row in the group carries any ordering metric at all, in which case we decline to invent one.
        - `team_id` string, 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.
        - `team_name` string, required
        - `logo_url` string, nullable: Treat as opaque; do not construct it.
        - `wins` integer, required
        - `losses` integer, required
        - `ties` integer: Draws. Always present, and 0 for sports that cannot draw, so a 0 here does not distinguish "no draws" from "draws impossible"; branch on `sport` for that.
        - `win_pct` number, nullable
        - `games_played` integer, required
        - `points_for` integer, nullable: Goals/runs/points scored, named uniformly across sports. This is NOT competition points; no points column is served.
        - `points_against` integer, nullable
        - `streak` string, nullable
        - `conference` string, nullable: The team's grouping inside the league, spelled as the league spells it. NULL wherever no grouping is recorded, which is most leagues; NULL means "not recorded", never "independent", which is itself a label ("Division I Independents"). Filter a table down to one grouping with `?conference=`.
        - `conference_wins` integer, nullable: Wins counted ONLY over matches where both teams carry the same non-null `conference`. NULL when `conference` is null.
        - `conference_losses` integer, nullable
        - `conference_rank` integer, nullable: 1-indexed position inside `conference`. NULL when `conference` is null; a rank inside no group is not a number.
        - `updated_at` string, date-time
  - `meta` ResponseMeta: Response metadata. NOTHING here is guaranteed: measured over 38 live envelopes on 2026-09-18, `cached` and `request_id` appeared in 29, `source` in 28, `cache_age_ms` in 9 and `confidence` in 6. This block previously declared all five REQUIRED, which no response has ever satisfied. Treat every field as optional and branch on its absence. Routes add their own keys here (`count`, `limit`, `offset`, `note`, `total`, and per-endpoint ones), so do not assume a closed set.
    - `date_window` object: Present when ?date= was applied. The exact window the filter used, so an omission at a day boundary is visible. start_utc is inclusive, end_utc exclusive. timezone is UTC, or the zone named by ?tz=, in which case the window is that local day. Boxing card dates are published event dates, so timezone and bounds are null there.
      - `date` string
      - `timezone` string, nullable
      - `start_utc` string, date-time, nullable
      - `end_utc` string, date-time, nullable
      - `note` string
    - `source` 'official-league' | 'aggregator-paid' | 'aggregator-free' | 'community-scraper' | 'forecast' | 'mixed' | 'cache' | 'none' | 'unknown-source' | 'stored' | 'catalogue' | 'gateway' | 'coverage-snapshot': Opaque source-tier label. Customers never see the vendor identity. `forecast` marks a weather forecast (GET /v1/matches/{id}/weather).
    - `confidence` number, float
    - `cached` boolean
    - `cache_age_ms` integer
    - `request_id` string
    - `fields_missing` string[]
    - `note` string: Short factual disclosure about something this response deliberately withheld, present only when there is something to disclose. On live scores it appears when `match_id` could not be resolved to a stored match and was therefore omitted: it gives the counts and points at `/v1/stored/matches`, whose ids you can call back into. Absent when every served id resolves. Distinct from `message`, which explains `coverage: false` or a stale response. This one is attached to a response that DID serve data and names the part of it that is deliberately missing.
  - `error` ApiError
    - `code` string, required
    - `message` string, required
    - `details` unknown

---

[API](https://skmtc.dev/bigballsdata/apis/big-ball-sports-api.md) · [All operations](https://skmtc.dev/bigballsdata/apis/big-ball-sports-api/llms.txt) · [OpenAPI document](https://skmtc.dev/bigballsdata/apis/big-ball-sports-api/revisions/31fb54ac414e?raw)
