---
title: "One monthly package — manifest, or the bulk file itself (PRO)"
method: GET
path: "/history/packages/{period}"
---

# One monthly package — manifest, or the bulk file itself (PRO)

`GET /history/packages/{period}`

## Request

- Base URL: `https://api.livetennisapi.com/api/public/v1`
- URL: `https://api.livetennisapi.com/api/public/v1/history/packages/{period}`
- Auth: one of:
  - HTTP bearer
  - API key in header `X-API-Key`

Without `format` returns the package manifest (file set, counts, sha256). With `format=jsonl` or `format=csv` streams that file as an attachment. Same entitlement as `/history/packages`. 404 when the month has not been built yet — list available months first.

## Path parameters

- `period` string, required

## Query parameters

- `kind` 'tape' | 'rankings' | 'rally' | 'archive' | 'elo' | 'archive_tape'
- `format` 'jsonl' | 'csv' | 'corrections'

## Response `200`

The manifest (no `format`), or the bulk file as an attachment (`format=jsonl` streams NDJSON, `format=csv` streams CSV, `format=corrections` streams the correction CSV). A gzipped file (see the manifest's `compression`) is served as `application/gzip`, never with `Content-Encoding: gzip` — the manifest's `sha256` covers the exact bytes you receive.

- 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_<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.
  - `period` string: Calendar month, `YYYY-MM` — or the bare year `YYYY` on the yearly `rally`/`archive`/`elo`/`archive_tape` kinds.
  - `status` 'ready': Only built months are listed or served.
  - `match_count` integer, nullable
  - `row_count` integer, nullable
  - `files` object[]: One entry per downloadable format.
    - `format` 'jsonl' | 'csv'
    - `filename` string
    - `bytes` integer
    - `sha256` string
    - `compression` 'gzip': 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` string, date-time, nullable
  - `schema_version` 1 | 2 | null, nullable: 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` 'tape' | 'rankings' | 'rally' | 'archive' | 'elo' | 'archive_tape': 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).

## Other responses

- `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.

---

[API](https://skmtc.dev/livetennisapi/apis/live-tennis-api.md) · [All operations](https://skmtc.dev/livetennisapi/apis/live-tennis-api/llms.txt) · [OpenAPI document](https://skmtc.dev/livetennisapi/apis/live-tennis-api/revisions/7881947e8e43?raw)
