---
title: "Head-to-head across the results archive (1968–2022) and our own completed matches (2023→now) (BASIC)"
method: GET
path: "/h2h"
---

# Head-to-head across the results archive (1968–2022) and our own completed matches (2023→now) (BASIC)

`GET /h2h`

## Request

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

The record between two players, assembled from BOTH halves of the product: the results archive, where the winner is a stored column, and our own completed matches, where the winner is derived from the final recorded state. Names are the keys — archive people have no roster ids. A fragment matching more than one player is refused with the candidate list (`400 ambiguous_name`), because two people summed into one record is a wrong answer, not a convenience. Passing a name EXACTLY selects that person, even where a longer name contains it, so `Carlos Alcaraz` resolves although `Carlos Alcaraz Gonzalez` also exists. A name you did not type in full stays a fragment: `Sinner` still refuses, and lists Jannik and Martin. Totals count meetings with a KNOWN winner; `undecided` counts the rest. Walkovers and retirements are part of the record, and each meeting carries `outcome` so you can exclude them. Requires BASIC, or any Historical Data API plan. On ULTRA, a per-player `stats` block adds serve/return/break-point aggregates over the pairing: `archive_serve` (serve-side, from 1991) and `current` (2023+, adding return and break-point conversion, aces and winners), each with `meetings_with_stats`.

## Query parameters

- `p1` string, required
- `p2` string, required

## Response `200`

The head-to-head record; empty totals when no player matches the names

- HeadToHead: The record between two players across both halves of the product — the results archive (1968–2022) and our own completed matches (2023→now). Totals count meetings with a KNOWN winner; `undecided` counts the rest.
  - `players` object, nullable: The resolved names ({"p1":{"name"}, "p2":{"name"}}); null when no player matches the fragments.
  - `totals` object
    - `p1_wins` integer
    - `p2_wins` integer
    - `meetings` integer
    - `undecided` integer: Meetings with no derivable winner (never counted in wins).
  - `by_surface` object: Per-surface win split of the decided meetings; keys are surface names plus 'unknown'.
  - `meetings` object[]: Newest first, capped at 200. `era` says which half served the row — 'archive' rows carry `archive_match_id`/`level`/`score`; 'current' rows carry `match_id`/`round_code` and read their score from the match endpoints. `winner` is 1|2 OF THIS H2H (p1/p2 as requested), null when underivable.
    - `era` 'archive' | 'current'
    - `date` string, nullable
    - `tournament` string, nullable
    - `level` string, nullable
    - `round` string, nullable
    - `surface` string, nullable
    - `score` string, nullable
    - `outcome` string, nullable
    - `winner` integer, nullable
  - `stats` object, nullable: ULTRA only — per-player serve/return/break-point aggregates over the pairing, keyed `p1`/`p2`. Each side carries `archive_serve` (serve-side figures, meetings from 1991) and `current` (2023+, adding return and break-point conversion, aces and winners), each with its own `meetings_with_stats` sample size. Absent below ULTRA.

## Other responses

- `400`: Bad query parameter
- `401`: Missing, unknown, or disabled credentials
- `403`: Your tier doesn't unlock this endpoint
- `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)
