← Developer blog

How to get live tennis scores in Python

Live ATP, WTA, Challenger and ITF scores in Python with the official livetennisapi client, plus the score-shape detail that trips most people up.

· · By the Live Tennis API team

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. Read column-wise from pairs it comes out as something else.

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 includes the endpoint, so you never have 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, which serves 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