Tennis point-by-point data API
Point-by-point tennis data records every point of a match — server, winner, the running game score, and events like break points. With Live Tennis API, the completed-match tape is available on the Basic tier via GET /matches/{id}/points, while live per-point events as a match unfolds require the Ultra tier (live points plus the WS /ws stream). This guide shows both, with runnable code.
All requests use the base URL https://api.livetennisapi.com/api/public/v1 and authenticate with an X-API-Key header. Coverage spans ATP, WTA, Challenger, and ITF. Grab a free key (no card) at livetennisapi.com/subscribe/free to explore live scores first, then upgrade when you need the tape.
What "point-by-point" means here
A point-by-point record is the ordered sequence of points inside a match. For each point you typically get:
- Who served and who won the point
- The game score after the point (e.g.
15-0,40-30,AD) - The set / game context the point belongs to
- Break-point flags, when applicable
Break points follow the standard rule: the receiver is at advantage, or the receiver holds 40 against a server on 0/15/30. Break-point flags never appear during tiebreaks, and are absent when the server or point fields are null.
The tier split (read this honestly)
There are two distinct things people mean by "point-by-point data", and they sit on different tiers:
| What you want | Endpoint | Tier |
|---|---|---|
| The full point tape of a completed match | GET /matches/{id}/points |
Basic ($9.99/mo) |
| Live per-point events as the match happens | GET /matches/{id}/points (live match) + WS /ws |
Ultra ($99.99/mo) |
| Match analysis / win-probability per point | GET /matches/{id}/analysis |
Ultra |
The Free tier does not include the tape — it covers the current state of play only (live matches and current scores). If you only need the live scoreline (not every point), see the tennis scores API guide, which works on the free key.
Getting a completed-match tape (Basic)
First find a match id. Recent completed matches show up through the fixtures/results surface; here we assume you already have an id. Then pull its points:
curl -s "https://api.livetennisapi.com/api/public/v1/matches/MATCH_ID/points" \
-H "X-API-Key: $LIVE_TENNIS_API_KEY"
The response is the ordered list of points for that match. A single point looks roughly like this (fields present depend on the match):
{
"match_id": "MATCH_ID",
"points": [
{
"set": 1,
"game": 1,
"server": "player_a",
"point_winner": "player_a",
"score": "15-0",
"break_point": false
},
{
"set": 1,
"game": 3,
"server": "player_b",
"point_winner": "player_a",
"score": "40-AD",
"break_point": true
}
]
}
Python
import os, requests
BASE = "https://api.livetennisapi.com/api/public/v1"
headers = {"X-API-Key": os.environ["LIVE_TENNIS_API_KEY"]}
r = requests.get(f"{BASE}/matches/MATCH_ID/points", headers=headers, timeout=10)
r.raise_for_status()
tape = r.json()
for p in tape["points"]:
flag = " [BREAK POINT]" if p.get("break_point") else ""
print(f"Set {p['set']} Game {p['game']}: "
f"{p['server']} serving, {p['point_winner']} won -> {p['score']}{flag}")
An official Python SDK (pip install livetennisapi) wraps the same endpoint.
JavaScript / TypeScript
const BASE = "https://api.livetennisapi.com/api/public/v1";
const headers = { "X-API-Key": process.env.LIVE_TENNIS_API_KEY };
const res = await fetch(`${BASE}/matches/MATCH_ID/points`, { headers });
if (!res.ok) throw new Error(`HTTP ${res.status}`);
const tape = await res.json();
for (const p of tape.points) {
const flag = p.break_point ? " [BREAK POINT]" : "";
console.log(
`Set ${p.set} Game ${p.game}: ${p.server} serving, ` +
`${p.point_winner} won -> ${p.score}${flag}`
);
}
An official JavaScript SDK (npm i livetennisapi) wraps the same endpoint.
Reconstructing break points yourself
If you want to derive break-point situations rather than read the flag, apply the rule directly. This only holds outside tiebreaks and when the server and score are known:
def is_break_point(server, receiver_score, server_score, in_tiebreak):
if in_tiebreak:
return False
if receiver_score is None or server_score is None:
return False
if receiver_score == "AD":
return True
return receiver_score == "40" and server_score in ("0", "15", "30")
This matches how the tape flags break points, so you can validate one against the other.
Live per-point events (Ultra)
For a match in progress, Ultra unlocks per-point events as they happen. You can poll the same points endpoint on a live match, but the efficient path is the WebSocket stream at WS /ws, which pushes updates instead of making you poll. The subscribe and message shapes below are illustrative — see the docs for the exact schema:
import asyncio, json, websockets
async def stream_points(match_id, api_key):
url = "wss://api.livetennisapi.com/api/public/v1/ws"
async with websockets.connect(url, extra_headers={"X-API-Key": api_key}) as ws:
await ws.send(json.dumps({"subscribe": "match", "id": match_id}))
async for raw in ws:
msg = json.loads(raw)
if msg.get("type") == "point":
print(msg["server"], "->", msg["point_winner"], msg.get("score"))
asyncio.run(stream_points("MATCH_ID", "YOUR_API_KEY"))
Ultra also adds GET /matches/{id}/analysis for per-point win-probability and analysis on top of the raw points.
Tips
- Start on the free key to learn the live-match and score endpoints, then add Basic for the completed tape.
- The tape is per completed match — iterate over your fixtures/results to backfill history.
- Fields can be null early in a point stream; guard for missing
server/scorebefore deriving break points. - See the pillar developer guide for the full endpoint map, and the stats & player database guide for rankings and Elo.
Full reference and the OpenAPI spec live at docs.livetennisapi.com; an MCP server is available at mcp.livetennisapi.com.