Live Tennis API DocsGet a free key
Explore the documentation

Live Tennis API · docs · version 1.13.54

Tennis scores for on-air graphics

How do you put live tennis scores into a broadcast graphics template? GET /broadcast/match/{matchId} returns ONE flat object for one match and GET /broadcast/live the same object for every live match in a single call. Both are ULTRA. A graphics template binds each field to a fixed path, so three things are contractual: the object is flat, every key is present on every read (null, never missing), and the score is already in display form — p1_points reads "40" or "AD" and set_line is one string. Built for 1 Hz: every response carries a strong ETag, so If-None-Match makes an unchanged state a 304 with no body. Detect a new state on sequence, never on served_at, which changes on every read.

2 endpoints on this page. Base URL https://api.livetennisapi.com/api/public/v1; authenticate with the X-API-Key header. Plans involved: ULTRA. A free key needs no card.

The endpoints

Each one below lists its plan tier, every parameter, the response shape and an example. They are the same entries as in the full reference, which carries all 48 operations of the API on one page.

GET /broadcast/match/{matchId}

One flat object for on-air graphics (ULTRA)

Plan required: ULTRA · operationId: getBroadcastMatch

Added 2026-09-24, from a broadcaster's request. ONE flat JSON object for one match, built so a graphics template can bind each field to a fixed path. Three properties make it different from GET /matches/{matchId}, and each exists because a graphics pipeline breaks without it. The object is FLAT — no nested score object, no arrays of arrays. Every key is PRESENT on every read: a value we do not hold is null, never missing, because a missing key breaks a template binding. And the score is already in DISPLAY form — p1_points reads "40" or "AD", set_line is one string such as "5-7 2-0", and each set has its own column — so nothing has to be formatted downstream. It is a PROJECTION of what the public API already publishes: the match object, its embedded score and the model probability, plus the break-, set- and match-point flags derived by the same classifiers the scoring pipeline uses. Nothing here is a new fact and nothing is guessed. BUILT FOR 1 Hz. Every response carries a strong ETag and Cache-Control: no-cache, max-age=1; send If-None-Match and an unchanged state costs a 304 with no body. ULTRA's burst window is 600 requests a minute, so ten single-match pollers at 1 Hz fit on one key. Detect a new state on sequence, not on served_at, which changes on every read. The key may be sent as Authorization: Bearer <key>, as X-API-Key, or as ?token=<key> for tools that cannot set a header.

Parameters

GET /broadcast/match/{matchId} — parameters
NameInTypeRequiredNotes
matchIdpathintegeryesThe match id. Every match route shares ONE id space, so the same value works everywhere matchId appears. Get it from any match list, where it is the id field of each match object: GET /matches?status=live (in progress), GET /matches?status=upcoming or GET /fixtures (scheduled), GET /matches?status=completed or GET /history/matches (finished). Ids are stable for the life of a match, so one captured before it starts still resolves after it finishes.

Responses

GET /broadcast/match/{matchId} — responses
StatusMeaning
200The broadcast object for this match
304Unchanged since the ETag you sent
401Missing, unknown, or disabled credentials
403Your tier doesn't unlock this endpoint
404No such resource, or no data yet
429Rate limit exceeded (Retry-After header present). Three body shapes, told apart by error and scope: the per-MINUTE limit (rate_limited, with upgrade_url, tier and price naming the next tier up); the per-DAY quota (rate_limited with scope: "day", limit_per_day, and resets_at — the absolute ISO instant the daily window resets); and abuse_throttled with retry_at_epoch — a 24-hour block applied to clients that keep hammering far past their cap, which a well-behaved retry loop never sees. Fix the loop rather than retrying through it.

Response fields

