Skip to navigation

Live WebSocket quickstart

Current measured availability and source checks: System status. Read the monitoring definitions 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 / categoryRaw singles statesModelEnrichment
ATP250 / ATP500 / ATP1000, menSupported adapterAdmitted ordinary best-of-three matches, including qualifyingCoherent observed point prefixes and derived statistics when available
ATP Challenger, menSupported adapter; category ChallengerUnsupportedScore and lifecycle; point/statistics coverage is not promised
WTA main tour / WTA125, womenSupported adapterUnsupportedScore and lifecycle; point history is not promised
ITF men / womenSupported adapter; categories such as M25 / W35UnsupportedObserved 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.

{
"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:

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

Wait for authenticated, then subscribe:

{
"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 fieldMeaning
match_idCanonical routing key and match foreign key
score_revisionVersion of score, serving side and lifecycle; not a point count
matchFixed player order, own parent IDs, current score, status and available statistics
last_pointConfirmed point compatible with this score, or null
predictionCurrent 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:

{"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:

{"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 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. Review the TypeScript source, recovery tests, pinned public contract and GitHub Actions checks before integrating it.

Use a tagged GitHub release 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.

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.