---
title: "Per-point price history — the match-winner quote in force at every played point (PRO)"
method: GET
path: "/history/matches/{matchId}/prices"
---

# Per-point price history — the match-winner quote in force at every played point (PRO)

`GET /history/matches/{matchId}/prices`

## Request

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

One row per played point of a match with a mapped market, ascending by `seq` (added 2026-09-15). Each row carries the score state the BASIC tape publishes and, per neutral side ("1" = players.p1's outcome, "2" = p2's), the newest quote observed at or before the point's capture time — or, when none precedes it, the first quote after it within five minutes (negative lag). A side with no usable quote is null, never invented. `resolution` is an honest label of the join: tick (|lag| <= 15 s), minute (<= 90 s), coarse (older); matches played from 2026-09-15 align against the full-resolution in-play tape, earlier ones against the thinned tape and read minute/coarse. The ULTRA-only per-point fields (server, winner, serve, outcome) are not on this surface. Works on a live match too. 404 `no_market` when the match has no mapped market — enumerate with `GET /history/matches?has_market=true`.

## Path parameters

- `matchId` integer, required

## Query parameters

- `after_seq` integer
- `limit` integer

## Response `200`

Per-point price rows, ascending by seq

- object
  - `data` PointPrice[]
    - `seq` integer: Per-match monotonic position 1..N — the same seq as the live points row.
    - `at` string, date-time: Our capture time of the point (UTC).
    - `sets` integer[]
    - `games` array[]: Per-set games arrays, [p1, p2].
      - integer[]
    - `score` object
      - `p1` string, nullable
      - `p2` string, nullable
    - `tiebreak` boolean
    - `prices` object: Keyed by side — "1" = players.p1's outcome, "2" = players.p2's.
      - `1` object, nullable: The quote for this side, or null when no usable quote exists for the point.
        - `bid` number, nullable
        - `ask` number, nullable
        - `mid` number, nullable
        - `spread` number, nullable
      - `2` object, nullable: The quote for this side, or null when no usable quote exists for the point.
        - `bid` number, nullable
        - `ask` number, nullable
        - `mid` number, nullable
        - `spread` number, nullable
    - `lag_seconds` integer, nullable: Seconds between the quote and the point, the larger of the two sides (a negative side lag means the first quote after the point was used); null when no side aligned.
    - `resolution` 'tick' | 'minute' | 'coarse' | 'null', nullable: tick |lag| <= 15 s, minute <= 90 s, coarse beyond — the coarser of the two sides; null when no side aligned. An honest label, never upgraded.
  - `meta` object
    - `match_id` integer
    - `count` integer
    - `limit` integer
    - `last_seq` integer: The resume cursor — pass back as ?after_seq= while has_more is true.
    - `has_more` boolean
    - `alignment` 'points' | 'none': points = the match has a per-point stream to align to; none = it has none (a reconstruction-only tape carries no point clock and no quote is fabricated — data is empty, not an error).
    - `resolution_counts` object
      - `tick` integer
      - `minute` integer
      - `coarse` integer

## 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
- `410`: The match id existed and was merged into another match record (since 2026-09-05). The body is `MatchMerged`, not `Error`: it carries a forwarding address, which is the whole point of answering 410 rather than 404.
- `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)
