Tennis scoring has more layers than most sports โ sets, games within a set, and points within a game, plus tiebreaks that need their own score. This guide covers fetching that full picture with Live Tennis API, and embedding the ready-made match tracker widget so you don't have to build a live scoreboard UI from scratch.
Two Endpoints for Two Different Views
/matchesโ a list of matches for a given day, with set and game scores, good for a schedule/results page/match/scoresโ the live detail for one specific match, including the current game's point score and who's serving
Fetching Today's Matches
import requests
response = requests.get(
'https://live-tennis-api.com/api/v1/matches',
params={'api_key': 'YOUR_KEY', 'date': '2026-07-13', 'lang': 'en'}
).json()
for match in response['matches']:
p1 = match['player1']['name']
p2 = match['player2']['name']
sets = match['score']['sets_won']
print(f"{p1} {sets[0]}-{sets[1]} {p2} ({match['status']})")
Understanding the Score Structure
Each match's score object breaks down as follows:
sets_wonโ[player1_sets, player2_sets], the overall match tallys1,s2,s3... โ games won per set, e.g.[7, 6]for a 7-6 first sets1_tb,s2_tb... โ tiebreak point score, only present if that set went to a tiebreak
function formatSetScore(match) {
const sets = ['s1', 's2', 's3', 's4', 's5'];
return sets
.filter(key => match.score[key])
.map(key => {
const [g1, g2] = match.score[key];
const tb = match.score[`${key}_tb`];
return tb ? `${g1}-${g2}(${tb[0]}-${tb[1]})` : `${g1}-${g2}`;
})
.join(', ');
}
// "7-6(7-1), 7-5" for a match with a first-set tiebreak
Fetching Live Detail for One Match
Once a user taps into a specific match, /match/scores gives you the live point-by-point state:
const res = await fetch(
'https://live-tennis-api.com/api/v1/match/scores' +
`?api_key=YOUR_KEY&match_id=${matchId}`
);
const data = await res.json();
if (data.match.status === 'inprogress') {
const { player1, player2 } = data.score.current_game;
const server = data.score.serving === 'player1' ? data.match.player1.name : data.match.player2.name;
console.log(`${player1} - ${player2} (serving: ${server})`);
}
current_game and serving are only present while the match is actually in progress โ always check match.status before reading them.
Building a Live Score Display
function LiveScoreCard({ data }) {
const { match, score } = data;
const isLive = match.status === 'inprogress';
return (
<div className="score-card">
<div className="players">
<span>{match.player1.name}</span>
<span>{match.player2.name}</span>
</div>
<div className="sets">
{formatSetScore({ score })}
</div>
{isLive && (
<div className="current-game">
{score.current_game.player1} - {score.current_game.player2}
<span className="serving-indicator">
{score.serving === 'player1' ? 'โ ' : ''}{match.player1.name}
{score.serving === 'player2' ? ' โ' : ''}{match.player2.name}
</span>
</div>
)}
</div>
);
}
Embedding the Match Tracker Widget (Without Exposing Your API Key)
Rather than building your own live scoreboard UI, /match/scores returns a ready-to-use tracker_url pointing to a fully rendered HTML widget โ and critically, that URL requires no API key to load, so it's safe to embed directly in a public page.
The correct pattern is a two-step fetch: get tracker_url server-side (where your key is safe), then use that URL client-side:
# Server-side: fetch with your API key, extract the public tracker_url
import requests
response = requests.get(
'https://live-tennis-api.com/api/v1/match/scores',
params={'api_key': 'YOUR_KEY', 'match_id': match_id}
).json()
tracker_url = response['tracker_url']
# Pass tracker_url to your frontend template โ it needs no key
<!-- Client-side: embed the key-free tracker_url directly -->
<iframe
src="{{ tracker_url }}"
width="800"
height="600"
frameborder="0"
></iframe>
This matters because embedding a URL that includes api_key=YOUR_KEY directly in client-side HTML would expose your key in the page source โ the tracker_url field exists specifically to avoid that.
Frequently Asked Questions
Does current_game show numeric points like 15 or 30?
Yes, values are "0", "15", "30", "40", or "A" (advantage) as strings, matching standard tennis scoring โ not raw point counts.
Can I call /match/tracker directly instead of going through /match/scores first?
Yes, /match/tracker works with just a match_id and no API key at all โ but fetching tracker_url from /match/scores first is the documented, recommended pattern since it keeps your key handling server-side by default.
How often should I poll /match/scores for a live match?
The endpoint's cache is 30 seconds for live matches, so polling more frequently than that won't get you fresher data โ 30 seconds is a sensible polling interval to match.
What does status_detail like "3rd set" tell me that status doesn't?
The status field is a fixed enum (inprogress, finished, etc.), while status_detail gives a human-readable phase within that status โ useful for displaying directly in a UI without building your own "which set are we in" logic.