← Developer Blog

REST or WebSocket for live tennis scores?

Poll REST for the scoreline — it is free and needs no reconnect logic. Use the push feed only for per-point events. The honest boundary, with the arithmetic.

· By the Live Tennis API team

Poll GET /matches?status=live on a timer if you need the scoreline. Use the WebSocket feed if you need every point as it happens and the delay between polls would change what your code does. That is the whole decision, and for most applications the answer is REST — which is also the one that works on a free key.

Base URL https://api.livetennisapi.com/api/public/v1, X-API-Key header on every request. Tier limits below are the published figures as of September 14, 2026. A free key needs no card: livetennisapi.com/subscribe/free.

What each one gives you

REST polling WebSocket push
Endpoint GET /matches?status=live, GET /matches/{id}/score push feed via GET /ws-token (or the native WS /ws)
Tier Free ($0, no card) Ultra ($99.99/mo)
Granularity the score when you asked every point as it is written
Cost model one request per poll, against 100/day on Free one connection
Fails by going stale between polls needing reconnect logic

What does polling actually cost?

The free tier allows 30 requests per minute and 100 per day. A scoreboard polling one call every 20 seconds uses 3 per minute — comfortably inside the rate limit, but it exhausts the daily 100 in about 33 minutes.

That is the real constraint on Free, and it is a daily cap rather than a speed limit. Options, in the order most people should consider them:

  1. Poll the slate, not each match. GET /matches?status=live returns every in-progress match in one request. Polling that once every 30 seconds is 2 requests/minute regardless of how many matches are on — which is the single biggest saving available, and the mistake most often made is polling per match.
  2. Poll only while matches are live. Tennis has long dead periods. A scheduler that sleeps when GET /fixtures says nothing starts for six hours costs nothing during those six hours.
  3. Move up a tier. Basic ($9.99/mo) raises the cap to 60/min and 1,000/day, which sustains a 30-second poll all day.

When is REST genuinely not enough?

Polling gives you the score at the moment you asked. If a point is won and lost between two polls, you never see it. That matters for exactly three kinds of application:

All three are Ultra: the push feed reached via GET /ws-token, live per-point events at GET /matches/{id}/points, and model win-probability at GET /matches/{id}/analysis.

Connecting to the push feed

There are two WebSocket surfaces and they are not interchangeable. PushStream is the high-fan-out push feed and the one to use for anything continuous; the native /ws feed rides shared infrastructure with a concurrent-connection ceiling per key.

The push feed speaks the Centrifugo client protocol rather than plain JSON frames, so use the SDK rather than a bare WebSocket client — it mints the short-lived token from /ws-token, connects to the URL that response returns, subscribes, answers heartbeats, and mints a fresh token on every reconnect:

from livetennisapi import PushStream

with PushStream() as stream:                  # slate:all — every live match
    for update in stream:
        print(update)

with PushStream(match_ids=[18953]) as stream:  # one match
    for update in stream:
        print(update)

with PushStream(match_ids=[18953], points=True) as stream:  # per-point events
    for update in stream:
        print(update)

pip install livetennisapi (≥ 1.4.0) or npm i livetennisapi (≥ 1.5.0) ships it. Writing your own Centrifugo client is possible and documented, but the token-refresh-on-reconnect behaviour is the part people get wrong, and it is the part the SDK already handles.

How do you read the score on either transport?

One detail catches people on both transports. Every score array is player-major: the first element is player one, the second is player two.

{ "sets": [1, 0], "games": [[6, 3], [4, 4]], "points": ["40", "30"], "server": 1 }

That reads: player one leads by a set, won the first set 6–4, the second is 3–4, the current game is 40–30, and player one is serving.

The honest recommendation

Start on REST with a free key. Poll the slate rather than each match, sleep when nothing is live, and see whether the staleness between polls actually costs you anything. Most live-score displays, notification bots and dashboards never need more. Move to the push feed when you can name the specific behaviour that polling makes impossible — not before, because a WebSocket adds reconnection, backfill-on-reconnect and ordering concerns that a polling loop simply does not have.

Full reference at docs.livetennisapi.com, with topic pages for live scores and the push feed and webhooks. Tiers and limits: pricing.

Frequently asked questions

Should I use REST or WebSocket for live tennis scores?

REST for almost everything. Polling `GET /matches?status=live` gives you the current scoreline, works on the free tier with no card, and needs no reconnection logic. Choose the WebSocket push feed only when you can name a behaviour polling makes impossible — reacting to individual points, acting where a twenty-second-old score is materially worse than a current one, or following many matches at a frequency per-match polling could not sustain. Those are Ultra-tier cases. Starting on REST and moving up when you hit a real limit is cheaper than starting with a connection you have to babysit.

How often can I poll live tennis scores on the free tier?

The free tier allows 30 requests per minute and 100 per day. The daily cap binds first: one call every 20 seconds is only 3 per minute, but it exhausts 100 requests in about 33 minutes. The fix is to poll the slate rather than each match — `GET /matches?status=live` returns every in-progress match in a single request, so your cost is independent of how many matches are on. Sleeping when no matches are live stretches the cap further. Basic at $9.99/month raises it to 60 per minute and 1,000 per day.

What does the tennis WebSocket feed require?

The Ultra tier at $99.99 per month. There are two surfaces: the high-fan-out push feed, reached by minting a short-lived token from `GET /ws-token`, and the native `WS /ws` feed, which rides shared infrastructure with a concurrent-connection ceiling per key. Use the push feed for anything continuous. It speaks the Centrifugo client protocol rather than plain JSON frames, so reach for the `PushStream` client in the official Python and JavaScript SDKs — it mints the token, subscribes, answers heartbeats and mints a fresh token on every reconnect, which is the part hand-rolled clients get wrong.

Will I miss points if I poll instead of streaming?

Yes, and that is the real difference between the two transports. Polling returns the score at the moment you asked, so any point won and lost between two polls is never visible to you — you see the state it produced, not the event. For a scoreboard, a notification bot or a dashboard that is usually irrelevant. For anything reasoning about individual points, break points or the shape of a game, it is disqualifying, and that is exactly the boundary where the push feed earns its tier.

How do I read the score arrays correctly?

Every score array is player-major: the first element is player one and the second is player two, on both REST and WebSocket. So `"sets": [1, 0]` means player one leads by a set, `"games": [[6, 3], [4, 4]]` means the first set was won 6–4 and the second stands at 3–4, `"points": ["40", "30"]` is the game in progress, and `"server": 1` means player one is serving. This is the single detail that most often trips up a first integration, because the arrays look positional by match rather than by player.

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