Properties
| Name | Type | Description |
|---|---|---|
| id | integer | |
| tournament | string | |
| tour | stringnull | 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 no tour is stated |
| tournament_id | stringnull | Stable tournament identity — one id per tournament × event type, stable across seasons. Joins `GET /tournaments/{tournamentId}`. Null when the tournament is not in our tournament list at all (UTR even |
| tier | stringnull | 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`, ` |
| tier_source | stringnull | 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 |
| surface | stringnull | 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 tou |
| indoor | boolean | |
| format | stringnull | |
| round | stringnull | The free-text round label, verbatim, as published for the match. 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 |
| round_code | stringnull | The round in the archive's controlled vocabulary, normalized from the free-text label above (`Q` = qualifying round the label does not number). It matches `/history/archive/matches?round=` exactly. Nu |
| is_qualifying | booleannull | 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 label that itself reads `Qualification |
| status | string | 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 ma |
| event_status | stringnull | The stated designator for the match state, published 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 missi |
| event_status_updated_at | stringnull | 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 t |
| is_doubles | boolean | Doubles match — kept for compatibility, and LOSSY. Evidence order: a doubles-team participant proves true regardless of the event type; otherwise the stated event type decides. The loss: false also co |
| draw | stringnull | 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 do |
| scheduled_time | stringnull | |
| live_at | stringnull | 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 |
| result_restated_at | stringnull | 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 |
| result_version | integer | `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 res |
| players | object | |
| score | object | |
| winner | integernull | 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 |
| outcome | stringnull | 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 i |
| withdrew | integernull | 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 |
| has_analysis | boolean | 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 |
| has_market | boolean | 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`). |
JSON Schema
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "https://artifacts.apis.io/livetennisapi-com/json-schema/livetennisapi-com-match-schema.json",
"title": "Match",
"x-generated": "2026-10-28",
"x-method": "derived",
"x-generator": "derive-json-schema.py",
"x-source": "openapi/livetennisapi-com-openapi.yaml#/components/schemas/Match",
"type": "object",
"properties": {
"id": {
"type": "integer"
},
"tournament": {
"type": "string"
},
"tour": {
"type": [
"string",
"null"
],
"enum": [
"atp",
"wta",
"challenger",
"itf",
"juniors",
null
],
"description": "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 no tour is stated for the match 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."
},
"tournament_id": {
"type": [
"string",
"null"
],
"description": "Stable tournament identity — one id per tournament × event type, stable across seasons. Joins `GET /tournaments/{tournamentId}`. Null when the tournament is not in our tournament list at all (UTR events), and on the small residue of matches whose tournament name could not be matched to exactly one edition in that list — 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.)"
},
"tier": {
"type": [
"string",
"null"
],
"enum": [
"grand_slam",
"atp_finals",
"atp_1000",
"atp_500",
"atp_250",
"next_gen_finals",
"united_cup",
"davis_cup",
"olympics",
"laver_cup",
"wta_finals",
"wta_elite_trophy",
"wta_1000",
"wta_500",
"wta_250",
"wta_125",
"bjk_cup",
"challenger_175",
"challenger_125",
"challenger_100",
"challenger_75",
"challenger_50",
"itf_m15",
"itf_m25",
"itf_w15",
"itf_w25",
"itf_w35",
"itf_w40",
"itf_w50",
"itf_w60",
"itf_w75",
"itf_w80",
"itf_w100",
"juniors",
"utr",
"exhibition",
null
],
"description": "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."
},
"tier_source": {
"type": [
"string",
"null"
],
"enum": [
"calendar",
"name",
"wikipedia",
"resolver",
null
],
"description": "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` = a published season schedule, 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."
},
"surface": {
"type": [
"string",
"null"
],
"enum": [
"hard",
"clay",
"grass",
null
],
"description": "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 our tournament list 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."
},
"indoor": {
"type": "boolean"
},
"format": {
"type": [
"string",
"null"
],
"enum": [
"BO3",
"BO5",
null
]
},
"round": {
"type": [
"string",
"null"
],
"description": "The free-text round label, verbatim, as published for the match. 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 on most matches — parse the suffix after the last `\" - \"`, or better, use `round_code`."
},
"round_code": {
"type": [
"string",
"null"
],
"enum": [
"F",
"SF",
"QF",
"R16",
"R32",
"R64",
"R128",
"RR",
"BR",
"Q",
"Q1",
"Q2",
"Q3",
"Q4",
"ER",
null
],
"description": "The round in the archive's controlled vocabulary, normalized from the free-text label above (`Q` = qualifying round the label does not number). It matches `/history/archive/matches?round=` exactly. Null when the label is unrecognised — never guessed.\nIT 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 label itself names the round as qualifying, which most labels do not. To branch on main draw vs qualifying, use `round_code` AND `is_qualifying` together."
},
"is_qualifying": {
"type": [
"boolean",
"null"
],
"description": "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 label that itself reads `Qualification Round 1` states 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.\nTHREE-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."
},
"status": {
"type": "string",
"enum": [
"upcoming",
"live",
"completed",
"cancelled"
],
"description": "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 stated reason 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 momentary status flap."
},
"event_status": {
"type": [
"string",
"null"
],
"enum": [
"Finished",
"Retired",
"Cancelled",
"Walk Over",
"Postponed",
"Interrupted",
"Unresolved",
null
],
"description": "The stated designator for the match state, published 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 nothing was ever stated 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."
},
"event_status_updated_at": {
"type": [
"string",
"null"
],
"format": "date-time",
"description": "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 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."
},
"is_doubles": {
"type": "boolean",
"description": "Doubles match — kept for compatibility, and LOSSY. Evidence order: a doubles-team participant proves true regardless of the event type; otherwise the stated 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."
},
"draw": {
"type": [
"string",
"null"
],
"enum": [
"singles",
"doubles",
null
],
"description": "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 stated event type decides. Null means neither says anything — no event type is stated, 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."
},
"scheduled_time": {
"type": [
"string",
"null"
],
"format": "date-time"
},
"live_at": {
"type": [
"string",
"null"
],
"format": "date-time",
"description": "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."
},
"result_restated_at": {
"type": [
"string",
"null"
],
"format": "date-time",
"description": "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."
},
"result_version": {
"type": "integer",
"description": "`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."
},
"players": {
"type": "object",
"properties": {
"p1": {
"$ref": "#/$defs/Player"
},
"p2": {
"$ref": "#/$defs/Player"
}
}
},
"score": {
"oneOf": [
{
"$ref": "#/$defs/Score"
},
{
"type": "null"
}
]
},
"winner": {
"type": [
"integer",
"null"
],
"description": "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."
},
"outcome": {
"type": [
"string",
"null"
],
"enum": [
"completed",
"retired",
"walkover",
"default",
"abandoned",
"unresolved",
null
],
"description": "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."
},
"withdrew": {
"type": [
"integer",
"null"
],
"description": "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."
},
"has_analysis": {
"type": "boolean",
"description": "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."
},
"has_market": {
"type": "boolean",
"description": "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`)."
}
},
"$defs": {
"Player": {
"type": "object",
"properties": {
"id": {
"type": "integer"
},
"name": {
"type": "string"
},
"tour": {
"type": [
"string",
"null"
],
"enum": [
"atp",
"wta",
null
],
"description": "The circuit the player is filed under: atp | wta | null (normalised 2026-09-24 — the capitalised and level values no longer appear). Not the competition level (use the match's tier) and not the draw (use the match's draw). A doubles team filed under a circuit of its own carries that circuit; one filed under a level word or nothing falls back to the circuit both its members are filed under, and is null when they disagree or either is unknown. null means not filed, never a guess. Distinct from the match-level tour, whose vocabulary is the tour-group filter's (atp | wta | challenger | itf | juniors)."
},
"country": {
"type": [
"string",
"null"
]
},
"ranking": {
"type": [
"integer",
"null"
],
"description": "Official singles ranking POSITION (the ordinal: 1, 25, 100) — never points, seed, race, UTR, Elo or a doubles rank. Men from the official ATP table, women from the WTA table, chosen by the player (a Challenger or ITF man who holds an ATP ranking carries it here). Refreshed from the standings ahead of each match the player has with us; a player who has not played recently keeps their last refreshed position. `null` when no ranking is held (doubles teams, most ITF/junior-only players). Always the CURRENT record, even on historical matches — use `/rankings?as_of=` for the rank in force on a date."
},
"ranking_points": {
"type": [
"integer",
"null"
],
"description": "Points on the same official table, refreshed with `ranking`."
},
"ranking_status": {
"type": [
"string",
"null"
],
"enum": [
"ranked",
"unranked",
"unverified",
null
],
"description": "What the last reconcile against the published ATP/WTA singles tables found (added 2026-09-23). `ranked` = on the table (`ranking` is that row). `unranked` = the full table was fetched and the player was not on it (a `ranking` here is the last value held, not current). `unverified` = could not be checked (no id, a name mismatch, or never checked). Null on doubles teams."
},
"ranking_checked_at": {
"type": [
"string",
"null"
],
"format": "date-time",
"description": "When that reconcile ran, UTC (added 2026-09-23)."
},
"ranking_movement": {
"type": [
"string",
"null"
],
"enum": [
"up",
"down",
"same",
null
]
},
"itf_ranking": {
"type": [
"object",
"null"
],
"description": "Added 2026-10-12, paid tiers (BASIC, PRO, ULTRA), null on a FREE key. The player's CURRENT rank on the ITF's own published World Tennis Tour ranking — the rank most ITF and junior players hold while they have no ATP/WTA one. `system` names the circuit (`itf_mt` men's pro, `itf_wt` women's pro, `itf_jt` juniors); a player ranked on a pro circuit and the junior one carries the pro rank. `rank_date` is the ITF's publication date of that row (weekly), so a stale rank is visible as such. NOT comparable with `ranking` (a circuit rank 1 is tens of points) and never written into it. Joined by the same exact ITF id `itf_id` exposes at ULTRA, never by name. Null when no ITF link is held, when that id is held by more than one roster row, when no published row exists, and on doubles teams. The weekly history of these ranks stays at `/rankings?system=itf_*` (ULTRA).",
"properties": {
"system": {
"type": "string",
"enum": [
"itf_mt",
"itf_wt",
"itf_jt"
]
},
"rank": {
"type": "integer"
},
"points": {
"type": [
"number",
"null"
]
},
"rank_date": {
"type": "string",
"format": "date"
}
},
"required": [
"system",
"rank",
"points",
"rank_date"
]
},
"hand": {
"type": [
"string",
"null"
],
"enum": [
"R",
"L",
null
]
},
"backhand": {
"type": [
"integer",
"null"
],
"enum": [
1,
2,
null
]
},
"birthday": {
"type": [
"string",
"null"
],
"format": "date"
},
"height_cm": {
"type": [
"integer",
"null"
],
"description": "Added 2026-10-22. Single-player read (`/players/{playerId}`) only; paid tiers (BASIC, PRO, ULTRA), null on a FREE key. Height in centimetres from the results archive's record of the same person. Null when the player has no archive record or the archive holds no height. Never inferred."
},
"doubles_ranking": {
"type": [
"object",
"null"
],
"description": "Added 2026-10-22. Single-player read (`/players/{playerId}`) only; paid tiers (BASIC, PRO, ULTRA), null on a FREE key. The current position on the official ATP/WTA doubles table, from our weekly refresh of the published tables. `observed_on` is the UTC date that refresh last found the player on the table. Null when the player is not on the table or the last sighting is more than 21 days old. Never copied into `ranking`, which stays the singles position.",
"properties": {
"rank": {
"type": "integer"
},
"points": {
"type": [
"integer",
"null"
]
},
"observed_on": {
"type": "string",
"format": "date"
}
}
},
"itf_id": {
"type": [
"integer",
"null"
],
"description": "ULTRA only (added 2026-09-24). The player's identifier in the ITF ranking tables we publish under `system=itf_*` — the `itf_jt` / `itf_mt` / `itf_wt` rows of `/rankings` are linked to this player by it (an id join, never a name match). One human's id across singles and doubles rows, so a join key, not a per-row primary key. Null when no ITF link is held and on doubles teams. Absent, not null, below ULTRA."
},
"is_doubles_team": {
"type": "boolean"
},
"profile_restated_at": {
"type": [
"string",
"null"
],
"format": "date-time",
"description": "When the published PROFILE of this player was LAST corrected (UTC), added 2026-10-05 — the player twin of `result_restated_at`. A correction is a change to a published profile field after it was first published; today that is the name (where the held name was another person's, from 2026-10-06) or the birth date, corrected to the results archive's record of the same person where the two disagreed. Null while the profile stands as first published. Poll `GET /players?restated_since=` to find corrections."
},
"profile_version": {
"type": "integer",
"description": "1 plus the number of corrections to the published profile (added 2026-10-05), the player twin of `result_version`. 1 = the profile stands as first published. Record it with the profile you hold and compare on re-read — a higher number means a field you hold was superseded."
},
"archive_player_id": {
"type": [
"integer",
"null"
],
"description": "The player's person id in the results archive (added 2026-09-23) — the id `/history/archive/players` publishes as `id` and archive match rows carry as `winner.player_id` / `loser.player_id`. Scoped by `tour` (men and women are numbered independently). Set by a tour-scoped link built offline from names and birth dates; null where none exists. Measured 2026-09-23: 35 of 5,914 links (0.6%) pointed at a differently named person; all 35 repaired on 2026-09-24. Confirm with name and birthday where identity matters."
},
"data_completeness": {
"type": "object",
"description": "How much biographical detail is known for this player, so a consumer can distinguish \"never published\" from \"not yet filled in\" without probing. Present on every player in a match payload. Lower tours carry far less of it than main tour.",
"properties": {
"known": {
"type": [
"integer",
"null"
],
"description": "Fields populated, of `of`. NULL on a doubles team, where per-player biography does not apply — distinct from `0`, which means the fields apply and none are known. Do not decode this into a non-nullable integer."
},
"of": {
"type": [
"integer",
"null"
],
"description": "Fields considered. NULL on a doubles team, as for `known`."
},
"missing": {
"type": "array",
"description": "Names of the unpopulated fields, e.g. [\"backhand\", \"hand\"].",
"items": {
"type": "string"
}
},
"note": {
"type": "string",
"description": "Present only when the object is not applicable, explaining why (e.g. a doubles team)."
}
}
},
"stats": {
"type": "object",
"description": "Single-player endpoint only",
"properties": {
"ratings": {
"type": [
"object",
"null"
]
},
"ratings_as_of": {
"type": [
"string",
"null"
],
"format": "date",
"description": "The date (UTC) the `ratings` block was last refreshed (added 2026-09-10). The current Elo is refreshed every week from the published rating tables; read this before treating a rating as this week's. Null when there is no rating."
},
"season": {
"type": [
"array",
"null"
]
}
}
}
}
},
"Score": {
"type": "object",
"description": "One state at one instant. ULTRA adds win_probability_p1 + danger. Order and de-duplicate on `sequence`, never on `timestamp`.",
"properties": {
"sets": {
"type": "array",
"items": {
"type": "integer"
}
},
"games": {
"type": [
"array",
"null"
],
"description": "[games_p1, games_p2]; each a per-set list. NULL means WITHHELD — we hold no state for this match we can stand behind (see `detail`); it never means 0-0, and an empty array is never used to mean \"unknown\". A deciding set played as a 10-point MATCH TIEBREAK (super-tiebreak) carries the BREAKER's score in its slot WHILE IT IS BEING PLAYED — `[[6,4,10],[4,6,5]]` is `6-4 3-6 [10-5]`, not a 10-game set — and `is_tiebreak` is true then. Once the breaker is over that set is often published as a one-game `1-0`: take the match winner from `sets`, never from the deciding set's games.",
"items": {
"type": "array",
"items": {
"type": "integer"
}
}
},
"points": {
"type": "array",
"items": {
"type": "string"
}
},
"server": {
"type": [
"integer",
"null"
],
"enum": [
1,
2,
null
]
},
"is_tiebreak": {
"type": "boolean",
"description": "The current game is a tiebreak. On a DECIDING set played as a 10-point match tiebreak this is true for the whole set, whose `games` slot holds the breaker."
},
"win_probability_p1": {
"type": [
"number",
"null"
]
},
"danger": {
"type": [
"number",
"null"
]
},
"win_probability_p1_model": {
"type": [
"number",
"null"
],
"description": "ULTRA, since 2026-09-12. The same model read computed WITHOUT the market-prior anchor — a probability no market price touched. Equals win_probability_p1 on a match the anchor does not apply to, and also on some rows of an anchored match (the anchor shifts the pre-match hold rates, which enter the engine snapped to a 0.01 grid, so a shift smaller than a grid step often solves to an identical value); the per-row test is win_probability_p1 != win_probability_p1_model. Null on rows stamped before it existed (never back-filled)."
},
"win_probability_meta": {
"type": [
"object",
"null"
# --- truncated at 32 KB (45 KB total) ---
# Full source: https://artifacts.apis.io/livetennisapi-com/json-schema/livetennisapi-com-match-schema.json
Work with this as data
Every JSON Schema here is available over the APIs.io API and to AI agents over MCP.
MCP server
One button, every client — Claude, Cursor, VS Code and the rest.
https://apis.io/mcp
Tools for schemas
4 MCP tools reach this
find_json_schemasBrowse and filter every JSON Schema in the catalog.apis_io_searchSTART HERE — APIs, providers and tags for one query, each with its total.resolveTurn a domain, URL or GitHub org into the provider it belongs to.find_cohortsEvery scored population of providers in the catalog.
Call it yourself
curl for this page
This JSON Schema
curl "https://apis.io/api/v1/json-schemas/livetennisapi-com-match"
All schemas
curl "https://apis.io/api/v1/json-schemas?limit=25"
Discovery needs no key. Ratings and market analysis are Pro.
Get an API key
Free tier, no form to fill in. Signing in shares your email address with us — we store it to create your key and to recognise you if you sign in with another provider. See our Privacy Policy and Terms.
A second provider on the same verified email joins the account you already have.