Training a tennis model needs three different shapes of history, and they live behind three different endpoints on three different tiers. This guide says plainly which is which, what each actually contains as of September 14, 2026, and where the leakage traps are.
Everything below uses the base URL https://api.livetennisapi.com/api/public/v1 with an X-API-Key header. A free key (no card) at livetennisapi.com/subscribe/free does not include history — it covers the current state of play — so you will need at least Basic ($9.99/mo) to follow along.
The three corpora, and what is actually in them
| Corpus | Endpoint | Tier | Size, counted 2026-09-14 |
|---|---|---|---|
| Results archive, 1968–2022 | GET /history/archive/matches |
Basic | 1,485,752 results |
| Point-by-point tape, 2023→now | GET /history/matches/{id} |
Basic | 179,578 matches of 187,832 completed (96%), 27,381,531 point-state rows |
| Shot-level charting | GET /charting/matches/{id} |
Ultra | 11,638 charted matches, 1,850,490 shot-level points, back to 1960 |
Those are counted figures, not estimates, and each carries the date it was counted. The coverage share is the one to watch: it is 96%, not 100%, and it moves as completed matches accumulate faster than tapes do. Treat the denominator as something that grows.
Which one do you actually want?
- Match-outcome models (who wins) — the results archive is the deepest thing available: 1968 onward, plus career aggregates at
GET /history/archive/careerand head-to-head atGET /h2h. Half a century of results is far more signal for an outcome model than three years of points. - In-play models (win probability as the match unfolds) — you need the tape.
GET /history/matches/{id}returns the score after every point, which is the only shape that lets you label a mid-match state. - Serve, return and rally models — only the charting corpus has shot-level data, and it is Ultra.
A common and reasonable combination is the archive for the outcome prior and the tape for the in-play update.
Pulling a training set
List completed matches, then pull each tape:
import os, requests
BASE = "https://api.livetennisapi.com/api/public/v1"
headers = {"X-API-Key": os.environ["LIVE_TENNIS_API_KEY"]}
# Completed matches, newest first — each row tells you whether a tape exists
r = requests.get(f"{BASE}/history/matches", params={"tour": "atp", "limit": 100},
headers=headers, timeout=30)
r.raise_for_status()
for m in r.json().get("matches", []):
if not m.get("has_tape"):
continue # skip rather than assume; coverage is 96%, not 100%
tape = requests.get(f"{BASE}/history/matches/{m['id']}",
headers=headers, timeout=30).json()
# tape carries the score after each point, and the model's probability where computed
Check the coverage rollup before you assume a slice is complete:
curl -s "https://api.livetennisapi.com/api/public/v1/history/coverage" \
-H "X-API-Key: $LIVE_TENNIS_API_KEY"
That endpoint returns measured completeness per tour and draw bucket, each with its own as_of. It exists so you do not have to infer completeness from the rows you happened to fetch.
Three leakage traps specific to tennis
1. The denominator moves. Coverage is 96% of completed matches and that share changes as results land. If you compute a feature like "share of this player's matches with a tape" at training time and again at inference, you are measuring two different populations. Freeze the corpus for a training run and record the date you froze it.
2. Rankings are as-of, or they are leakage. A player's ranking today is not their ranking at the time of a 2019 match. GET /rankings serves per-player as-of records on Ultra precisely so a backtest can ask what was known then. Joining current rankings onto historical matches is the single most common way a tennis model appears to work and does not.
3. Retirements and walkovers are not ordinary wins. A match that ends in retirement has a winner and a scoreline that does not describe a completed contest. Decide explicitly whether they belong in your training set; do not let the default join decide for you.
Where the archive stops and the tape starts
The results archive runs to the end of 2022. The point-by-point tape starts in January 2023. They do not overlap, and they are different shapes of data — results versus points. Matches from 2013 to 2022 additionally carry a reconstructed tape at GET /history/archive/matches/{id}/tape on Ultra, which is derived rather than recorded and should be labelled as such in any feature you build from it.
What this costs
- Basic ($9.99/mo) — the results archive and the point-by-point tape, 60 requests/minute, 1,000/day.
- Pro ($29.99/mo) — adds market prices and the rank-ordered rankings listing, 300/min, 10,000/day.
- Ultra ($99.99/mo) — adds shot-level charting, per-player as-of rankings, model win-probability fields and the reconstructed archive tape, 600/min, 500,000/day.
For a one-off training pull, the daily cap matters more than the rate limit. Pre-built monthly bulk files are available at GET /history/packages on Pro if you would rather download than iterate.
Full endpoint reference at docs.livetennisapi.com, including a point-by-point topic page and the results archive. Plans and limits are on the pricing page.