Skip to main content
Live Tennis API

livetennisapi

Official Python client for the Live Tennis API.

Real-time tennis scores, players, rankings, match-winner market prices and model win-probability — for ATP, WTA, Challenger and ITF, over REST and WebSocket.

PyPI Python License

Documentation · Get a free API key


Install

pip install livetennisapi          # REST client + CLI
pip install "livetennisapi[all]"   # + WebSocket feed and rich CLI tables

Use

from livetennisapi import LiveTennisAPI

with LiveTennisAPI(api_key="twjp_…") as client:   # or set LIVETENNISAPI_KEY
    for match in client.list_matches(status="live"):
        print(match.tournament, match.p1.name, "vs", match.p2.name, match.score.sets)

Async is the same API, awaited:

from livetennisapi import AsyncLiveTennisAPI

async with AsyncLiveTennisAPI() as client:
    match = await client.get_match(18953)

Command line

The package ships a livetennis command:

$ livetennis live
Live matches (3)
ID     Tournament            Rd   Players                  Score
18953  ATP Wimbledon         R16  *Alcaraz / Sinner        6-4 3-6 2-1 (40-30)

$ livetennis match 18953
$ livetennis players djokovic
$ livetennis watch --match 18953     # live WebSocket stream

Live score feed (ULTRA)

from livetennisapi import LiveScoreStream

with LiveScoreStream() as stream:
    for update in stream:
        print(update.match_id, update.score.sets)

Reconnects automatically with backoff and re-subscribes. Heartbeats are consumed internally, so you only see real score changes. It deliberately does not reconnect on a bad key or an insufficient tier — those raise immediately rather than retry forever.

Break-point signals

Opt in with signals=["break_point"] to also receive the headline break-point feed. The stream then yields a BreakPoint the moment a break point arises and a BreakPointResult when it resolves, alongside the usual ScoreUpdate:

from livetennisapi import LiveScoreStream, ScoreUpdate, BreakPoint, BreakPointResult

with LiveScoreStream(signals=["break_point"]) as stream:
    for frame in stream:
        if isinstance(frame, BreakPoint):
            print(f"BREAK POINT on match {frame.match_id}: "
                  f"p{frame.returner} has {frame.break_points} vs server p{frame.server}")
        elif isinstance(frame, BreakPointResult):
            print(f"  -> {frame.outcome} (p1 win prob now {frame.win_probability_p1_after})")
        elif isinstance(frame, ScoreUpdate):
            print(frame.match_id, frame.score.sets)

With no signals the stream behaves exactly as before — score frames only. Both the feed and the model fields are ULTRA-only. A runnable example lives in livetennisapi-starter-python.

Tiers

FREE BASIC PRO ULTRA
list_matches get_match get_match_score
search_players get_player list_fixtures
list_tournaments get_tournament
list_completed_matches (history) ✅¹
list_archive_matches get_archive_match list_archive_players get_archive_career get_h2h (results archive · head-to-head) ✅¹
list_match_events list_markets get_market_prices
get_match_analysis, win_probability_p1 / danger, WebSocket

¹ Also unlocked by any History plan, which works on top of a FREE key.

Calling above your tier raises UpgradeRequired, which tells you which tier you need:

from livetennisapi import UpgradeRequired

try:
    client.get_match_analysis(18953)
except UpgradeRequired as exc:
    print(exc.required_tier)   # 'ULTRA'

Errors

Exception When
Unauthorized 401 — key missing, unknown, or disabled
UpgradeRequired 403 — valid key, tier too low (carries .required_tier)
NotFound 404 — no such resource, or no data yet
RateLimited 429 — carries .retry_after in seconds
ServerError / ServiceUnavailable 5xx
APIConnectionError / APITimeoutError never reached the API

All inherit from LiveTennisAPIError.

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

The results archive (1968–2022) and head-to-head

Two halves, one product: the results archive — a licensed corpus of completed-match results, ATP and WTA, main draws, qualifying and the ITF/futures tiers, 1968 through 2022 — and the point-by-point tape (2023→now) behind list_completed_matches. The archive ends exactly where the tape begins, so no match is ever served from two datasets.

# Winner/loser-shaped results with ranks and seeds AT THE TIME of the match.
for m in client.list_archive_matches(tour="atp", name="borg", round="F"):
    print(m.event_date, m.tournament, m.winner.name, m.score)

# Cross-era head-to-head — archive + our own completed matches, in one call.
h2h = client.get_h2h("federer", "nadal")
print(h2h.totals, h2h.by_surface)

# Career aggregates: W-L by surface/level/year, titles, summed serve stats.
career = client.get_archive_career("borg")

