HistoryPackage
Manifest of one pre-built bulk package (PRO, Historical Data API Pro+, or a one-off package pass). Tape coverage is not a contiguous run of months and is still being extended backwards, so treat the packages listing as the authoritative set of periods that exist. For `kind=tape` the JSONL file holds ONE LINE PER MATCH (a whole HistoryTape object per line, coverage meta included), not one line per point; the CSV is flattened to one row per point and carries no coverage columns. Under `schema_version` 2 (since 2026-09-22) each JSONL line is exactly what `GET /history/matches/{matchId}` returns for that match on its default basis, produced by the same code: the same tape rows with the same fields as the per-match tape (`origin`, `serve`, `outcome`, `winner` beside the score columns) and the same `meta` (`coverage`, `point_source`, `enrichment`, `reconstructed_at`, `observed_span`, `tiebreaks_source`, and `meta.points` whenever the endpoint serves it), so a model that initialises its serve states from `server` / `serve` / `winner` reads them from the bulk file just as it would from the endpoint. The CSV column order is stable and grows only at the end: `match_id`, `row`, `timestamp`, `sets_p1`, `sets_p2`, `games_p1`, `games_p2`, `points_p1`, `points_p2`, `server`, `is_tiebreak`, `win_probability_p1`, `danger` — the columns every month has ever carried — then, since schema version 2, `origin`, `serve`, `outcome`, `winner`, with the same values and vocabularies as the JSONL row (a null is an empty cell); read the header, not positions, for anything past `danger`. A month may also list the complete-basis ADDENDUM files (tennis_history_points_complete_.jsonl.gz / .csv.gz, gzipped and marked by `compression`, added 2026-08-18). The two bases, plainly: the BASE files carry every match's DEFAULT read — already the complete tape for most point-complete matches — and the ADDENDUM carries, for exactly the matches whose complete point sequence exists only as the on-disk reconstruction, the same tape `?points=complete` serves (reconstruction contract: null timestamps and null model fields). Base files are never rewritten by the addendum; their sha256 values do not move. For `kind=rally` (the charted rally corpus, ULTRA) the JSONL holds one line per charted match with its full point list and the CSV one row per point, one file per YEAR. For `kind=archive` (the results archive, 1968–2022) the JSONL holds one line per archive result (the ArchiveMatch shape) and the CSV one row per result, one file per YEAR. For `kind=archive_tape` (the reconstructed 2013–2022 point-by-point tape; ULTRA, History Pro/Business, or an active one-off package window) the JSONL record is byte-for-byte what `GET /history/archive/matches/{archiveId}/tape` returns — one whole ArchiveTape object per line, `meta` included — so a parser written against one match runs over a whole year unchanged; the CSV is the flat per-row view keyed on `archive_match_id` and deliberately carries NO `timestamp`, `win_probability_p1` or `danger` column, because a column empty in 100% of a file invites the belief that it is sometimes populated. Ten periods, `period` 2013 through 2022, each with a JSONL and a CSV file, all `ready`; no earlier year exists, because no tape earlier than 2013 exists. For `kind=elo` (the as-of Elo tape; ULTRA or History Pro/Business) the JSONL holds one rating record per line — the endpoint's field set plus `archive_player_id`, the stable person key a bulk join needs — and the CSV one row per record, one file per YEAR; the files are written in primary-key order, so a rebuild of the same data is byte-identical and the published `sha256` is a real checksum.
CompanySportsDataReal-timeAPITennis
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "https://artifacts.apis.io/livetennisapi-com/json-schema/livetennisapi-com-history-package-schema.json",
"title": "HistoryPackage",
"description": "Manifest of one pre-built bulk package (PRO, Historical Data API Pro+, or a one-off package pass). Tape coverage is not a contiguous run of months and is still being extended backwards, so treat the packages listing as the authoritative set of periods that exist. For `kind=tape` the JSONL file holds ONE LINE PER MATCH (a whole HistoryTape object per line, coverage meta included), not one line per point; the CSV is flattened to one row per point and carries no coverage columns. Under `schema_version` 2 (since 2026-09-22) each JSONL line is exactly what `GET /history/matches/{matchId}` returns for that match on its default basis, produced by the same code: the same tape rows with the same fields as the per-match tape (`origin`, `serve`, `outcome`, `winner` beside the score columns) and the same `meta` (`coverage`, `point_source`, `enrichment`, `reconstructed_at`, `observed_span`, `tiebreaks_source`, and `meta.points` whenever the endpoint serves it), so a model that initialises its serve states from `server` / `serve` / `winner` reads them from the bulk file just as it would from the endpoint. The CSV column order is stable and grows only at the end: `match_id`, `row`, `timestamp`, `sets_p1`, `sets_p2`, `games_p1`, `games_p2`, `points_p1`, `points_p2`, `server`, `is_tiebreak`, `win_probability_p1`, `danger` — the columns every month has ever carried — then, since schema version 2, `origin`, `serve`, `outcome`, `winner`, with the same values and vocabularies as the JSONL row (a null is an empty cell); read the header, not positions, for anything past `danger`. A month may also list the complete-basis ADDENDUM files (tennis_history_points_complete_<period>.jsonl.gz / .csv.gz, gzipped and marked by `compression`, added 2026-08-18). The two bases, plainly: the BASE files carry every match's DEFAULT read — already the complete tape for most point-complete matches — and the ADDENDUM carries, for exactly the matches whose complete point sequence exists only as the on-disk reconstruction, the same tape `?points=complete` serves (reconstruction contract: null timestamps and null model fields). Base files are never rewritten by the addendum; their sha256 values do not move. For `kind=rally` (the charted rally corpus, ULTRA) the JSONL holds one line per charted match with its full point list and the CSV one row per point, one file per YEAR. For `kind=archive` (the results archive, 1968–2022) the JSONL holds one line per archive result (the ArchiveMatch shape) and the CSV one row per result, one file per YEAR. For `kind=archive_tape` (the reconstructed 2013–2022 point-by-point tape; ULTRA, History Pro/Business, or an active one-off package window) the JSONL record is byte-for-byte what `GET /history/archive/matches/{archiveId}/tape` returns — one whole ArchiveTape object per line, `meta` included — so a parser written against one match runs over a whole year unchanged; the CSV is the flat per-row view keyed on `archive_match_id` and deliberately carries NO `timestamp`, `win_probability_p1` or `danger` column, because a column empty in 100% of a file invites the belief that it is sometimes populated. Ten periods, `period` 2013 through 2022, each with a JSONL and a CSV file, all `ready`; no earlier year exists, because no tape earlier than 2013 exists. For `kind=elo` (the as-of Elo tape; ULTRA or History Pro/Business) the JSONL holds one rating record per line — the endpoint's field set plus `archive_player_id`, the stable person key a bulk join needs — and the CSV one row per record, one file per YEAR; the files are written in primary-key order, so a rebuild of the same data is byte-identical and the published `sha256` is a real checksum.",
"x-generated": "2026-10-28",
"x-method": "derived",
"x-generator": "derive-json-schema.py",
"x-source": "openapi/livetennisapi-com-openapi.yaml#/components/schemas/HistoryPackage",
"type": "object",
"properties": {
"period": {
"type": "string",
"description": "Calendar month, `YYYY-MM` — or the bare year `YYYY` on the yearly `rally`/`archive`/`elo`/`archive_tape` kinds."
},
"status": {
"type": "string",
"enum": [
"ready"
],
"description": "Only built months are listed or served."
},
"match_count": {
"type": [
"integer",
"null"
]
},
"row_count": {
"type": [
"integer",
"null"
]
},
"files": {
"type": "array",
"description": "One entry per downloadable format.",
"items": {
"type": "object",
"properties": {
"format": {
"type": "string",
"enum": [
"jsonl",
"csv"
]
},
"filename": {
"type": "string"
},
"bytes": {
"type": "integer"
},
"sha256": {
"type": "string"
},
"compression": {
"type": "string",
"enum": [
"gzip"
],
"description": "Present only on compressed files (the yearly kinds and the points_complete addendum ship gzipped). `bytes` and `sha256` cover the COMPRESSED bytes — exactly what you download."
}
}
}
},
"built_at": {
"type": [
"string",
"null"
],
"format": "date-time"
},
"schema_version": {
"type": [
"integer",
"null"
],
"enum": [
1,
2,
null
],
"description": "Which shape this month's files carry (added 2026-09-22), so you can tell without opening them. `2` = a package row is the API tape row (`origin`, `serve`, `outcome`, `winner`, and the full `meta`), and the CSV carries the trailing columns. `1` = the shape every month built before 22 September 2026 carries: tape rows hold the score columns only (no `origin`, `serve`, `outcome` or `winner`; no `meta.enrichment`, `reconstructed_at` or `observed_span`), and the CSV ends at `danger`. Months built before 22 September 2026 keep this shape UNTIL THEY ARE REBUILT; the rebuild runs newest month first, then backwards, and a month's `schema_version` flips to `2` (and its `built_at` and `sha256` values move) when its turn comes. Check the manifest rather than assuming a month is one or the other: a consumer that needs the enriched fields should require `schema_version >= 2` and, for a month still at `1`, read the per-match endpoint for the matches it needs. `null` = not versioned: the non-tape kinds (`rankings`, `elo`, …)."
},
"kind": {
"type": "string",
"enum": [
"tape",
"rankings",
"rally",
"archive",
"elo",
"archive_tape"
],
"description": "Present only on non-tape packages, so the shape a tape client already parses is unchanged. On a rankings package `match_count` is the number of players covered and `row_count` the number of ranking records; on a rally package the counts are charted matches and points; on an archive package the counts are archive results; on an `elo` package `row_count` is the number of rating records; on an `archive_tape` package `match_count` is the reconstructed tapes in that year and `row_count` their score rows (97,901 and 14,340,663 across the ten years)."
}
}
}
Every JSON Schema here is available over the APIs.io API and to AI agents over MCP.