Live Tennis API · docs · version 1.13.4

Shot-by-shot tennis rally and charting API

Is there tennis data below the point — shot by shot? Yes, for charted matches. GET /rally/matches lists them and /rally/matches/{rallyMatchId} returns the rally construction; GET /history/matches/{matchId}/rally reaches the same data by our own match id so a live match and its charting share one identifier. GET /charting/players aggregates a player's career at shot level and /charting/matches/{chartingMatchId} returns every stat family for both players in one charted match. All ULTRA.

5 endpoints on this page. Base URL https://api.livetennisapi.com/api/public/v1; authenticate with the X-API-Key header. Plans involved: ULTRA. A free key needs no card.

GET /rally/matches

Charted matches with shot-by-shot data (ULTRA)

Plan required: ULTRA · operationId: listRallyMatches

Charted matches, newest first. RALLY CONSTRUCTION IS THE LAYER BELOW THE TAPE: the tape says what the score became after each point, this says how the point was played. It has its OWN id space. The charted corpus and our own match table are different populations — the corpus reaches back decades and concentrates on the biggest events, while our matches begin when our own collection did. Keying this on our match ids would hide most of it. Ask this endpoint for the authoritative coverage list rather than assuming a match is charted: charting is human work, so coverage is deep, not universal.

Parameters

GET /rally/matches — parameters
NameInTypeRequiredNotes
playerquerystringnoSubstring match on either player name.
fromquerystringnoYYYY-MM-DD.
toquerystringnoYYYY-MM-DD.
surfacequerystringno
genderquerystring (M, W)no
limitqueryintegerno Default 50.
offsetqueryintegerno Default 0.

Responses

GET /rally/matches — responses
StatusMeaning
200Charted matches, with per-match parse-quality counts
400Bad query parameter
401Missing, unknown, or disabled credentials
403Your tier doesn't unlock this endpoint
429Rate 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.

Response fields

GET /rally/matches — response fields
FieldTypeDescription
dataarray of object
metaobject

Example

curl https://api.livetennisapi.com/api/public/v1/rally/matches \
  -H "Authorization: Bearer twjp_..."

GET /rally/matches/{rallyMatchId}

Rally construction for one charted match (ULTRA)

Plan required: ULTRA · operationId: getRallyMatch

One charted match with its points, in play order. Paged with limit/offset; meta.total is the match's full point count.

Parameters

GET /rally/matches/{rallyMatchId} — parameters
NameInTypeRequiredNotes
rallyMatchIdpathintegeryes
limitqueryintegerno Default 50.
offsetqueryintegerno Default 0.

Responses

GET /rally/matches/{rallyMatchId} — responses
StatusMeaning
200The charted match with its rally points
401Missing, unknown, or disabled credentials
403Your tier doesn't unlock this endpoint
404No such resource, or no data yet
429Rate 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.

Response fields

GET /rally/matches/{rallyMatchId} — response fields
FieldTypeDescription
rally_match_idintegerThe id this product is keyed on.
source_idstring
match_idinteger or nullOUR match id, when the charted match is also one we hold. Null otherwise — most charted matches predate our own collection.
datestring or null
tournamentstring or null
roundstring or null
surfacestring or null
genderstring or null (M, W, null)
best_ofinteger or null
playersarray of object
pointsintegerCharted points in this match.
points_parsedintegerHow many of them our parser read cleanly — the per-match quality number.
metaobject
rallyarray of object

Example

curl https://api.livetennisapi.com/api/public/v1/rally/matches/{rallyMatchId} \
  -H "Authorization: Bearer twjp_..."

GET /history/matches/{matchId}/rally

Rally construction by OUR match id (ULTRA)

Plan required: ULTRA · operationId: getMatchRally

Rally construction addressed by OUR match id, resolved through the optional link. Answers 404 {"error":"not_charted"} when we hold the match but nobody charted it — deliberately distinct from "no such match", because most of our matches are not charted and a consumer walking the archive must tell them apart.

Parameters

