Full match detail (FREE; +market PRO, +analysis ULTRA)
Request
- Base URL: https://api.livetennisapi.com/api/public/v1
- URL: https://api.livetennisapi.com/api/public/v1/matches/{matchId}
- Auth: one of:
- HTTP bearer
- API key in header X-API-Key
Path parameters
The match id. Every match route shares ONE id space, so the same value works everywhere matchId appears.
Get it from any match list, where it is the id field of each match object: GET /matches?status=live (in progress), GET /matches?status=upcoming or GET /fixtures (scheduled), GET /matches?status=completed or GET /history/matches (finished).
Ids are stable for the life of a match, so one captured before it starts still resolves after it finishes.
Response
Match with score; market embed at PRO+, analysis embed at ULTRA
The tour, in the SAME vocabulary the tour query filter accepts — both are derived from one registry, so a match selected by ?tour=X always carries that value in tour. Null when the feed never stated a tour or the event type has no public tour name (exhibitions, team and mixed events) — never guessed. Safe to group and filter on; never parse the tournament name for this.
Stable tournament identity — one id per tournament × event type, stable across seasons. Joins GET /tournaments/{tournamentId}. Null when the tournament is not in the catalogue at all (UTR events), and on the small residue of matches discovered through a secondary source whose tournament name could not be matched to exactly one catalogue edition — never guessed. Measured over August 2026 terminal matches: ATP 1 of 531 and WTA 3 of 513 null; ITF about 2.5% after the 2026-09-04 backfill (was ~25%); UTR 100%. (Edge case — a match mislabelled as qualifying by the feed has its id re-pointed to the main-draw tournament when detected, so the id can change once, early, in that direction only.)
The level the tournament was played at IN THIS MATCH'S SEASON (added 2026-09-22) — the official category as the tour publishes it, in a closed vocabulary: grand_slam; the ATP levels (atp_finals, atp_1000, atp_500, atp_250, next_gen_finals); the WTA levels (wta_finals, wta_elite_trophy — last held 2023 — wta_1000, wta_500, wta_250, wta_125); the ATP Challenger levels (challenger_175 … challenger_50); the ITF World Tennis Tour categories (itf_m15, itf_m25, itf_w15 … itf_w100 — the 2023 women's categories itf_w25/itf_w40/itf_w60/itf_w80 are kept as printed that season, not mapped onto the 2024 ladder, because the level a match was played at is a fact about that season); the team events (united_cup, davis_cup and bjk_cup in every group and stage, laver_cup, olympics); juniors; utr; and exhibition (no ranking points — Hopman Cup class). Same vocabulary as the ?tier= filter, read from the same per-season table, so filter and field cannot disagree. Resolved by the MATCH'S season (the calendar year of scheduled_time), never copied from the tournament row: a tournament keeps one tournament_id across seasons while its level moves — Dallas was atp_250 in 2023 and 2024 and atp_500 from 2025, so a 2024 Dallas match reads atp_250 and a 2025 one atp_500 under the same id. Coverage of the 2023–2026 seasons was verified event by event against the official calendars (99.1% of tournament-seasons resolved; the rest are deliberately null — the Asian Games tennis event, which no tour lists). A new event may read null for a short while after its first match until its tier is resolved. Null when nothing we hold names the level — never guessed; null is "we do not know", never a level. category on /tournaments is unchanged and remains the coarse class (challenger, itf, atp_250 …); read this field for the level.
How tier was established (added 2026-09-22): calendar = the official per-season tour calendar named the level (ATP tour and Challenger calendars, the WTA tournament list); name = an unambiguous name rule (the ITF category is in the event's official name — M25 …, W35 … — team and UTR events are named as such, and the Slam junior draws and exhibitions are their own event type); wikipedia = the season's schedule page, used only where the official calendar could not state the level for that season (Eastbourne 2023–2025); resolver = resolved after the seed dataset, by the same rules, from the event's name, tour and dates in that season. Null exactly when tier is null.
Court surface. Null where no source states one, never inferred from a tournament's usual surface in other seasons. TEAM COMPETITIONS (Davis Cup, Billie Jean King Cup, Laver Cup) have no surface at tournament level — each tie is played on the surface its host nation chose — so the match carries its TIE's surface, taken from the ITF's published tie record (venue, indoor/outdoor, surface) and the same for every rubber of that tie; tournament.surface stays null for them. A team-event match is null only when the tie's venue is not yet published or the match cannot be matched to a single tie (competition, date window and the nations on court must agree). The other structural null is UTR events, which are not in the vendor tournament catalogue at all and so carry tournament_id: null too. For every other event a surface that IS known is published on both the match and its tournament, and the two agree.
The feed's own free-text round label, verbatim. It is the round WITHIN the draw this match belongs to, and the draw is named by is_qualifying — NOT by this string. A qualifying draw has its own semi-final and its own final, so "Buenos Aires 3 - Semi-finals" with is_qualifying: true is the semi-final of the QUALIFYING draw, not of the tournament. Read the two fields together or not at all. The label also embeds the tournament name for most feeds — parse the suffix after the last " - ", or better, use round_code.
The round in the archive's controlled vocabulary, normalized from the free-text label above (Q = qualifying round the feed does not number). It matches /history/archive/matches?round= exactly. Null when the label is unrecognised — never guessed. IT IS DRAW-RELATIVE AND CANNOT, BY ITSELF, TELL YOU WHETHER A MATCH IS QUALIFYING. A qualifying semi-final carries SF, the same as a main-draw semi-final; only Q/Q1..Q4 appear when the feed itself names the round as qualifying, which most feeds do not. To branch on main draw vs qualifying, use round_code AND is_qualifying together.
Whether this match belongs to the QUALIFYING draw, as the source states it — never GUESSED from main-draw round vocabulary or from the tournament name. (A round the feed itself spells Qualification Round 1 is the source stating it, and reads true: a false beside such a round is a payload contradicting itself, not a main-draw assertion. Semi-finals and Final are main-draw vocabulary and are never read that way.) This is the only field that separates a qualifying draw from the main draw, and it is load-bearing because the two are routinely carried under ONE tournament name with ONE round vocabulary: a tournament will publish a dozen Semi-finals and several Final that are all qualifying, a week before the main draw begins. Neither tournament, tournament_id nor round_code can tell them apart. THREE-VALUED, and null is NOT false. true/false are the source's own assertion; null means no source has ever stated it — every row written before the field existed, and rows whose only feed carries no equivalent field. Treat null as unknown and filter accordingly, exactly as with every other nullable field on this object.
The settled lifecycle. completed is asserted only for a match we observed being played or whose match-winner market settled decisively (one side paid out) — a closed market alone never finishes a match (rule stated 2026-08-23; rows mis-published before it were corrected). cancelled with event_status: null means we hold positive evidence the match was not played as scheduled (the market settled void) but no vendor word for why — outcome and the fixture reason stay null rather than guessed, and the row upgrades to a completed walkover with winner / withdrew if a Walk Over with a stated winner lands later. The flip upcoming → cancelled runs through a gate (well past the scheduled time, no play recorded), never inline on a feed flap.
The feed's own designator for the match state, passed through verbatim. Finished is by far the most common value — 144,266 of the 150,678 rows carrying one at 2026-09-18 — and until this version it was missing from this enum, so a strictly-validating generated client rejected the majority of completed matches. It is listed now, with Unresolved (every source lost the match before a result; see outcome). The rest name a match that did not run its course: retirement, cancellation, walkover, postponement, or an in-play suspension (Interrupted — rain/darkness/medical; the match is paused, not over). NULL does NOT mean "completed normally": it means the feed never stated anything for this match, which covers matches that ran their course and matches nothing was ever said about alike. event_status: Finished while status is still live is not a contradiction — one source has called the match over and we have not yet confirmed the final, so the score may stand still until we do (stale goes true, age_seconds climbs). Measured over the seven days to 2026-09-18, across 649 matches, that gap closed in 111 s at the median and 911 s at the ninetieth percentile; 85 ran past ten minutes. Two honest caveats: the value is cleared if a suspended match resumes (no record of the pause survives), and for Retired/Walk Over the withdrawing player is reported in withdrew, where derivable.
When event_status last CHANGED, UTC (ISO-8601, Z) — added 2026-08-19. The instant WE recorded the walkover / retirement / cancellation / postponement / suspension (or its clearing), not when the tournament desk or the feed did: this is the field to measure our admin-status latency with. Bumps only on a change of value (a re-read of the same status never moves it; a clear back to null does). Null while event_status has never changed since the field was introduced (2026-08-19) — never backfilled, never guessed.
Doubles match — kept for compatibility, and LOSSY. Evidence order: a doubles-team participant proves true regardless of the event type; otherwise the feed's event type decides. The loss: false also covers "unknown" — a match with no stated event type and no team participant reads false here, which is not a claim of singles. Prefer draw, whose null says so honestly.
The honest THREE-VALUED draw (added 2026-08-18) — same vocabulary as the ?draw= filter, decided by the same shared definition, so filter and field cannot disagree. Evidence order as is_doubles: a doubles-team participant proves doubles over any event type; otherwise the feed's event type decides. Null means neither says anything — the feed stated no event type, or the match is part of a team tie (Davis Cup / BJK Cup / United Cup class), where one event type covers both singles and doubles rubbers and we will not guess which this is. Null is NOT singles.
The instant our feed last reported the match in play (UTC), added 2026-09-09 — the closest thing to an actual start time; a match that starts late has this well after scheduled_time, one that starts early has it before. Null for matches that went live before 2026-09-05 (when the stamp began) or were never observed live; re-stamped if a match is demoted and promoted again.
When the published result of this match was LAST corrected (UTC), added 2026-09-22 — the instant of the newest restatement row on GET /matches/{matchId}/status-history. A correction is any change to status, event_status, winner or the final score (sets, games) after the match was first published as completed (or as a cancelled walkover with a winner). Null when the result stands as first published. Present on every match surface. Poll GET /history/matches?restated_since= to find corrections.
1 plus the number of corrections to the published result (added 2026-09-22). 1 = the result stands as first published; each restatement row on the status history adds one. Record it with the result you settle on and compare on re-read — a higher number means the result you hold was superseded, and the status history says what changed.
Completed matches only — derived from final sets. Served for the full archive age: a match older than the live-table window reads its final state from the same store the tape serves, so old completed matches carry a winner too.
The settlement outcome (added 2026-08-18), one closed vocabulary derived from status + event_status so the two can never disagree: completed = ran its full course; retired = a player retired in play; walkover = conceded before a ball was struck; default = a player was defaulted; abandoned = called off unfinished with no result awarded; unresolved (added 2026-09-10) = every source lost the match before a result and it was closed unfinished — score is the last state we observed, winner is null and no result is asserted; it flips to completed with the proven final when an authority confirms the result. NULL while the match is upcoming/live and on a cancelled fixture that never produced a result — null is not completed. Branch settlement on this field, never on event_status spellings. New values may be added within v1; handle unknowns gracefully.
Completed matches only — which player retired or conceded the walkover (1|2). Present only when event_status is Retired/Walk Over and the winner is derivable; the withdrawer is the loser by the rules of the sport. Absent means "not a withdrawal, or no evidence" — never a guess.
Whether a model thesis or profile exists for this match — on every list row and the detail, every tier (since 2026-09-02). Filter the slate on this before calling /matches/{matchId}/analysis, which answers 404 no_analysis about the same fact.
Whether a match-winner market is mapped to this match (every tier, since 2026-09-02). Same role for /markets/{matchId}/prices (404 no_market).
Example response
{
"score": {
"win_probability_meta": {
"model_version": "markov-population-2026-08-23"
}
}
}Changes
No recorded changes to this operation across all 1 revision of this API.