---
title: "The per-match status ledger — every status / event_status transition with its UTC instant (BASIC, history)"
method: GET
path: "/matches/{matchId}/status-history"
---

# The per-match status ledger — every status / event_status transition with its UTC instant (BASIC, history)

`GET /matches/{matchId}/status-history`

## Request

- Base URL: `https://api.livetennisapi.com/api/public/v1`
- URL: `https://api.livetennisapi.com/api/public/v1/matches/{matchId}/status-history`
- Auth: one of:
  - HTTP bearer
  - API key in header `X-API-Key`

Added 2026-09-12. Append-only, oldest first: one row per change of `status` and/or `event_status`, with the instant we published it, the value before and the effective value after, the derived `outcome`, and the newest score row at that instant. A correction is a new row, never an edit — a close published as `unresolved` and later confirmed shows the flip to `completed`; a completion that reopened shows `completed -> live`. `basis: observed` rows exist from 2026-09-11T22:45:48Z; `basis: backfill` rows (2026-09-12) were reconstructed from the one stamp per kind the match row kept before the ledger existed (last promotion to live from 2026-09-05, completion instant from 2026-08-21, last reopen, last event_status change) — one row per stamp, overwritten intermediate transitions are not recovered. A correction to a PUBLISHED result — `status`, `event_status`, winner or the final score changing after the match was first published as completed — is a new row with `basis: restatement` (from 2026-09-22), never a silent edit; `result_restated_at` / `result_version` on the match summarise them and `GET /history/matches?restated_since=` finds them. History capability (BASIC and the Historical Data plans), like the tape.

## Path parameters

- `matchId` integer, required

## Query parameters

- `limit` integer
- `offset` integer

## Response `200`

Status transitions, oldest first

- object
  - `data` StatusChange[]
    - `at` string, date-time: The instant we published the change (UTC).
    - `status` object
      - `from` string, nullable
      - `to` string, nullable
    - `event_status` object
      - `from` string, nullable
      - `to` string, nullable
    - `outcome` string, nullable: Derived from the after-state by the Match.outcome definition.
    - `basis` 'observed' | 'backfill' | 'restatement': How the row was made. observed = written at the instant of the change (from 2026-09-11T22:45:48Z); backfill = reconstructed on 2026-09-12 from the one stamp per kind the match row kept before the ledger existed (last promotion to live from 2026-09-05, completion instant from 2026-08-21, last reopen, last event_status change). One row per stamp; overwritten intermediate transitions are not recovered. restatement (added 2026-09-22) = a CORRECTION to a result that had already been published — the match's `status`, `event_status`, winner or final score (sets, games) changed after it was first published as completed (or as a cancelled walkover with a winner). A correction is always a new row, never an edit, written at the instant we made the change; `status` / `event_status` carry before and after as usual (the same value twice when only the winner or the score moved) and its `score` is the result AFTER the correction (null when the match holds no score row, as on a walkover). Every path that can change a result writes one — a source's late final, a second authority's correction, an operator's repair, a completion reopened and re-closed — because it is enforced where the result is written. A re-assertion of the same result, or a change to points alone, is not a correction and writes nothing. `result_restated_at` and `result_version` on the Match summarise these rows. Rows with this basis exist from 2026-09-22; earlier corrections were not signalled.
    - `score` object, nullable: The newest score row at that instant (sets, games, points, server); null when none existed.
  - `meta` ListMeta
    - `limit` integer
    - `offset` integer
    - `count` integer
    - `total` integer, nullable: Size of the whole filtered set. Null when it cannot be counted cheaply.
    - `has_more` boolean: More results exist beyond this page. Read this rather than comparing count to limit. For a complete enumeration of a filtered set, page `offset` forward by `limit` until this is false; on `completed`/`cancelled` listings `total` is null and this is the only end-of-data signal.
    - `next_cursor` string, nullable: Change-feed only (`?updated_since=` / `?cursor=`). Opaque token to pass back as `?cursor=`. Null means you are caught up — stop paging and switch to polling from `watermark`.
    - `watermark` string, date-time, nullable: Change-feed only. The floor to use on your NEXT poll. Deliberately trails the server clock by 5s so a row committed during this request cannot fall through the gap.

## Other responses

- `401`: Missing, unknown, or disabled credentials
- `403`: Your tier doesn't unlock this endpoint
- `404`: No such resource, or no data yet
- `410`: The match id existed and was merged into another match record (since 2026-09-05). The body is `MatchMerged`, not `Error`: it carries a forwarding address, which is the whole point of answering 410 rather than 404.
- `429`: Rate limit exceeded (Retry-After header present). Three body shapes, told apart by `error` and `scope`: the per-MINUTE limit (`rate_limited`, with `upgrade_url`, `tier` and `price` naming the next tier up); the per-DAY quota (`rate_limited` with `scope: "day"`, `limit_per_day`, and `resets_at` — the absolute ISO instant the daily window resets); and `abuse_throttled` with `retry_at_epoch` — a 24-hour block applied to clients that keep hammering far past their cap, which a well-behaved retry loop never sees. Fix the loop rather than retrying through it.

---

[API](https://skmtc.dev/livetennisapi/apis/live-tennis-api.md) · [All operations](https://skmtc.dev/livetennisapi/apis/live-tennis-api/llms.txt) · [OpenAPI document](https://skmtc.dev/livetennisapi/apis/live-tennis-api/revisions/7881947e8e43?raw)