GET /history/matches/{matchId}/rally — parameters
NameInTypeRequiredNotes
matchIdpathintegeryesThe match id. Every match route shares ONE id space, so the same value works everywhere matchId appears. Get it from any match list, where it is the id field of each match object: GET /matches?status=live (in progress), GET /matches?status=upcoming or GET /fixtures (scheduled), GET /matches?status=completed or GET /history/matches (finished). Ids are stable for the life of a match, so one captured before it starts still resolves after it finishes.
limitqueryintegerno Default 50.
offsetqueryintegerno Default 0.

Responses

GET /history/matches/{matchId}/rally — responses
StatusMeaning
200The charted match with its rally points
401Missing, unknown, or disabled credentials
403Your tier doesn't unlock this endpoint
404No such resource, or no data yet
410The 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.
429Rate 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.

Response fields

GET /history/matches/{matchId}/rally — response fields
FieldTypeDescription
rally_match_idintegerThe id this product is keyed on.
source_idstring
match_idinteger or nullOUR match id, when the charted match is also one we hold. Null otherwise — most charted matches predate our own collection.
datestring or null
tournamentstring or null
roundstring or null
surfacestring or null
genderstring or null (M, W, null)
best_ofinteger or null
playersarray of object
pointsintegerCharted points in this match.
points_parsedintegerHow many of them our parser read cleanly — the per-match quality number.
metaobject
rallyarray of object

Example

curl https://api.livetennisapi.com/api/public/v1/history/matches/18953/rally \
  -H "Authorization: Bearer twjp_..."

GET /charting/players

Career shot-level charting aggregate for one player (ULTRA)

Plan required: ULTRA · operationId: getChartingPlayer

The deepest serve/return profile we hold, from the Match Charting Project: serve placement (deuce/ad × wide/body/T), return depth and outcomes, net and serve-and-volley conversion, clutch break/game/set-point serving and returning, winners and unforced errors by wing, and rally-length and shot-direction tendencies — summed over the player's charted matches. name (min 3 chars) is the key; a fragment matching more than one charted person is refused with the candidate list, and gender=men|women disambiguates. Every field is a raw SUM over the player's Total rows and matches_charted states the sample. COVERAGE IS CURATED — 11,646 charted matches across both tours back to the 1960s, concentrated on the majors, NOT full-slate coverage.

Parameters

GET /charting/players — parameters
NameInTypeRequiredNotes
namequerystringyesPlayer name (fragment, min 3 chars).
genderquerystring (men, women)noDisambiguates a fragment that matches one charted person per tour side.

Responses

GET /charting/players — responses
StatusMeaning
200The player's summed charting families with the sample size
400Bad query parameter
401Missing, unknown, or disabled credentials
403Your tier doesn't unlock this endpoint
404No such resource, or no data yet
429Rate 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.

Response fields

GET /charting/players — response fields
FieldTypeDescription
playerobject
matches_chartedinteger
coveragestring
familiesobjectPer-family summed numeric columns.

Example

curl https://api.livetennisapi.com/api/public/v1/charting/players \
  -H "Authorization: Bearer twjp_..."

GET /charting/matches/{chartingMatchId}

One charted match, every stat family for both players (ULTRA)

Plan required: ULTRA · operationId: getChartingMatch

Every Match Charting Project stat family for one charted match, both players, with the per-set split (row/set 1, 2, Total) exactly as charted. chartingMatchId is this product's own id space (1960–2026, mostly matches with no counterpart in the live table).

Parameters

GET /charting/matches/{chartingMatchId} — parameters
NameInTypeRequiredNotes
chartingMatchIdpathintegeryes

Responses

GET /charting/matches/{chartingMatchId} — responses
StatusMeaning
200The charted match's stat families, both players, per set
401Missing, unknown, or disabled credentials
403Your tier doesn't unlock this endpoint
404No such resource, or no data yet
429Rate 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.

Response fields

GET /charting/matches/{chartingMatchId} — response fields
FieldTypeDescription
charting_match_idinteger
mcp_idstring
genderstring
playersobject
familiesobject

Example

curl https://api.livetennisapi.com/api/public/v1/charting/matches/{chartingMatchId} \
  -H "Authorization: Bearer twjp_..."