← Developer Blog

How to get live tennis scores in Python

Fetch live ATP, WTA, Challenger and ITF scores in Python with the official livetennisapi client — install, authenticate, read the score, and handle the one score-shape detail that trips most people up.

Live tennis scores are awkward to get right. Most feeds either poll a scraped web page or hand you a shape that quietly loses information. This walks through reading real live scores in Python in about five minutes.

Install

pip install livetennisapi

One dependency (httpx). Python 3.9+.

Authenticate

Get a key at livetennisapi.com. Keys look like twjp_…. The client reads LIVETENNISAPI_KEY from the environment, so you never have to hard-code it:

export LIVETENNISAPI_KEY=twjp_your_key_here

Read the live board

from livetennisapi import LiveTennisAPI

with LiveTennisAPI() as client:
    for match in client.list_matches(status="live"):
        print(match.tournament, "—", match.p1.name, "vs", match.p2.name)
        print("  ", match.score.sets, match.score.points)

Real output looks like this:

Lincoln — Bernard Tomic vs Spencer Johnson
   [0, 0] ['40', '30']
M15 Rochester, NY — Michael Antonius vs Ilyas Milad Fahim
   [0, 0] ['40', '0']

The one thing to get right: games is player-major

This is the detail that catches almost everyone. games is not a list of sets. It is [games_p1, games_p2], where each side is its own per-set list:

score.games      # [[6, 3, 2], [4, 6, 1]]
                 #  ^ p1 per set  ^ p2 per set

That reads 6-4, 3-6, 2-1 — not 6-4, 3-6, 2-1 read column-wise from pairs. Indexing it the other way gives you a plausible-looking but wrong scoreline, which is the worst kind of bug. Use the helper:

score.games_for_set(0)   # (6, 4)
score.games_for_set(1)   # (3, 6)

sets is simpler: [sets_p1, sets_p2]. And server is 1 or 2.

Handle the errors you will actually hit

from livetennisapi import UpgradeRequired, RateLimited

try:
    analysis = client.get_match_analysis(match_id)
except UpgradeRequired as exc:
    print(f"needs the {exc.required_tier} plan")   # 'ULTRA'
except RateLimited as exc:
    print(f"slow down, retry in {exc.retry_after}s")

UpgradeRequired is not an auth failure — the key is fine, the plan is too low. The client tells you which tier unlocks the endpoint rather than leaving you to guess from a bare 403.

Requests retry automatically on 429 and 5xx only, honouring Retry-After with exponential backoff. Other 4xx are never retried, because a bad key or an unentitled tier cannot start working and retrying only burns your rate limit.

Async, if you need it

Identical API, awaited:

from livetennisapi import AsyncLiveTennisAPI

async with AsyncLiveTennisAPI() as client:
    page = await client.list_matches(status="live")

Paginate

limit defaults to 50 and the API rejects anything above 200. To walk everything without managing offsets:

for player in client.paginate("search_players", search="alcaraz"):
    print(player.name, player.ranking)

From the command line

The package ships a CLI, so you can check the feed without writing any code:

livetennis live
livetennis match 18953
livetennis players djokovic

Where to go next

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