Guides / Handling live matches

Handling live matches

A live match moves through a small set of statuses. Knowing them lets you poll only when it matters and react the moment a score changes.

Samples use the small get() helper from Build a live score app.

Match lifecycle

Every fixture carries a status.short code. A normal match goes NS → LIVE → HT → LIVE → FT; a few never start.

Fixture status codes
StatusMeaningWhat to do
NSNot started.Show the kick-off time. No need to poll yet.
LIVEIn play; status.minute holds the match minute.Poll the live endpoint every 60 seconds.
HTHalf-time.Keep polling — play resumes shortly.
FTFull-time; the score is final.Stop polling this fixture and refresh standings.
PSTPostponed.Show it as postponed and look out for a new date.
CANCCancelled.Remove it from schedules.

When to poll

  • Before kick-off — load the schedule, then sleep until the first kick-off time.
  • In play — poll /football/fixtures/live every 60 seconds. One request returns every live fixture matching your filters, so never poll fixture by fixture.
  • After full time — stop polling and fetch the final result once.

60 seconds is the sweet spot

Data freshness is about 30 seconds. Polling faster than the recommended 60 seconds spends requests without showing your users anything newer.

A small scheduler that follows those rules:

const IN_PLAY = ["LIVE", "HT"];
// Milliseconds until the next poll, or null when every fixture is done
function nextPollDelay(fixtures, now = Date.now()) {
if (fixtures.some((f) => IN_PLAY.includes(f.status.short))) {
return 60 * 1000; // in play: every 60 seconds
}
const untilKickOff = fixtures
.filter((f) => f.status.short === "NS")
.map((f) => Date.parse(f.date) - now);
if (untilKickOff.length === 0) return null; // nothing left today
return Math.max(Math.min(...untilKickOff), 60 * 1000); // sleep until the next kick-off
}

Detecting changes

The API returns the current state of each match, not a list of events. To send goal alerts or update a timeline, compare each poll with the previous one:

const previous = new Map();
function diff(live) {
const events = [];
for (const f of live) {
const before = previous.get(f.id);
if (!before) {
events.push({ type: "kickoff", fixture: f });
} else {
for (const side of ["home", "away"]) {
if (f[side].score !== before[side].score) {
events.push({ type: "score", side, fixture: f });
}
}
if (f.status.short !== before.status.short) {
events.push({ type: "status", fixture: f });
}
}
previous.set(f.id, f);
}
return events;
}
  • Compare scores with !==, not >: a disallowed goal makes a score go down.
  • If your process restarts mid-match, seed previous from the first poll without emitting events, or every live match will look like a new kick-off.

When a match ends

A finished fixture drops out of /fixtures/live. When a fixture you were tracking disappears, fetch it once more to confirm its final score and status:

const liveIds = new Set(live.map((f) => f.id));
for (const [id, tracked] of previous) {
if (liveIds.has(id)) continue;
// No longer live: fetch it once more to confirm the final state
const { data } = await get("/football/fixtures", {
league_id: tracked.league.id,
date: tracked.date.slice(0, 10), // kick-off date, UTC
});
const final = data.find((f) => f.id === id);
if (final) onFinished(final); // FT, PST or CANC
previous.delete(id);
}

This is the moment to refresh anything that depends on results — standings, form, player statistics — and to invalidate their caches. See Caching.

Other sports

Every sport with live data has a live endpoint that works the same way. Only the fields that describe the match clock differ:

Live endpoints by sport
SportLive endpointClock fields
Football/football/fixtures/livestatus.minute
Basketball/basketball/livescoresstatus.period, status.clock
Tennis/tennis/livescoresstatus.set, status.game, sets
Cricket/cricket/livescoresruns, wickets, overs, target
Baseball/baseball/livescoresstatus.inning, status.half, status.outs
Hockey/hockey/livescoresstatus.period, status.clock
American Football/american-football/livescoresstatus.quarter, status.clock
Motorsport/motorsport/live-timinglap, positions, fastest_lap
Rugby/rugby/livescoresstatus.minute
Volleyball/volleyball/livescoresstatus.set, set_scores
Handball/handball/livescoresstatus.minute