Three things worth knowing before you lean on it:

  • event_date is the tournament START date — per-match dates do not exist in this era's records, and none are invented.
  • Names are the keys for get_h2h and get_archive_career (archive people have no roster ids). A fragment matching more than one player raises BadRequest with error_code == "ambiguous_name" and the candidate list in exc.body["candidates"] — disambiguate and retry.
  • meetings[i]["winner"] in an H2H is 1|2 of your request (p1/p2 as you passed them), not of the underlying match row.

Pagination

limit defaults to 50; the API rejects anything above 200. To walk everything — paginate() clamps the page size for you:

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

Forward compatibility

The API ships additive changes within v1, so this client never rejects a field it doesn't recognise. Unknown fields stay reachable:

match = client.get_match(18953)
match.raw["some_new_field"]   # present if the server sent it
match.some_new_field          # also works

That means a new server-side field is usable without upgrading this package.

The score shape (read this one)

games is player-major, not set-major:

score.games      # [[6, 3, 2], [4, 6, 1]]  ->  6-4, 3-6, 2-1
                 #  ^p1 per set  ^p2 per set
score.sets       # [1, 1]  ->  one set each
score.server     # 1 or 2

Indexing it the other way is the most common mistake made against this API, so there's a helper:

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

Configuration

LiveTennisAPI(
    api_key="twjp_…",          # or $LIVETENNISAPI_KEY
    base_url=None,             # or $LIVETENNISAPI_BASE_URL
    timeout=30.0,
    max_retries=2,
    auth_header="bearer",      # or "x-api-key"
)

Contributing

Issues and pull requests welcome at livetennisapi/livetennisapi-python.

pip install -e ".[dev]"
pytest -m "not contract"                  # unit tests, offline
LIVETENNISAPI_KEY=twjp_… pytest -m contract   # verify against the live API

The contract tests assert that the live API's real responses match these models. If the API and the spec disagree, that's a bug worth reporting.

Related

Everything in the Live Tennis API developer surface:

Install Source Package
Python client (this repo) pip install livetennisapi package
JavaScript / TypeScript client npm install livetennisapi repo package
MCP server for LLM agents npx livetennisapi-mcp repo package
Vercel AI SDK tools npm install livetennisapi-ai repo
Break-point starter — Python repo
Break-point starter — Node repo
Break-point starter — Go repo

Licence

MIT — see LICENSE. Use of the API service is governed by the Terms of Service.

Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

livetennisapi-1.2.0.tar.gz (40.3 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

livetennisapi-1.2.0-py3-none-any.whl (32.0 kB view details)

Uploaded Python 3

File details

Details for the file livetennisapi-1.2.0.tar.gz.

File metadata

  • Download URL: livetennisapi-1.2.0.tar.gz
  • Upload date:
  • Size: 40.3 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/7.0.0 CPython/3.13.14

File hashes

Hashes for livetennisapi-1.2.0.tar.gz
Algorithm Hash digest
SHA256 80e9da56cb16f1be41caf570dded2ebc81d7ff3909289bfea087feb4717f0a99
MD5 8c1a37a0a483f84c4f9c98ef44a44548
BLAKE2b-256 83416c18be5c615f0d60439303f941c9b1c35600723f2eaac83a9baf7a599858

See more details on using hashes here.

Provenance

The following attestation bundles were made for livetennisapi-1.2.0.tar.gz:

Publisher: publish.yml on livetennisapi/livetennisapi-python

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file livetennisapi-1.2.0-py3-none-any.whl.

File metadata

  • Download URL: livetennisapi-1.2.0-py3-none-any.whl
  • Upload date:
  • Size: 32.0 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/7.0.0 CPython/3.13.14

File hashes

Hashes for livetennisapi-1.2.0-py3-none-any.whl
Algorithm Hash digest
SHA256 32bbe390e0e6e9420d81345890dd5c9c4436419bbf81825ac2ae21e156595cef
MD5 65a1a706d26473b46c6034332a8e6088
BLAKE2b-256 4fc17fb62326f93728d22611e4fa64d25e243c0f11cf2ef4af49beb8c500d2bb

See more details on using hashes here.

Provenance

The following attestation bundles were made for livetennisapi-1.2.0-py3-none-any.whl:

Publisher: publish.yml on livetennisapi/livetennisapi-python

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Release history Release notifications | RSS feed

1.7.0

2 files

1.6.0

2 files

1.5.0

2 files

1.4.0

2 files

1.3.2

2 files

1.3.1

2 files

1.3.0

2 files

This release

1.2.0 This release

2 files

1.1.0

2 files

1.0.2

2 files

1.0.1

2 files

1.0.0

2 files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page