GET /history/archive/matches
Results archive (1968–2022) — deep historical results (BASIC)
Completed-match RESULTS from a licensed historical corpus — ATP and WTA main draws, qualifying/challengers and futures tiers, 1968 through 2022. Winner/loser-shaped records with final score, round, seeds, the players' ranks AT THE TIME, and per-match serve statistics where the era recorded them. Requires BASIC on the live API, or any Historical Data API plan (Starter and up).
A SEPARATE id space from /matches — archive people are identified by the corpus person id and by name, never by roster player ids — and the archive ends where our own point-by-point coverage begins (2023-01), so no match is ever served from two datasets. event_date is the TOURNAMENT START date, the only date records of this era carry.
Parameters
| Name | In | Type | Required | Notes |
|---|---|---|---|---|
tour | query | string (atp, wta) | no | The archive covers the ATP and WTA corpora only. |
name | query | string | no | Case-insensitive substring match on EITHER player's name (min 3 chars). |
from | query | string | no | Earliest tournament start date (YYYY-MM-DD). |
to | query | string | no | Latest tournament start date (YYYY-MM-DD). |
round | query | string (F, SF, QF, R16, R32, R64, R128, RR, BR, Q1, Q2, Q3, Q4, ER) | no | The archive's controlled round vocabulary. |
level | query | string | no | Source tier code: G=grand slam, M=masters, A=tour, F=finals, D=davis cup, C=challenger, O=olympics; the futures tiers carry their category codes (e.g. 15, 25) as published. |
limit | query | integer | no | Default 50. |
offset | query | integer | no | Default 0. |
Responses
| Status | Meaning |
|---|---|
200 | Archive results, newest tournament first |
400 | Bad query parameter |
401 | Missing, unknown, or disabled credentials |
403 | Your tier doesn't unlock this endpoint |
429 | Rate limit exceeded (Retry-After header present). Three body shapes, told apart by error and scope: the per-MINUTE limit (rate_limited, with upgrade_url, tier and price naming the next tier up); the per-DAY quota (rate_limited with scope: "day", limit_per_day, and resets_at — the absolute ISO instant the daily window resets); and abuse_throttled with retry_at_epoch — a 24-hour block applied to clients that keep hammering far past their cap, which a well-behaved retry loop never sees. Fix the loop rather than retrying through it. |
Response fields
| Field | Type | Description |
|---|---|---|
data | array of object | |
meta | object |
Example
curl https://api.livetennisapi.com/api/public/v1/history/archive/matches \
-H "Authorization: Bearer twjp_..."
GET /history/archive/matches/{archiveId}
One archive result, with serve statistics where recorded (BASIC)
Same entitlement as the archive listing. stats is null for the (mostly pre-1991) rows the source never recorded statistics for — never synthesised. A result played 2013–2022 may also have a reconstructed point-by-point tape at /history/archive/matches/{archiveId}/tape; the RESULT stays on BASIC either way.
Parameters
| Name | In | Type | Required | Notes |
|---|---|---|---|---|
archiveId | path | integer | yes |
Responses
| Status | Meaning |
|---|---|
200 | The archive record, stats included where the era recorded them |
401 | Missing, unknown, or disabled credentials |
403 | Your tier doesn't unlock this endpoint |
404 | No such resource, or no data yet |
429 | Rate limit exceeded (Retry-After header present). Three body shapes, told apart by error and scope: the per-MINUTE limit (rate_limited, with upgrade_url, tier and price naming the next tier up); the per-DAY quota (rate_limited with scope: "day", limit_per_day, and resets_at — the absolute ISO instant the daily window resets); and abuse_throttled with retry_at_epoch — a 24-hour block applied to clients that keep hammering far past their cap, which a well-behaved retry loop never sees. Fix the loop rather than retrying through it. |
Response fields
| Field | Type | Description |
|---|---|---|
id | integer | |
source_id | string | |
tour | string (atp, wta) | |
level | string or null | Source tier code (G/M/A/F/D/C/O, or a futures category code as published). |
tournament | string or null | |
surface | string or null | |
draw_size | integer or null | |
event_date | string or null | Tournament START date — per-match dates do not exist in this era's records, and we do not invent them. |
round | string or null | |
best_of | integer or null | |
minutes | integer or null | |
winner | object | One participant of an archive result, as the corpus records them. |
loser | object | One participant of an archive result, as the corpus records them. |
score | string or null | The final score as published, e.g. "6-4 7-6(5)", "6-3 RET", "W/O". |
outcome | string or null (completed, retired, walkover, default, abandoned, null) | Parsed from the score's own vocabulary; null when unparseable — never guessed. |
stats | object or null | Detail endpoint only. {"winner":{...}, "loser":{...}} with aces, double_faults, serve_points, first_in, first_won, second_won, serve_games, bp_saved, bp_faced where the source recorded them; null otherwise (most rows before 1991) — never synthesised. |
Example
curl https://api.livetennisapi.com/api/public/v1/history/archive/matches/{archiveId} \
-H "Authorization: Bearer twjp_..."
GET /history/archive/matches/{archiveId}/tape
Reconstructed 2013–2022 point-by-point tape for one archive result (ULTRA, or any History plan)
The score sequence behind a 2013–2022 archive result, REBUILT FROM THE PUBLIC RECORD. The result tells you what happened; this tells you how.
NOBODY WATCHED THESE MATCHES, and every field that would imply otherwise is null. timestamp is null on every row — not by convention, but because the production table has no timestamp column at all, so no repair, backfill or re-archive can put a wall clock on a 2016 row, and the promotion script refuses to run if such a column ever appears. win_probability_p1 and danger are null on every row: the model was not running in 2016 and nothing has been back-computed and served as if it had been (meta.model_rows and meta.observed_rows are both 0, always). profiles is always []. The rule we publish for the 2023+ tape — a non-null timestamp means we watched that point live — holds across both halves of the product.
YEARS: 2013–2022 ONLY. 97,901 matches and 14,340,663 rows. The results archive holds a further 977,903 matches from 1968–2012 and NONE of them has a tape — there is no public point-by-point record of those years to rebuild and we do not manufacture one. A match with no tape answers 404, which is also the answer when a tape exists and failed the proof below; the two are deliberately not distinguished.
COVERAGE OF THE ERA, thin spots beside the strong ones: 19.3% of archive matches played 2013–2022 overall, 44.9% of tour-level play. Main-draw tour buckets run 91.6–98.7% (ATP Masters 98.7%, ATP slam 98.0%, WTA Premier Mandatory 97.9%, WTA slam 97.4%, ATP 250–500 95.4%, WTA Premier 94.1%, WTA International 91.6%); ATP Challenger main draws 55.3% and Challenger qualifying 33.6%; slam QUALIFYING is thin at 16.0% (ATP) and 18.1% (WTA); ITF and futures are effectively absent — 25 of 116,575 ATP futures matches, 68 of 19,162 ITF M15, 48 of 9,380 ITF M25 — and this product does not cover them. If your work is ITF, this is the wrong dataset and we would rather you knew now. 31.0% of the corpus is qualifying-draw play, which is unusual at this scale and is the part you are least likely to hold already.
HOW A TAPE IS BOUND TO ITS MATCH. A tape attached to the wrong match is silent, permanent and poisons anything trained on it, so each one proves five things before publication: the EDITION binds by majority vote onto one archive tournament; the NAMES resolve to exactly one archive row with an unambiguous orientation; the ROUND agrees through an explicit map (this is what separates the qualifying and main-draw meetings of the same pair inside one edition); the SET SPINE derived from the rows equals the published final score exactly (a retirement matches on a one-directional prefix rule — a tape may stop short, never run past the printed score); and the vendor DATE lies inside the tournament's window. The tape is then walked step by step against 23 interior invariants — games and sets never regress or jump, a closed set holds a score a set can actually finish on, a tiebreak is flagged only where one can be played, points never run backwards inside a game, seq is contiguous. A tape that fails any clause or any invariant is REFUSED WHOLESALE rather than published against a guess, which is why this endpoint answers 404 instead of serving a probable match.
archiveId is an archive_matches.id (the id on a /history/archive/matches row), NOT a match id — the two are separate, overlapping integer spaces, so passing one to the other's routes resolves a different, real record without erroring. The response names it meta.archive_match_id for exactly that reason. Rows are WINNER-FIRST (sets[0] / games[0] / points[0] are the winner's, server: 1 means the winner was serving), unlike the p1/p2-shaped 2023+ tape — a live match has no winner while it is being played.
This operation takes NO query parameters: sequence and points are ignored rather than refused. meta.sequence is always raw — a reconstructed tape has one source and no corrections, so there is nothing for a clean collapse to remove.
Tier: core ULTRA, or any active History plan (Starter included, which opens it on a FREE core key). The archive RESULT itself (GET /history/archive/matches/{archiveId}) stays on BASIC — core BASIC and core PRO read the result and are refused the tape, and the refusal is 403 upgrade_required carrying capability: archive_tape. The per-year BULK files (?kind=archive_tape) are a separate gate: core ULTRA, or a History Pro/Business subscription, or an active one-off package window.
Parameters
| Name | In | Type | Required | Notes |
|---|---|---|---|---|
archiveId | path | integer | yes | An archive_matches.id — the id on a /history/archive/matches row, never a match id. |
Responses
| Status | Meaning |
|---|---|
200 | The reconstructed tape (match + tape + tiebreaks + an always-empty profiles + provenance meta) |
401 | Missing, unknown, or disabled credentials |
403 | Your tier doesn't unlock this endpoint |
404 | No tape for this archive result — it is 1968–2012, it is 2013–2022 and was never reconstructed, or a reconstruction exists and did not prove its binding. The three are deliberately not distinguished: "we hold something we will not stand behind" is not a state this product reports. |
429 | Rate limit exceeded (Retry-After header present). Three body shapes, told apart by error and scope: the per-MINUTE limit (rate_limited, with upgrade_url, tier and price naming the next tier up); the per-DAY quota (rate_limited with scope: "day", limit_per_day, and resets_at — the absolute ISO instant the daily window resets); and abuse_throttled with retry_at_epoch — a 24-hour block applied to clients that keep hammering far past their cap, which a well-behaved retry loop never sees. Fix the loop rather than retrying through it. |
Response fields
| Field | Type | Description |
|---|---|---|
match | object | The archive result the tape was proved to belong to. |
tape | array of object | The score sequence in seq order, WINNER-FIRST — sets[0] / games[0] / points[0] are the winner's, and server: 1 means the winner was serving. Row shape is HistoryTapeRow, with timestamp, win_probability_p1 and danger null throughout. point_winner is present on every row and is null throughout a per-GAME tape, where consecutive rows differ by a whole game and no single point is attributable. |
tiebreaks | array or null | Per-set tiebreak final scores, same rule and shape as on HistoryTape. Usually PRESENT even on a per-game tape — the 2013–2022 public record expands points inside tiebreaks even where it does not elsewhere. |
profiles | array of object | Always empty. The key is kept so the envelope matches HistoryTape; no model analysis exists, or could exist, for a 2013–2022 match. |
meta | object |
Example
curl https://api.livetennisapi.com/api/public/v1/history/archive/matches/{archiveId}/tape \
-H "Authorization: Bearer twjp_..."
GET /history/archive/players
Archive player bios — hand, DOB, country, height, career-high (BASIC)
People of the results archive (1968–2022), in their own id space — id is the corpus person id that archive match rows carry as winner.player_id / loser.player_id, scoped per tour; never a roster id. Career-high rank and the earliest week it was reached are computed offline from the corpus's own weekly ranking tables. Null fields are the era's silence, never guessed. Requires BASIC, or any Historical Data API plan.
Parameters
| Name | In | Type | Required | Notes |
|---|---|---|---|---|
name | query | string | no | Case-insensitive substring filter (min 3 chars). |
tour | query | string (atp, wta) | no | |
limit | query | integer | no | Default 50. |
offset | query | integer | no | Default 0. |
Responses
| Status | Meaning |
|---|---|
200 | Archive people, ordered by name |
400 | Bad query parameter |
401 | Missing, unknown, or disabled credentials |
403 | Your tier doesn't unlock this endpoint |
429 | Rate limit exceeded (Retry-After header present). Three body shapes, told apart by error and scope: the per-MINUTE limit (rate_limited, with upgrade_url, tier and price naming the next tier up); the per-DAY quota (rate_limited with scope: "day", limit_per_day, and resets_at — the absolute ISO instant the daily window resets); and abuse_throttled with retry_at_epoch — a 24-hour block applied to clients that keep hammering far past their cap, which a well-behaved retry loop never sees. Fix the loop rather than retrying through it. |
Response fields
| Field | Type | Description |
|---|---|---|
data | array of object | |
meta | object |
Example
curl https://api.livetennisapi.com/api/public/v1/history/archive/players \
-H "Authorization: Bearer twjp_..."
GET /history/archive/career
Career aggregates over the results archive, 1968–2022 (BASIC)
One player's whole archive career in one response: W-L record (overall, by surface, by level, by year), titles, and the summed serve-stat block with derived ratios. Everything is a sum or a ratio of sums over rows you can fetch individually from /history/archive/matches — nothing is modelled. serve.matches_with_stats states the coverage honestly: the corpus records per-match serve statistics from 1991 only, so a 1970s career has a full W-L record and an empty serve block. Ambiguous name fragments are refused with candidates (same rule as /h2h); an unknown name is a 404. Requires BASIC, or any Historical Data API plan.
Parameters
| Name | In | Type | Required | Notes |
|---|---|---|---|---|
name | query | string | yes | Player name (fragment, min 3 chars — must resolve to one person). |
Responses
| Status | Meaning |
|---|---|
200 | The career aggregate body |
400 | Bad query parameter |
401 | Missing, unknown, or disabled credentials |
403 | Your tier doesn't unlock this endpoint |
404 | No such resource, or no data yet |
429 | Rate limit exceeded (Retry-After header present). Three body shapes, told apart by error and scope: the per-MINUTE limit (rate_limited, with upgrade_url, tier and price naming the next tier up); the per-DAY quota (rate_limited with scope: "day", limit_per_day, and resets_at — the absolute ISO instant the daily window resets); and abuse_throttled with retry_at_epoch — a 24-hour block applied to clients that keep hammering far past their cap, which a well-behaved retry loop never sees. Fix the loop rather than retrying through it. |
Response fields
| Field | Type | Description |
|---|---|---|
player | object | |
span | object | |
record | object | |
by_year | array of object | |
serve | object | Summed serve statistics + derived ratios; null ratios where the denominator is zero. |
Example
curl https://api.livetennisapi.com/api/public/v1/history/archive/career \
-H "Authorization: Bearer twjp_..."
GET /h2h
Head-to-head across the results archive (1968–2022) and our own completed matches (2023→now) (BASIC)
The record between two players, assembled from BOTH halves of the product: the results archive, where the winner is a stored column, and our own completed matches, where the winner is derived from the final recorded state. Names are the keys — archive people have no roster ids. A fragment matching more than one player is refused with the candidate list (400 ambiguous_name), because two people summed into one record is a wrong answer, not a convenience. Totals count meetings with a KNOWN winner; undecided counts the rest. Walkovers and retirements are part of the record, and each meeting carries outcome so you can exclude them. Requires BASIC, or any Historical Data API plan. On ULTRA, a per-player stats block adds serve/return/break-point aggregates over the pairing: archive_serve (serve-side, from 1991) and current (2023+, adding return and break-point conversion, aces and winners), each with meetings_with_stats.
Parameters
| Name | In | Type | Required | Notes |
|---|---|---|---|---|
p1 | query | string | yes | First player name (fragment, min 3 chars). |
p2 | query | string | yes | Second player name (fragment, min 3 chars). |
Responses
| Status | Meaning |
|---|---|
200 | The head-to-head record; empty totals when no player matches the names |
400 | Bad query parameter |
401 | Missing, unknown, or disabled credentials |
403 | Your tier doesn't unlock this endpoint |
429 | Rate limit exceeded (Retry-After header present). Three body shapes, told apart by error and scope: the per-MINUTE limit (rate_limited, with upgrade_url, tier and price naming the next tier up); the per-DAY quota (rate_limited with scope: "day", limit_per_day, and resets_at — the absolute ISO instant the daily window resets); and abuse_throttled with retry_at_epoch — a 24-hour block applied to clients that keep hammering far past their cap, which a well-behaved retry loop never sees. Fix the loop rather than retrying through it. |
Response fields
| Field | Type | Description |
|---|---|---|
players | object or null | The resolved names ({"p1":{"name"}, "p2":{"name"}}); null when no player matches the fragments. |
totals | object | |
by_surface | object | Per-surface win split of the decided meetings; keys are surface names plus 'unknown'. |
meetings | array of object | Newest first, capped at 200. era says which half served the row — 'archive' rows carry archive_match_id/level/score; 'current' rows carry match_id/round_code and read their score from the match endpoints. winner is 1|2 OF THIS H2H (p1/p2 as requested), null when underivable. |
stats | object or null | ULTRA only — per-player serve/return/break-point aggregates over the pairing, keyed p1/p2. Each side carries archive_serve (serve-side figures, meetings from 1991) and current (2023+, adding return and break-point conversion, aces and winners), each with its own meetings_with_stats sample size. Absent below ULTRA. |
Example
curl https://api.livetennisapi.com/api/public/v1/h2h \
-H "Authorization: Bearer twjp_..."