> 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 WebSocket quickstart

> Receive canonical singles match states and independently entitled ATP predictions through one V2 connection.

Current measured availability and source checks: [System status](https://status.datafortennis.com). Read the [monitoring definitions](/start-here/service-status) before interpreting uptime or an empty live response.


Connect to **`wss://api.datafortennis.com/v2/live`**. Every server message uses `schema_version: "2.0"`. Scores, observed points, statistics, lifecycle changes and ATP predictions share canonical match IDs and committed event order. Collection and calculation are shared across subscribers.

This is an engineering preview. Access requires `live`; predictions additionally require `predictions`. Contract support does not imply a commercial plan, complete source coverage, model accuracy or a live SLA.

## Supported coverage cells

| Tour / category | Raw singles states | Model | Enrichment |
| --- | --- | --- | --- |
| ATP250 / ATP500 / ATP1000, men | Supported adapter | Admitted ordinary best-of-three matches, including qualifying | Coherent observed point prefixes and derived statistics when available |
| ATP Challenger, men | Supported adapter; category `Challenger` | Unsupported | Score and lifecycle; point/statistics coverage is not promised |
| WTA main tour / WTA125, women | Supported adapter | Unsupported | Score and lifecycle; point history is not promised |
| ITF men / women | Supported adapter; categories such as `M25` / `W35` | Unsupported | Observed point prefixes and allowlisted provider statistics when available |

Actual delivery depends on the source, current matches and accepted observations. Missing enrichment remains missing. Doubles, mixed matches and unsupported formats are outside this rollout. Predictions remain `qualification: "shadow"`.

## Obtain a connection ticket

Call `POST /v2/live/stream-tickets` with your private API key from a trusted backend. A browser receives the short-lived ticket from its own authenticated backend. Keep API keys out of browser bundles, URLs and logs. For server-side HTTP calls, send an application User-Agent such as `DataForTennisSDK/1.0`. The current edge policy can reject generic library identifiers before a JSON API response; an identified Python client was verified during native acceptance.

```json
{
  "match_ids": ["mat_00000000-0000-4000-8000-000000000001"],
  "include_predictions": true,
  "origin": "https://your-application.example"
}
```

This ID is an illustrative template. Discover actual IDs with `GET /v2/live/matches` or an unrestricted stream snapshot. Omit `origin` for a server client without an Origin header. A browser must use its exact HTTPS origin.

Tickets may restrict `tours`, `genders` and `categories`, for example `{"tours":["wta"],"genders":["women"],"categories":["125"]}`. Restrictions are signed. A subscription can narrow them, never broaden them. A live-only ticket receives raw data without prediction objects or events.

The response contains `endpoint`, `version`, `ticket`, `expires_at` and `renew_after_seconds`; its version is `2.0`. The lease lasts at most 60 seconds. Request a new ticket after 30 seconds, authenticate again on the existing connection, then resubscribe. Renewal resets the subscription. Account/key changes affect the next lease; a previous lease expires normally.

Retry transient renewal failures with bounded backoff while the current lease is valid. If it expires, reconnect with a fresh ticket and your last processed cursor. A failed renewal never extends expiry.

Keep the existing stream open during a transient ticket-request failure. Bound retries by `expires_at`; do not retry authentication or entitlement denial as a network error. The acceptance client makes at most three transient attempts with short backoff and a five-second request deadline. This is a client recovery policy, not a longer server lease.

## Authenticate and subscribe

Send the first message within five seconds:

```json
{"type":"authenticate","ticket":"<short-lived ticket>"}
```

Wait for `authenticated`, then subscribe:

```json
{
  "type": "subscribe",
  "tours": ["atp"],
  "genders": ["men"],
  "categories": ["250", "500", "1000"],
  "include_point_events": true
}
```

Omitted filters inherit the ticket scope. An unrestricted subscription discovers the tracked set. An explicit `match_ids` list supports at most 32 IDs. Admission permits two connections per account and at most 128 gateway connections. Each connection accepts at most 120 messages and eight subscriptions per minute, including resubscription after renewal.

Without `after`, receive a replacement snapshot. A small snapshot is one `snapshot` message. Larger snapshots use `snapshot.started`, ordered `snapshot.chunk` messages and `snapshot.completed` at the same cursor. Buffer chunks and replace local state after completion. Each chunk has at most 64 matches and bounded encoded size. Bootstrap and complete-cohort heartbeat metadata support at most 3072 tracked matches; capacity failures are explicit rather than silent truncation. Validators using the earlier 1024-match schema must load the updated authoritative schema. Message fields and `schema_version: "2.0"` are unchanged. Catch-up replay may follow a snapshot.

## Join updates without matching names

| Composed `state` field | Meaning |
| --- | --- |
| `match_id` | Canonical routing key and match foreign key |
| `score_revision` | Version of score, serving side and lifecycle; not a point count |
| `match` | Fixed player order, own parent IDs, current score, status and available statistics |
| `last_point` | Confirmed point compatible with this score, or null |
| `prediction` | Current state aligned with score/input revisions; absent without prediction access |

Join by `player_ids`, `tournament_id`, `edition_id`, `draw_id` and point `id`. Names are display data. Existing ATP identities are retained, and existing imported ATP/Challenger registry entries are reused. A live entity may precede its publication in a named historical release. Source identities and payloads stay private; unmatched secondary sources are not published as duplicate matches.

`prediction.probabilities` is `[side1, side2]` in `[0,1]`, in the same order as `match.player_ids`. Multiply by 100 for percentages.

A new score is published immediately and invalidates the previous current probability. The shared worker waits up to eight seconds from database acceptance for coherent enrichment, finishing early when it arrives. At expiry, a score-only calculation may proceed with explicit missing inputs. Unknown serving side, incomplete score, missing verified prematch rules or an unavailable model can prevent calculation.

If required inputs remain unavailable after the deadline, the current prediction becomes `unavailable` with null probabilities and calculation clocks. An earlier result remains in immutable history; it is not the current forecast for the new score. Later valid input can still produce a result for that same score revision. Optional enrichment never restarts the original deadline, and a superseding score prevents publication of an obsolete calculation.

Prematch snapshots are built from the shared database independently of browser refresh. They preserve history/knowledge cutoffs and remain frozen for the match. Later coherent enrichment can create a new input revision and result for the same score, linked by `supersedes_prediction_id`. Feature counts measure finite input coverage, not accuracy. Model queue pressure does not block raw admission; storage capacity remains bounded.

## Points, lifecycle and freshness

Upsert points by `id` plus `revision`; `sequence` is a match-local ordinal. Corrections retain logical point IDs. `match.point.removed` retracts a coordinate from a corrected full prefix. A score gap never becomes an invented point. Missing server, winner, serve number or rally remains null/missing. `score.points_kind` distinguishes ordinary points, a numeric tie-break and unknown state.

Lifecycle includes scheduled, warmup, live, suspended, finished, retired, walkover, cancelled and unknown. A terminal correction is `match.status.corrected`, using the same match ID. Disappearance means missing/stale evidence, not completion. Status history retains interruptions and corrections. Public interruption reasons are `rain`, `medical`, `toilet_break`, `interruption` and `cancelled`; a pause does not imply its cause unless current source evidence supports it. Completed or negated medical/toilet text does not establish an active pause, and a current resume message clears a retained pause label. Source-specific unknown codes remain `unknown`. Only allowed interruption reasons are public; ATP commentary is private and deduplicated with snapshot links.

Unplayed score evidence can identify a scheduled match. An ambiguous source code alone cannot establish cancellation or a medical interruption; insufficient evidence remains `unknown`. Explicit cancellation and interruption messages retain their normalized meaning. Invalid ITF discovery rows are quarantined individually with bounded private diagnostics so they do not block valid matches in the same card. Accepted rows still require valid typed identity, status and score evidence; missing or incorrectly typed side order is not guessed or coerced. Quarantine counts represent unavailable observations, not complete coverage of those matches. A rejected Challenger lifecycle observation also does not block valid later matches in the same card. The rejected observation remains unavailable, and a state change or missing source clock is not guessed to bypass its guard. Authentication and transport failures remain source failures.

`heartbeat` refreshes source clocks and prediction status without recalculation. Clear cached probabilities when status is not `live`, score/input revisions disagree or expiry passes. Successful unchanged source responses may renew presence; source failures cannot. An open socket does not prove fresh source data.

## Reconnect, replay and migration

Reconnect with a fresh ticket and `subscribe.after` set to your last processed cursor. `replay.started` / `replay.completed` bracket historical delivery. Replayed probabilities are historical records. Save completed checkpoints even when filters excluded some events. Deduplicate by `event_id`, retaining entity versions by own ID plus revision.

Hot replay is bounded to 5000 events and 16 MiB. Outside that window, `resync_required` precedes a replacement snapshot. Durable outbox retention targets 48 hours. Cursor gaps are valid; use `BigInt` for decimal-string cursors/revisions. Protocol ping/pong runs every ten seconds. Slow consumers close with `1013`; invalid, expired, over-limit or out-of-scope requests close with `1008`.

Close reasons are bounded labels. `CONNECTION_LIMIT` means the account already has two authenticated connections; a disconnected transport may still occupy a slot until heartbeat cleanup or lease expiry. `TICKET_EXPIRED` requires a fresh ticket. `SUBSCRIPTION_DENIED` and `ORIGIN_DENIED` require correcting the request scope or origin. `REQUEST_IN_PROGRESS` means a new operation arrived before the preceding one completed. An abnormal `1006` close has no reliable application reason.

### Replace a disconnected lane

Opt in when authenticating:

```json
{"type":"authenticate","ticket":"<fresh short-lived ticket>","resumable":true}
```

The `authenticated` response includes a private `resume_token`. Keep the latest token in memory separately for each connection. On reconnect, obtain a fresh ticket and send:

```json
{"type":"authenticate","ticket":"<fresh short-lived ticket>","resumable":true,"resume_token":"<previous token for this lane>"}
```

The gateway replaces that lane before admitting its new session, so a half-open transport does not consume a third authenticated slot. The replaced connection closes normally with `SESSION_REPLACED`; its token is retired and the new response supplies another token. A token grants no access: the ticket must still be valid for the same account and origin, and its current restrictions apply. Classic authentication remains supported without additional response fields. Tokens expire with their server session, may disappear after a gateway restart and must stay out of URLs, logs and persistent client storage.

### Keep delivery available through two connections

For an integration that needs redundancy, open two lanes with identical ticket scope and subscriptions, including prediction access and point-event settings. Renew them at different times, for example after 30 and 45 seconds initially, then every 30 seconds within each lease. Initial staggering alone does not guarantee availability: retries can align both renewal attempts. Use one shared renewal permit, acquire it only while the peer lane is ready, and hold it through resubscription until the snapshot or replay completes. An `authenticated` response alone does not make a lane ready. Bound waiting by the current lease expiry and release a pending permit when its owning connection closes. A successful renewal requires resubscription. Both lanes consume the existing two-connection allowance and receive the same shared event stream; they do not start another collector or inference worker.

Maintain one application state and one committed checkpoint for both lanes. Apply each `event_id` once, preserve entity revisions and decimal-string cursor order, and request replay after that checkpoint on recovery. Commit a checkpoint only after applying its events or atomically installing a completed snapshot. A heartbeat's cursor can include events excluded by your filters, so retain the same scope when using that checkpoint. An older lane must not replace newer application state with an older snapshot. Outside the replay window, buffer and install a completed replacement snapshot before resuming live updates.

Two lanes can preserve delivery during a failure of one connection. They share the gateway and upstream dependencies, so a gateway, source or common network failure can affect both. Count physical socket closes, time with no ready lane and missing committed events separately. A successful replay proves recovery of retained events, not zero interruption or fresh source data.

Reconnect with capped exponential backoff and jitter, obtain a new ticket, then subscribe with the last processed cursor. Resume only after the snapshot or replay completes. Deduplicate by `event_id`. A recovered connection and uninterrupted delivery are separate measurements; replay can restore event continuity without making the socket interruption disappear.

## Lifecycle and identity corrections

Canonical identity links can be published after a match starts. Admission compares the linked identities while retaining the match's stored side IDs and order. Prediction history and point coordinates are not rewritten when an alias becomes known. Prematch selection follows identity links at its requested publication epoch, with the existing knowledge and history cutoffs.

Match projections expose nullable `winner_player_id`, `winner_basis`, `result_reason` and `status_updated_at`. A winner is one of the fixed own `player_ids`. `winner_basis` is `provider` for explicit source evidence or `completed_score` for a complete supported ordinary best-of-three/five score. A retirement or walkover never chooses the score leader. Missing winner evidence remains null; a partial later terminal snapshot does not erase an already confirmed winner. `result_reason` distinguishes `normal`, `retired`, `walkover` and `cancelled` when supported by the accepted state.

Reopening a terminal state, changing an already confirmed winner or moving from live/suspended back to preplay requires qualified correction evidence with strictly newer status and publication clocks. A scheduled/warmup observation cannot contain a played point. Correction metadata remains private; the public correction event retains the same canonical match ID.

These projection fields extend the engineering-preview contract. Retained older events may omit them; treat omitted values as unknown. Upgrade strict client validators to the current machine schema before consuming the extended projection. The wire version remains 2.0.

A generic completed status can carry less information than an explicit retirement, walkover or cancellation. A correction to the result preserves an already confirmed retirement, walkover or cancellation cause even when the source labels the updated result as generic Completed. Reopening the match or changing its terminal cause requires separate lifecycle evidence; correcting the winner cannot authorize reopening. After source restart or an unconfirmed bridge request, qualification still uses the accepted durable state and both source clocks. First winner enrichment remains an update, and a partial terminal snapshot cannot retract a confirmed provider winner. This prevents terminal oscillation while preserving correction history. Strictly older provider versions are discarded with a diagnostic counter; equal-clock enrichment can still be admitted.

## Reliability qualification

Engineering checks distinguish schema/access integrity, socket continuity, reconnect-to-replay recovery, source freshness and prediction availability. First-prediction latency is measured per match and score revision; later enrichment calculations and replay are reported separately. A live connection does not guarantee a complete feature vector or calibrated probabilities. Current probabilities remain shadow qualification, and no monthly contractual SLA is established by a short or twelve-hour test.

The former `/v1/live` WebSocket and `/v1/live/*` REST routes return **410** with `/v2/live` as the replacement. Update the ticket, URL, version checks, filters, snapshot assembly and event handlers together. Historical `/v1/*` APIs retain their contracts; retained V1 prediction reads are compatibility routes. Use V2 prediction/history routes for new live integrations.

See [live revisions and prediction history](/data-model/live-revisions-and-prediction-history) for retained reads and latency clocks. The machine WebSocket reference describes client messages, server events and composed states.

## Automatic failover reference client

The Live V2 reference client opens two identical subscriptions and merges them into one ordered callback stream. If one connection closes, its ready peer continues delivery. The closed lane reconnects with a fresh ticket and replays from the shared acknowledged cursor. The existing limit is two connections per principal: one client instance owns both slots, including across browser tabs and server processes.

The client is maintained in the public [DataForTennis/live-client repository](https://github.com/DataForTennis/live-client). Review the TypeScript source, recovery tests, pinned public contract and [GitHub Actions checks](https://github.com/DataForTennis/live-client/actions/workflows/ci.yml) before integrating it.

Use a [tagged GitHub release](https://github.com/DataForTennis/live-client/releases) for the browser bundle, ESM module for Node.js 24 or browser applications, TypeScript declarations and Node example. Releases include `SHA256SUMS`, a file manifest and third-party dependency notices. You can also clone the repository and reproduce the build with the pinned dependencies. No npm installation name or new server is required.

For Node.js, keep your API key in the server process environment. The included `node-example.mjs` obtains tickets from the existing API with an application User-Agent. Its callbacks demonstrate in-memory delivery. Replace them with your application's storage transactions before using persisted crash recovery.

For browsers, self-host the release bundle and obtain short-lived tickets from your own authenticated backend. That backend must authorize the current user and request the ticket for the browser's exact HTTPS origin. Apply your normal session and CSRF protections to that route. The private API key stays on the backend. The precompiled schema validators work with CSP without `unsafe-eval`; allow `wss://api.datafortennis.com` in your application's `connect-src`.

```js
import {ResilientLiveClient} from './dft-live-client.mjs';

const client = new ResilientLiveClient({
  endpoint: 'wss://api.datafortennis.com/v2/live',
  includePredictions: false,
  subscription: {include_point_events: true},
  ticket: async signal => {
    const response = await fetch('/your-authenticated-live-ticket-route', {
      method: 'POST', credentials: 'same-origin', signal,
      // Add your application's required CSRF header.
    });
    if (!response.ok) throw new Error('HTTP_' + response.status);
    return response.json(); // Your backend returns the short-lived ticket object.
  },
  onSnapshot: async snapshot => {
    await replaceStateAndCheckpoint(snapshot.matches, snapshot.cursor);
  },
  onEvent: async event => {
    await applyEventAndCheckpointAtomically(event, event.cursor);
  },
  onCheckpoint: async cursor => {
    await saveFilteredReplayCheckpoint(cursor);
  },
  onHeartbeat: async heartbeat => {
    await refreshFreshnessAndClearExpiredProbabilities(heartbeat);
  },
  onStatus: status => showConnectionState(status),
  onDiagnostic: diagnostic => recordTransportDiagnostic(diagnostic),
});

await client.start();
```

The application functions in this example are integration points, not functions supplied by the client. Both tickets must authorize the same filters and prediction capability. The example requests raw data; set `includePredictions: true` only when your tickets also authorize predictions.

`start()` resolves when one lane is ready; `status().readyLanes === 2` confirms that the peer is ready too. Every event callback finishes before the next begins. Its cursor advances only after the callback succeeds. Identical event copies are delivered once within the running client. Conflicting event IDs, cursors or payloads fail visibly. Persist event IDs, entity versions, state and cursor in one application transaction. Supply `initialCursor` after restart only with the corresponding restored state. The library cannot provide exactly-once external side effects across a process crash.

Tickets renew on one lane while the peer stays subscribed. Renewal remains reserved through authentication and replay. Reconnect uses exponential backoff with jitter, capped at 30 seconds plus up to 249 ms of jitter. Authentication/startup and ticket requests retain five-second deadlines. An absence of application frames for 15 seconds reconnects that lane. Physical closes and retry failures remain observable through `onDiagnostic` even when delivery continues through the peer.

If replay is no longer retained, `RESYNC_REQUIRED` reports the loss and a complete replacement snapshot follows. The client calls `onSnapshot` with `replacement: true` after validating every chunk and its count. This restores current state; it does not recover missing historical events. An application requiring a complete event log must handle that boundary explicitly.

The client enforces the published 3,072-match snapshot limit, 64-match chunks, a 16 MiB snapshot bound and 1 MiB frame bound. The pending callback queue is limited to 256 frames or 16 MiB; overflow or a callback failure stops delivery without silently dropping events or advancing the checkpoint. The duplicate conflict cache retains at most 16,384 IDs or 16 MiB. `stop()` cancels tickets, timers and both connections. An already executing callback must cooperate with its aborted signal.

Replayed probabilities are historical. Connection readiness does not prove current source freshness or valid prediction expiry. Use heartbeat freshness fields and clear expired or unavailable probabilities. Both lanes share the existing gateway and infrastructure, so this client reduces individual connection interruptions but does not provide independent host redundancy or establish a monthly SLA.