Historical Data API

Point-by-point tennis history, 2013 onward

A clean, point-by-point event tape of completed matches — the ordered score states of each match. Two records, one row shape. From January 2023 it is our own collection across ATP, WTA, Challenger, ITF and doubles: every match declares its own coverage, so you always know whether you are holding a tape we watched live from 0-0 — timestamps and model win-probability included — or one reconstructed from a point-by-point feed. Backtest, train, and study how matches actually swing. Before that, a separate 2013-2022 reconstruction: 97,901 more ATP, WTA and Challenger matches, rebuilt from the public record and carrying the score sequence alone.

See plans — from $29/mo Create an account

Same shape, different provenance

One parser reads both. What separates them is not the format — it is how the rows came to exist, and that governs what you may ask of them.

The two halves of the Historical Data product. Archive figures measured September 1, 2026; 2023+ figures as of the dates named in each cell.
  2013-2022 archive January 2023 → now
How the rows were obtained Reconstructed from the public record Watched live by our own collection, or reconstructed from a point-by-point feed
Did anyone watch it? No — not one row Yes, on the observed rows
Per-point timestamp null on every row — the table has no timestamp column Real UTC timestamp on every observed row; observed rows begin March 2026 — capture itself started 12 October 2025 at low volume (25,747 matches carry at least one as of August 27, 2026)
Model win-probability / danger null on every row — these states were never scored On most observed rows; null where the best-effort stamp did not run
Size 97,901 matches · 14,340,663 point rows More than 171,000 matches · over 26 million point states (counted August 19, 2026)
Span 2013–2022, and nothing earlier January 2023 → August 2026, growing as matches complete
Tours ATP, WTA and Challenger; ITF not covered ATP, WTA, Challenger and ITF, singles and doubles
meta.point_source always reconstructed observed, mixed or reconstructed
What “reconstructed” means here Rebuilt from the public record years later, and proved back onto the archive match Rebuilt from a point-by-point feed we did not watch from 0-0 — same word, a different source
Read it at GET /history/archive/matches/{id}/tape GET /history/matches/{id}
Included with core ULTRA, or any Historical Data plan Core BASIC and above, or any Historical Data plan

The two records live in separate id spaces and are never served from one endpoint. An archive id is an /history/archive/matches row id, not a match id; the response names it meta.archive_match_id so the two can never be silently swapped.

The 2023+ tape: what we collect ourselves

Before 2023: the 2013-2022 reconstruction

97,901 ATP, WTA and Challenger matches played between 2013 and 2022, 14,340,663 point rows — the score sequence behind a result that was never streamed. It is rebuilt from the public record, not a recording, and everything below follows from that. There is nothing before 2013: our results archive reaches back to 1968, but no point-by-point record of those years exists to rebuild from, and we do not manufacture one.

Coverage by tier and draw

Share of archive matches actually played in 2013-2022 that carry a tape, as of September 1, 2026: 19.3% across all play, and 44.9% of tour-level play. Where that difference lands is what the table is for — main draws run 91.6% to 98.7%, Challenger main draw 55.3%, slam qualifying 16.0% to 18.1%:

Tape coverage by tier and draw, measured September 1, 2026. Selected tiers, ordered strongest to thinnest — not a partition of the corpus, so the covered column does not sum to 97,901.
Tier and drawWith a tape PlayedShare
ATP Masters, main draw4,7754,83798.7%
ATP Grand Slam, main draw4,7024,79798.0%
WTA Premier Mandatory, main draw1,8411,88097.9%
WTA Grand Slam, main draw4,6744,79797.4%
ATP 250/500, main draw12,21912,80295.4%
WTA Premier, main draw5,7826,14294.1%
WTA International, main draw7,2967,96591.6%
ATP Challenger, main draw23,80243,03855.3%
ATP Challenger, qualifying7,21221,45533.6%
WTA Grand Slam, qualifying6753,73418.1%
ATP Grand Slam, qualifying7064,41216.0%
ITF and futures, all draws141145,117under 0.1%

ITF and futures are not covered: 25 of 116,575 ATP futures, 68 of 19,162 ITF M15 and 48 of 9,380 ITF M25 matches carry a tape — if your work is ITF, this corpus is not for you and we would rather you knew that here than after buying. Coverage also varies by year (Coverage roughly doubles from 2017 on and holds there; 2020 is the short covid season): 2013 14.0%, 2014 13.2%, 2015 13.6%, 2016 12.5%, 2017 21.3%, 2018 22.9%, 2019 22.7%, 2020 25.9%, 2021 25.5%, 2022 24.2%.

