Tennis scores API: live ATP & WTA scores
A tennis scores API returns the current state of play: which matches are live and what the score is right now. With Live Tennis API you list live matches at GET /matches?status=live and read one match's score at GET /matches/{id}/score, across ATP, WTA, Challenger and ITF. Both are on the free tier.
The base URL is https://api.livetennisapi.com/api/public/v1 and every request authenticates with an X-API-Key header. You can get a free API key without a card. The free tier allows 30 requests/minute and 100 requests/day, which is enough to poll a live scoreboard.
What the free tier covers
The free tier is built for the current state of play:
GET /matches?status=livelists every match in progress now (filter bytour=atp|wta|challenger|itf)GET /matches/{id}/scoreis the fastest read for a single match's live scoreGET /fixtureslists upcoming matches, so you know what's about to startGET /players/{id}returns a player's profile, including current ranking and Elo rating
Completed-match history and the point-by-point tape start on Basic. See tennis point-by-point data.
For the full surface and tiers, read the tennis API developer guide.
Get live tennis scores (curl)
List what's live right now:
curl -s "https://api.livetennisapi.com/api/public/v1/matches?status=live&tour=atp" \
-H "X-API-Key: $LIVETENNIS_API_KEY"
Then read one match's score by its id:
curl -s "https://api.livetennisapi.com/api/public/v1/matches/12345/score" \
-H "X-API-Key: $LIVETENNIS_API_KEY"
/matches/{id}/score is the lowest-overhead read. Use it when you already know the match id and only need the score to tick over.
Poll a live scoreboard in Python
import os, time, requests
BASE = "https://api.livetennisapi.com/api/public/v1"
HEADERS = {"X-API-Key": os.environ["LIVETENNIS_API_KEY"]}
def live_scoreboard(tour="wta"):
r = requests.get(f"{BASE}/matches",
params={"status": "live", "tour": tour},
headers=HEADERS, timeout=10)
r.raise_for_status()
for m in r.json().get("matches", []):
print(m["id"], "-", m.get("score"))
# Refresh every 20s to stay well inside the free 30 req/min limit
while True:
live_scoreboard()
time.sleep(20)
Prefer not to hand-roll HTTP? The official SDK wraps the same endpoints: pip install livetennisapi.
JavaScript / Node
const BASE = "https://api.livetennisapi.com/api/public/v1";
const headers = { "X-API-Key": process.env.LIVETENNIS_API_KEY };
async function liveScores(tour = "atp") {
const res = await fetch(`${BASE}/matches?status=live&tour=${tour}`, { headers });
if (!res.ok) throw new Error(`HTTP ${res.status}`);
const { matches } = await res.json();
return matches.map((m) => ({ id: m.id, score: m.score }));
}
liveScores().then(console.log);
There's a JavaScript SDK too (npm i livetennisapi), plus an OpenAPI spec and an MCP server at mcp.livetennisapi.com if you're wiring scores into an AI agent.
How often should I poll?
The free tier is 30 requests/minute and 100 requests/day. A single scoreboard call every ~20 seconds fits comfortably and keeps scores fresh. If you're tracking many matches independently, poll the lightweight /matches/{id}/score per match instead of re-listing everything, and mind the daily cap. Push instead of polling, meaning the WebSocket live stream and live per-point events, is on the Ultra tier (WS /ws).
Full endpoint reference and response fields live at docs.livetennisapi.com.