> For clean Markdown of any page, append .md to the page URL.
> For a complete documentation index, see https://docs.datafortennis.com/llms.txt.
> For AI client integration (Claude Code, Cursor, etc.), connect to the MCP server at https://docs.datafortennis.com/_mcp/server.

# Live revisions and prediction history

> Join match states, points and predictions by canonical IDs and measure observed pipeline delay.

The same canonical `mat_`, `ply_`, `trn_`, `edt_`, `drw_` and `pnt_` identities are used across live states, predictions and historical publication. A live match can exist before inclusion in a named historical release. An older release is not changed to include it; hydrate its operational state with the live endpoints until historical publication is available.

## Identity and correction rules

| Identity/version | Grain and join |
| --- | --- |
| Match `id` / `match_id` | Both denote the same canonical match identity. The envelope uses `match_id` for routing. |
| `player_ids` | Fixed side order, shared by score, points and probabilities. Names are display data. |
| `score_revision` | Changes when normalized sets, game points, server, status or interruption reason changes. A repeated observation does not restart the enrichment deadline. |
| `prediction_input_revision` / prediction `state_revision` | Full model input version. These must match for a composed current prediction, even when the displayed score did not change. |
| Point `id`, `sequence`, `revision` | Stable logical point identity, match-local ordinal and corrected payload version. Sequence is not a stream cursor or score revision. |
| `prediction_id` | Immutable accepted model result. Recalculation creates another ID. |
| `event_id` and `cursor` | Committed stream event identity and global order. Lifecycle transition IDs are shared with their live events. |

Source match identifiers, alternate player-name spellings and provider routing fields stay private. Conflicting identities and duplicate primary cards are rejected before public admission. Additional source observations must reconcile into the authoritative identity; name similarity alone does not create a second public match.

A score-only fallback has `point_id: null`. It remains associated with its exact `score_revision`. A later confirmed point is not retroactively attached to that immutable result.

## Read current data and retained history

| Request | Result |
| --- | --- |
| `GET /v2/live/matches` | Paginated tracked states; optional current `status`, `tour`, `gender` and `category` filters. |
| `GET /v2/live/matches/{matchId}` | Current operational match projection. |
| `GET /v2/live/matches/{matchId}/points` | Latest observed versions of the match's unretracted points. |
| `GET /v2/live/matches/{matchId}/status-history` | Observed lifecycle transitions, including the preceding status and reason. |
| `GET /v2/live/matches/{matchId}/prediction` | Current prediction state and freshness. |
| `GET /v2/live/matches/{matchId}/predictions` | Immutable accepted prediction history, ordered by `history_sequence`. |
| `GET /v2/live/predictions/{predictionId}` | One immutable accepted result. |

Filter prediction history with `point_id` and/or `score_revision`. Cursors are signed and bound to the account, route, match and filters; do not reuse them with different filters. History retention is 180 days for superseded results; the latest result can remain beyond that window. A removed or expired history record can return `404`.

Polling should use a new ordinary GET request. Reusing an idempotency key is a retry of the original operation, not a request for a new polling sample. Pagination of operational live states is dynamic; use a named release for an immutable historical dataset.

For example, `GET /v2/live/matches?status=cancelled` selects currently cancelled tracked matches. Status history preserves an earlier interruption even if a match later resumed, including explicit terminal corrections. Statuses include `scheduled`, `warmup`, `live`, `suspended`, `finished`, `retired`, `walkover`, `cancelled` and `unknown`. A rain delay or play abandoned for the day is an interruption, not automatically a permanent cancellation.

Commentary is stored privately and deduplicated by content with snapshot links. Only normalized lifecycle state and an allowed interruption reason are public. Commentary text and general card insights are not stream or REST fields.

## Time semantics and measured delay

| Clock | What it records |
| --- | --- |
| `score_observed_at` | First time the collector observed this normalized score revision. |
| `source_last_observed_at` | Latest source observation/heartbeat, including repeated score states. |
| `score_received_at` | Database acceptance of the score revision. |
| Point `source_observed_at`, `received_at` | First observation and acceptance of this point payload revision. A full prefix may reveal earlier points together. |
| `enrichment_received_at` | Acceptance of the coherent enrichment used at inference admission. |
| `prediction_requested_at` | Acceptance clock of the stored model input event. It can equal `score_received_at`; it is not the time an inference HTTP request started. |
| `computed_at` | Acceptance/storage of the computed model result. |
| Event `published_at` | Outbox event timestamp; delivery becomes visible after transaction commit. |
| Client receive time | Record in your own process when the frame arrives. It is not a server-produced timestamp. |

Clocks use UTC and preserve available microsecond evidence precision. Clock precision is not proof of physical event-time accuracy. `score_observed_at` is an observed pipeline boundary, not the time the ball was struck or the point ended on court.

Useful measurements are `score_received_at - score_observed_at`, `prediction_requested_at - score_received_at`, `computed_at - prediction_requested_at`, and your live receive time minus `computed_at`. The last two include queueing and transport, not only CPU inference time. Exclude replay frames from live-latency samples and synchronize participating clocks.

For first-prediction latency, group by `match_id` plus `score_revision` and choose the earliest `computed_at` from immutable prediction history. Report later input/enrichment revisions separately. Include eligible score revisions with no accepted result in the availability denominator; a histogram of successful calculations alone does not measure availability. Superseded scores, startup boundaries and source-stale intervals need explicit categories.

Pending, unavailable, stale or terminal current states carry null probabilities and no calculation identity or clocks from an earlier score. Use immutable history to inspect previous results. A missing result can reflect missing required inputs or a dependency failure; a public `unavailable` state does not imply that missing inputs were filled.

## Coverage and units

Live statistics use the canonical metric code, side, period, numerator, denominator, value and unit. Ratios use `[0,1]`; a zero denominator yields null. Missing outcome classifications do not imply zero aces or errors. Point `result_code` is a canonical outcome or null.

`rally_count_by_side` contains observed per-side source counts and `rally_length_missing` records their missingness. These values are not promoted to exact rally lengths or stroke trajectories. `serve_speed_raw` has an unverified unit and must not be assumed to be km/h. The public schema does not label uncertain point-duration fields as timestamps.

`features_used` measures finite model inputs for a particular result; `features_total` is 77. Coverage varies by point, denominators, available profiles/rankings, matched historical baselines and live enrichment. Recorded source capability has reached 70 inputs, but neither 70 on every point nor model accuracy/calibration is guaranteed by that count.

## Bounded live pages

A page contains at most the requested `limit` and may contain fewer rows to fit the authenticated response byte budget. Follow `page.next_cursor` until it is null; a short page is not a completion signal. The cursor refers to the last returned row and remains bound to the principal, operation and filters. An individual row that cannot fit still returns `413 RESPONSE_LIMIT`; the byte cap is never disabled. Live pages describe a changing observed set, not a frozen historical release.