← Developer Blog

Tennis Point-by-Point Data API (ATP & WTA)

Get tennis point-by-point data: the completed-match tape on Basic and live per-point events on Ultra. Real endpoints, curl + Python + JS examples.

· · By the Live Tennis API team

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:

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

Full reference and the OpenAPI spec live at docs.livetennisapi.com; an MCP server is available at mcp.livetennisapi.com.

Frequently asked questions

Is tennis point-by-point data free?

The Free tier covers live matches and current scores only, not the point-by-point tape. The completed-match tape is on the Basic tier ($9.99/mo) via GET /matches/{id}/points. Live per-point events as a match happens are on the Ultra tier.

What endpoint returns tennis point-by-point data?

Use GET /matches/{id}/points against https://api.livetennisapi.com/api/public/v1 with an X-API-Key header. On Basic it returns the full tape of a completed match; on Ultra it also serves live per-point events, alongside the WS /ws stream.

What is the difference between the point tape and live per-point events?

The tape is the complete, ordered point history of a match that has finished (Basic). Live per-point events are pushed as each point is played during an in-progress match, which requires Ultra and is best consumed over the WS /ws WebSocket.

Does the point-by-point data include break points?

Yes. Points carry a break-point flag following the standard rule: receiver at advantage, or receiver on 40 against a server on 0/15/30. Break points are never flagged in tiebreaks and are absent when the server or point fields are null.

Which tours are covered?

ATP, WTA, Challenger, and ITF. You can pull point-by-point tapes for completed matches across all four tours on the Basic tier, and stream live per-point events for them on Ultra.

Built with the Live Tennis API — real-time scores, players, odds and model win-probability for ATP, WTA, Challenger and ITF.

API reference SDKs on GitHub Plans from $9.99/mo