Live Tennis API · docs · version 1.13.4

Historical tennis results API — 1968 onward

How far back does the historical tennis data go, and what is in it? The results archive covers 1968 to 2022: GET /history/archive/matches for results, /archive/players for bios, /archive/career for career aggregates and GET /h2h for head-to-head across both the archive and our own completed matches. All BASIC. Matches from 2013 onward additionally carry a reconstructed point-by-point tape at /archive/matches/{archiveId}/tape (ULTRA).

6 endpoints on this page. Base URL https://api.livetennisapi.com/api/public/v1; authenticate with the X-API-Key header. Plans involved: BASIC, ULTRA. A free key needs no card.

GET /history/archive/matches

Results archive (1968–2022) — deep historical results (BASIC)

Plan required: BASIC · operationId: listArchiveMatches

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

GET /history/archive/matches — parameters
NameInTypeRequiredNotes
tourquerystring (atp, wta)noThe archive covers the ATP and WTA corpora only.
namequerystringnoCase-insensitive substring match on EITHER player's name (min 3 chars).
fromquerystringnoEarliest tournament start date (YYYY-MM-DD).
toquerystringnoLatest tournament start date (YYYY-MM-DD).
roundquerystring (F, SF, QF, R16, R32, R64, R128, RR, BR, Q1, Q2, Q3, Q4, ER)noThe archive's controlled round vocabulary.
levelquerystringnoSource 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.
limitqueryintegerno Default 50.
offsetqueryintegerno Default 0.

Responses

GET /history/archive/matches — responses
StatusMeaning
200Archive results, newest tournament first
400Bad query parameter
401Missing, unknown, or disabled credentials
403Your tier doesn't unlock this endpoint
429Rate 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

GET /history/archive/matches — response fields
FieldTypeDescription
dataarray of object
metaobject

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)

Plan required: BASIC · operationId: getArchiveMatch

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

GET /history/archive/matches/{archiveId} — parameters
NameInTypeRequiredNotes
archiveIdpathintegeryes

Responses

GET /history/archive/matches/{archiveId} — responses
StatusMeaning
200The archive record, stats included where the era recorded them
401Missing, unknown, or disabled credentials
403Your tier doesn't unlock this endpoint
404No such resource, or no data yet
429Rate 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

GET /history/archive/matches/{archiveId} — response fields
FieldTypeDescription
idinteger
source_idstring
tourstring (atp, wta)
levelstring or nullSource tier code (G/M/A/F/D/C/O, or a futures category code as published).
tournamentstring or null
surfacestring or null
draw_sizeinteger or null
event_datestring or nullTournament START date — per-match dates do not exist in this era's records, and we do not invent them.
roundstring or null
best_ofinteger or null
minutesinteger or null
winnerobjectOne participant of an archive result, as the corpus records them.
loserobjectOne participant of an archive result, as the corpus records them.
scorestring or nullThe final score as published, e.g. "6-4 7-6(5)", "6-3 RET", "W/O".
outcomestring or null (completed, retired, walkover, default, abandoned, null)Parsed from the score's own vocabulary; null when unparseable — never guessed.
statsobject or nullDetail 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)

Plan required: ULTRA · operationId: getArchiveTape

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

GET /history/archive/matches/{archiveId}/tape — parameters
NameInTypeRequiredNotes
archiveIdpathintegeryesAn archive_matches.id — the id on a /history/archive/matches row, never a match id.

Responses

GET /history/archive/matches/{archiveId}/tape — responses
StatusMeaning
200The reconstructed tape (match + tape + tiebreaks + an always-empty profiles + provenance meta)
401Missing, unknown, or disabled credentials
403Your tier doesn't unlock this endpoint
404No 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.
429Rate 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

GET /history/archive/matches/{archiveId}/tape — response fields
FieldTypeDescription
matchobjectThe archive result the tape was proved to belong to.
tapearray of objectThe 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.
tiebreaksarray or nullPer-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.
profilesarray of objectAlways empty. The key is kept so the envelope matches HistoryTape; no model analysis exists, or could exist, for a 2013–2022 match.
metaobject

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)

Plan required: BASIC · operationId: listArchivePlayers

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

GET /history/archive/players — parameters
NameInTypeRequiredNotes
namequerystringnoCase-insensitive substring filter (min 3 chars).
tourquerystring (atp, wta)no
limitqueryintegerno Default 50.
offsetqueryintegerno Default 0.

Responses

GET /history/archive/players — responses
StatusMeaning
200Archive people, ordered by name
400Bad query parameter
401Missing, unknown, or disabled credentials
403Your tier doesn't unlock this endpoint
429Rate 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

GET /history/archive/players — response fields
FieldTypeDescription
dataarray of object
metaobject

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)

Plan required: BASIC · operationId: getArchiveCareer

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

GET /history/archive/career — parameters
NameInTypeRequiredNotes
namequerystringyesPlayer name (fragment, min 3 chars — must resolve to one person).

Responses

GET /history/archive/career — responses
StatusMeaning
200The career aggregate body
400Bad query parameter
401Missing, unknown, or disabled credentials
403Your tier doesn't unlock this endpoint
404No such resource, or no data yet
429Rate 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

GET /history/archive/career — response fields
FieldTypeDescription
playerobject
spanobject
recordobject
by_yeararray of object
serveobjectSummed 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)

Plan required: BASIC · operationId: getHeadToHead

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

GET /h2h — parameters
NameInTypeRequiredNotes
p1querystringyesFirst player name (fragment, min 3 chars).
p2querystringyesSecond player name (fragment, min 3 chars).

Responses

GET /h2h — responses
StatusMeaning
200The head-to-head record; empty totals when no player matches the names
400Bad query parameter
401Missing, unknown, or disabled credentials
403Your tier doesn't unlock this endpoint
429Rate 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

GET /h2h — response fields
FieldTypeDescription
playersobject or nullThe resolved names ({"p1":{"name"}, "p2":{"name"}}); null when no player matches the fragments.
totalsobject
by_surfaceobjectPer-surface win split of the decided meetings; keys are surface names plus 'unknown'.
meetingsarray of objectNewest 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.
statsobject or nullULTRA 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_..."