GET /broadcast/match/{matchId} — response fields
FieldTypeDescription
match_idinteger
statusstringlive, completed, upcoming
event_statusstring or nullThe terminal badge where one is stated (Finished, Retired, Walk Over, Interrupted); null on a normal live row.
outcomestring or null
winnerinteger or null1 or 2, null until a result is established.
tourstring or null
tournamentstring or null
roundstring or null
surfacestring or null
best_ofinteger or null
is_doublesboolean
p1_idinteger or null
p1_namestring or null
p1_shortstring or nullDisplay short form, "J. Sinner". A doubles team keeps each side's last token.
p1_countrystring or nullThree-letter code.
p1_rankinginteger or null
p2_idinteger or null
p2_namestring or null
p2_shortstring or null
p2_countrystring or null
p2_rankinginteger or null
p1_setsinteger or null
p2_setsinteger or null
p1_gamesinteger or nullGames in the set in play.
p2_gamesinteger or null
p1_pointsstring or nullAs printed: "0", "15", "30", "40", "AD", or the tiebreak count.
p2_pointsstring or null
p1_set1integer or nullPer-set columns. A set not yet played is null on both sides.
p2_set1integer or null
p1_set2integer or null
p2_set2integer or null
p1_set3integer or null
p2_set3integer or null
p1_set4integer or null
p2_set4integer or null
p1_set5integer or null
p2_set5integer or null
set_linestring or nullThe completed and in-play sets as one string, "5-7 2-0".
current_setinteger or null
is_tiebreakboolean
match_tiebreakbooleanTrue while a deciding match tiebreak is in play.
serverinteger or null1 or 2.
p1_servingboolean or null
p2_servingboolean or null
break_point_forinteger or null1, 2 or null. Cue an animation on the transition to non-null.
set_point_forinteger or null
match_point_forinteger or null
p1_win_pctinteger or nullThe model probability as a whole percent. Null where no model figure was computed for this state — never guessed.
p2_win_pctinteger or null
sequenceinteger or nullThe per-match accept counter. THIS is how a poller detects a new state.
updated_atstring or null
age_secondsinteger or nullSeconds since the score CONTENT last changed.
staleboolean or nullA live match whose content has frozen past the threshold.
served_atstringWhen this response was built. Changes on every read — do not use it to detect a new state.

Example

curl https://api.livetennisapi.com/api/public/v1/broadcast/match/18953 \
  -H "Authorization: Bearer twjp_..."

GET /broadcast/live

The broadcast object for every live match (ULTRA)

Plan required: ULTRA · operationId: getBroadcastLive

The same object as GET /broadcast/match/{matchId}, for every actively-live match, in one call. A ticker or a multi-match wall polls this instead of one URL per match, and it stays ONE request however many matches are live. At most 200 objects. Same 1 Hz contract, same ETag and Cache-Control, same display-form fields, same every-key-present rule. An empty data means nothing is live right now and is the ordinary overnight answer, not a fault.

Parameters

GET /broadcast/live — parameters
NameInTypeRequiredNotes
tourquerystringnoRestrict to one tour, same vocabulary as ?tour= elsewhere.

Responses

GET /broadcast/live — responses
StatusMeaning
200Every live match as a broadcast object
304Unchanged since the ETag you sent
401Missing, unknown, or disabled credentials
403Your tier doesn't unlock this endpoint
429Rate limit exceeded (Retry-After header present). Three body shapes, told apart by error and scope: the per-MINUTE limit (rate_limited, with upgrade_url, tier and price naming the next tier up); the per-DAY quota (rate_limited with scope: "day", limit_per_day, and resets_at — the absolute ISO instant the daily window resets); and abuse_throttled with retry_at_epoch — a 24-hour block applied to clients that keep hammering far past their cap, which a well-behaved retry loop never sees. Fix the loop rather than retrying through it.

Response fields

GET /broadcast/live — response fields
FieldTypeDescription
dataarray of object
countinteger
served_atstring

Example

curl https://api.livetennisapi.com/api/public/v1/broadcast/live \
  -H "Authorization: Bearer twjp_..."