Coverage labels on the tape itself: reconstructed on 94,307 matches (96.33%) means it reproduces the published final score exactly, and that score is a match that finished. reconstructed_partial on 3,594 (3.67%) has two causes and the label does not say which: 3,038 of them are matches that stopped early — a retirement or a default, with the tape running exactly as far as play did — and 556 are per-game tapes, which are labelled this way whatever their completeness (542 of those are complete matches). To tell them apart, read granularity and the archive row's own score and outcome, not the label.

How to reach it. One match: GET /history/archive/matches/{id}/tape — core ULTRA, or any Historical Data plan. A whole year in bulk: GET /history/packages/{YYYY}?kind=archive_tape — core ULTRA, or Historical Data Pro, Business or an active one-off package; all ten years 2013-2022 are published as of September 1, 2026. A key without the entitlement gets 403 upgrade_required carrying capability: archive_tape, which is the string to branch on. The archive result (GET /history/archive/matches/{id}) stays where it has always been — core Basic and above, or any History plan; the tape is the half this adds. Ids here are archive_matches.id, a different space from live match ids. Full field-by-field reference: the 2013-2022 tape in the API docs.

Backtesting against ratings too? The core API's ULTRA plan serves an as-of Elo tape on GET /rankings?system=elo — what a player was rated before a match, ATP from 1877-07-23 and WTA from 1968-01-08, main tour plus challengers plus the futures tier, with no look-ahead by construction (65,622 rated players and 3,417,835 rating snapshots as of the 2026-08-12 build). It is a historical tape, not a live leaderboard: the corpus behind it is fixed, ending 2026-05-25. The plan also serves per-player as-of UTR ratings on system=utr, with history since 2026-07-29.

What a response actually looks like

Three real matches, one from each half of the product. The first we watched live from 0-0; the second is a 2023+ match reconstructed from a point-by-point feed; the third is a 2013-2022 archive tape. The differences are visible in the data, not just in the docs. Field-by-field reference: the 2013-2022 tape in the API docs.

2023+ tape, watched live — coverage: from_start

GET /api/public/v1/history/matches/22896?sequence=clean
Kei Nishikori vs Juncheng Shang, ATP Washington, 27 Jul 2026.

{
  "meta": {
    "coverage":      "from_start",
    "point_source":  "observed",
    "raw_rows":      212,
    "rows":          199,
    "unique_states": 199,
    "sequence":      "clean"
  },
  "tape": [
    { "sets": [0,0], "games": [[0],[0]], "points": ["0","0"],  "server": null,
      "is_tiebreak": false,
      "timestamp": "2026-07-27T21:08:22.773825Z",
      "win_probability_p1": 0.39962309494767223, "danger": 0.05732571932609154 },
    { "sets": [0,0], "games": [[0],[0]], "points": ["0","15"], "server": 2,
      "is_tiebreak": false,
      "timestamp": "2026-07-27T21:08:48.219296Z",
      "win_probability_p1": 0.3970008761094225,  "danger": 0.10835421670547885 }
  ]
}

Every row carries a real timestamp, because we were watching when it happened. The model's win-probability is stamped where it was computed — that step is best-effort in the live path, so on some observed rows the field is null rather than filled in after the fact. Both rows above happen to carry it.

2023+ tape, reconstructed from a point-by-point feed — coverage: reconstructed

GET /api/public/v1/history/matches/167732?sequence=clean
Marco Trungelliti vs Maximus Jones, Nonthaburi, 31 Dec 2024.

{
  "meta": {
    "coverage":      "reconstructed",
    "point_source":  "reconstructed",
    "raw_rows":      166,
    "rows":          149,
    "unique_states": 149,
    "sequence":      "clean"
  },
  "tape": [
    { "sets": [0,0], "games": [[0],[0]], "points": ["0","0"],  "server": 1,
      "is_tiebreak": false,
      "timestamp": null, "win_probability_p1": null, "danger": null },
    { "sets": [0,0], "games": [[0],[0]], "points": ["15","0"], "server": 1,
      "is_tiebreak": false,
      "timestamp": null, "win_probability_p1": null, "danger": null }
  ]
}

The score sequence is real — it came from a point-by-point feed. What is absent is absent on purpose: timestamp and the model fields are null, because no model ever saw that state. Nothing is synthesised from a final scoreline, and you can exclude these entirely with ?coverage=from_start if your backtest needs observed data only.

point_source can also read mixed, where we joined a match part-way and the opening was filled in afterwards. And note raw_rows vs rows above: ?sequence=clean collapses repeated commits of the same score state — on a busy match that is 5,905 rows down to 89.

Reconstructed archive (2013-2022) — granularity: point

GET /api/public/v1/history/archive/matches/989456/tape
Angelique Kerber def. Serena Williams 6-4 3-6 6-4, Australian Open final, 18 Jan 2016. 177 rows. Note the id: this is an archive_matches.id, not a match id.

{
  "meta": {
    "archive_match_id":    989456,
    "rows":                177,
    "coverage":            "reconstructed",
    "granularity":         "point",
    "point_source":        "reconstructed",
    "model_rows":          0,
    "observed_rows":       0,
    "point_source_detail": "reconstruction from public record",
    "sequence":            "raw",
    "generated_at":        "2026-09-01T07:42:54.016856Z"
  },
  "match": {
    "id": 989456, "tour": "wta", "level": "G", "tournament": "Australian Open",
    "surface": "hard", "draw_size": 128, "event_date": "2016-01-18", "round": "F",
    "winner": { "name": "Angelique Kerber", "rank": 6, "seed": 7, "player_id": 201493 },
    "loser":  { "name": "Serena Williams",  "rank": 1, "seed": 1, "player_id": 200033 },
    "score": "6-4 3-6 6-4", "outcome": "completed"
  },
  "profiles":  [],
  "tiebreaks": null,
  "tape": [
    { "sets": [0,0], "games": [[0],[0]], "points": ["0","0"],  "server": 2,
      "is_tiebreak": false, "point_winner": null,
      "timestamp": null, "age_seconds": null, "stale": false,
      "win_probability_p1": null, "danger": null },
    { "sets": [0,0], "games": [[0],[0]], "points": ["0","15"], "server": 2,
      "is_tiebreak": false, "point_winner": 2,
      "timestamp": null, "age_seconds": null, "stale": false,
      "win_probability_p1": null, "danger": null },
    … 174 rows …
    { "sets": [2,1], "games": [[6,3,6],[4,6,4]], "points": ["0","0"], "server": 2,
      "is_tiebreak": false, "point_winner": 1,
      "timestamp": null, "age_seconds": null, "stale": false,
      "win_probability_p1": null, "danger": null }
  ]
}

Three fields are null on every row this endpoint can ever serve — timestamp, win_probability_p1 and danger — and meta.model_rows and meta.observed_rows are always 0. That is not a property of this match; the table has no such columns. meta.granularity says point or game, and meta.coverage is one of the two words above. Rows are winner-firstsets[0] is the winner's and server: 1 means the winner was serving — because an archive result is recorded winner/loser-shaped and the match is over; the 2023+ tape is p1/p2-shaped instead. A match with no tape answers 404 not_found, which is also the answer when a tape exists and failed the identity proof: we do not distinguish them.

In bulk: GET /history/packages/{YYYY}?kind=archive_tape — one JSONL and one CSV per year. The JSONL record is what the per-match endpoint returns, so a parser written against one match runs over a whole year unchanged. The CSV deliberately carries no timestamp, win_probability_p1 or danger column at all: a column empty in every row of a file invites the belief that it is sometimes populated.

Pricing

Three monthly plans, one ladder: Starter reads the tape per match — both halves, including the 2013-2022 reconstruction — Pro adds the bulk downloads, Business adds year-scale package exports. Every plan includes API access to the history endpoints on your linked key — a free key is enough. Prefer no subscription? Buy a one-off package instead.

The other way in is the core API: the archive is on core ULTRA, $99.99/mo, along with the per-point live feed, WebSocket and webhook push, and the as-of Elo tape. For the 2013-2022 archive on its own, History Starter is the cheaper of the two at $29/mo.

Starter $29/mo

  • Best for: A single backtest or study
  • Point-by-point tape from January 2023 + model win-probability where we scored it, per match
  • Per-match reads of the 2013-2022 reconstructed archive
  • Completed-match listings — ATP · WTA · Challenger · ITF
  • Works on a free API key — no core subscription needed
Get Starter

Pro $99/mo

  • Best for: Ongoing modelling across tours
  • Everything in Starter, plus:
  • Bulk monthly package downloads (JSONL + CSV)
  • Per-year 2013-2022 archive files — one JSONL + CSV for each of the ten years, all published
  • 300 requests/min on the history endpoints
Get Pro

Business $299/mo

  • Best for: Whole-corpus access for a team or product
  • Everything in Pro, plus:
  • Year-scale package exports — a full year of published months in one call
  • Top history rate limit — 600 requests/min
  • Priority support
Get Business
Monthly subscription tiers
 StarterProBusiness
Price$29/mo$99/mo$299/mo
Best forA single backtest or studyOngoing modelling across toursWhole-corpus access for a team or product
Point-by-point tape (per match)
2013-2022 archive — per match
Bulk monthly downloads
2013-2022 archive — per-year files
Year-scale package exports
History rate limitYour key's plan limit (a free key: 30 req/min)300 req/min (or your key's higher limit)600 req/min
SupportStandardStandardPriority
Get started Get Starter Get Pro Get Business

No subscription needed? Grab a one-off package: $49 for a full month of history, or $399 for a full year. While it's active, a package works as Pro-level access for its window — tape reads (both halves, including the 2013-2022 archive) and its per-year archive files, plus the bulk monthly downloads; the 1-year package also includes the year-scale package listing.

Buy 1 month — $49 Buy 1 year — $399

Need a custom volume, date range or tour mix? Email us and we'll size a plan.

Plans, coverage and the tape — FAQ

What's the difference between the plans?

Starter is per-match reads: the point-by-point tape and completed-match listings across ATP, WTA, Challenger and ITF from January 2023, plus per-match reads of the 2013-2022 reconstructed archive. Pro adds the bulk monthly package downloads (JSONL and CSV), the per-year 2013-2022 archive files, and a higher rate limit on the history endpoints (300 requests/min). Business adds year-scale package exports (every published month of a year in one call), the top history rate limit (600 requests/min), and priority support.

How far back does the data go?

The live point-by-point tape runs from January 2023 through August 2026 — more than 171,000 completed matches and over 26 million archived point states (counted August 19, 2026), across ATP, WTA, Challenger, ITF and doubles. It grows as matches complete. That span is the score sequence. The rows we watched ourselves — the ones carrying a real per-point timestamp — begin in March 2026 (our own live capture started on 12 October 2025 at low volume; rows reach the product from March 2026): as of August 27, 2026, 25,747 completed matches carry at least one, across 6.2 million such rows. Earlier matches are reconstructed from a point-by-point feed: true about the score sequence, with no timestamp, and we never estimate one. There is a second, separate corpus behind that: the 2013-2022 reconstructed archive, 97,901 ATP, WTA and Challenger matches and 14,340,663 point rows rebuilt from the public record, with no timestamps and no model fields on any row. It stops at 2013: our results archive reaches back to 1968, but no point-by-point record of those earlier years exists to rebuild from.

What's in the tape?

The ordered score states of a completed match — sets, games, points, server — plus match metadata. Every match declares its own coverage, and you can filter on it: from_start means we watched it live from 0-0, so those rows carry a real timestamp, and our model's win-probability where it was computed — that stamp is best-effort, so it is present on most observed rows and null on the rest. Read the flag as a statement about how the tape opens: a match we watched whose opening games were rebuilt afterwards reports reconstructed even though the rest of it was observed live, so select on tape.rows or meta.point_source rather than the flag alone. At row level the rule is simple: a rebuilt row carries the score only — no timestamp, no model probability — and a tape can hold both kinds. Nothing is ever synthesised from a final scoreline. Completeness is measured per match, not assumed: every tape read carries a meta.points verdict, ?points=complete serves the sequence in full — score after each point, with server and point winner — where that verdict is complete, and as of August 19, 2026, 75.0% of completed matches since 2023 are measured point-complete, a share the running backfills keep raising.

Is there point-by-point data before 2023?

Yes, back to 2013 — and nothing before it. The 2013-2022 archive holds 97,901 ATP, WTA and Challenger matches and 14,340,663 point rows, reconstructed from the public record rather than watched, so no row carries a timestamp or a model number and none ever will. 99.43% of those matches are point-granular; the other 556 resolve to one row per game (555 of them in 2013 and one in 2014). Coverage as of September 1, 2026 is 19.3% of archive matches played in those years and 44.9% of tour-level play — the main draws run 91.6% to 98.7%, Challenger main draw 55.3%, but slam qualifying is 16.0% to 18.1% and ITF and futures are not covered at all. Read a match with GET /history/archive/matches/{id}/tape (core ULTRA, or any Historical Data plan); the per-year bulk files are core ULTRA, or Historical Data Pro, Business or an active one-off package.

Do I need a core API subscription?

No. Every History plan includes API access to the history endpoints on your linked key — a free key is enough. The reverse holds in part: a core PRO or ULTRA subscription already includes the 2023-onward per-match tape and the bulk monthly package downloads on its own key, so there is no History plan to add for those. The 2013-2022 reconstructed archive is the exception — it is on core ULTRA, and on any History plan alongside the key you already have, but it is not part of core PRO.

Choose a plan Create an account

Doing non-commercial academic research or teaching? The January 2023 onward point-by-point data is free under our academic programme, with a citable DOI. The 2013-2022 reconstruction is a commercial product and is not part of that release.

From the makers of the Live Tennis API — real-time scores, odds and model win-probability. Want live signals instead? See Break-point Alerts.

All products Discord alerts app Create an account