Get live match timeline
Get the events of a match while it is being played: goals, cards, substitutions, shots and more, in order. Poll it during the match to power a live feed or goal alerts. The response has the same shape as the match timeline.
https://api.soccer-data.co/v1/matches/{match_id}/live-timeline
Live
- Only works while the match is in play. Otherwise it returns 409, so switch to the match timeline endpoint after full-time.
- Find matches in play with List matches and status=live.
- There are no webhooks or streams. Poll this endpoint to pick up new events.
- Responses are normally at most 5 minutes old on Free and 15 seconds old on Pro and Business. Use meta.last_updated to see how fresh the events are. If a fresh answer can't be fetched, you may get an older copy marked X-Cache: STALE instead: see When an answer is stale.
- Penalties are penalty_scored and own goals are own_goal, so send event_type=goal,penalty_scored,own_goal to catch every goal. Shoot-out kicks are penalty_scored with period set to penalties.
- An unknown event_type returns 400.
The match's id, like match_…. Find one with the ID guide.
Comma-separated list of event types to keep. Leave it out to get every event.
goalown_goalshot_on_targetshot_off_targetshot_blockedshot_savedshot_woodworkyellow_cardred_cardyellow_red_cardfouloffsidecorner_kicksubstitutionpenalty_awardedpenalty_scoredpenalty_missedpenalty_savedvarperiod_startperiod_endadded_timeComma-separated list of periods to keep.
first_halfsecond_halfextra_time_first_halfextra_time_second_halfpenaltiesOnly return events for this team.
The match's id.
full when we have events for this match, none when we don't.
Events so far, in the order they happened. Read the list as it comes; it is already sorted.
Position of the event in the match, starting at 1.
Minute within normal time for that period (up to 45 in the first half, up to 90 in the second). Stoppage minutes go in extra_time.
Stoppage minutes on top of minute. 0 for events in normal time.
first_half, second_half, extra_time_first_half, extra_time_second_half or penalties.
What happened. One of goal, own_goal, shot_on_target, shot_off_target, shot_blocked, shot_saved, shot_woodwork (hit the post or bar), yellow_card, red_card, yellow_red_card, foul, offside, corner_kick, substitution, penalty_awarded, penalty_scored, penalty_missed, penalty_saved, var, period_start, period_end or added_time. For own_goal, player is the one who put it in their own net and team is their side, so the goal counts for the other team.
The team involved.
Team's id. Null when only the name is known.
Team's name.
URL-friendly name, handy for building links.
The player involved. Same fields as team: id (null when only the name is known), name and slug.
Number of events returned, after filters.
Same as data.coverage: full or none.
Only present when coverage is none. match_not_started, no_events, or not_supported when we don't cover events for this competition.
When the events last changed.
Always live on this endpoint.
curl "https://api.soccer-data.co/v1/matches/match_9pd0ck6g2m9i/live-timeline" \ -H "X-Api-Key: $SOCCER_DATA_KEY"
const res = await fetch("https://api.soccer-data.co/v1/matches/match_9pd0ck6g2m9i/live-timeline", {
headers: { "X-Api-Key": process.env.SOCCER_DATA_KEY },
});
const { data } = await res.json();
import os, requests
res = requests.get(
"https://api.soccer-data.co/v1/matches/match_9pd0ck6g2m9i/live-timeline",
headers={"X-Api-Key": os.environ["SOCCER_DATA_KEY"]},
)
data = res.json()["data"]
$data = Http::withHeaders(['X-Api-Key' => env('SOCCER_DATA_KEY')])
->get('https://api.soccer-data.co/v1/matches/match_9pd0ck6g2m9i/live-timeline')
->json('data');
{
"data": {
"match_id": "match_9pd0ck6g2m9i",
"coverage": "full",
"events": [
{
"sequence": 3,
"minute": 45,
"extra_time": 3,
"period": "first_half",
"type": "yellow_card",
"team": {
"id": "team_4mimn5fmya2v",
"name": "Aston Villa",
"slug": "aston-villa-team_4mimn5fmya2v"
},
"player": {
"id": "player_85xcr2xm5vxo",
"name": "Ollie Watkins",
"slug": "ollie-watkins-player_85xcr2xm5vxo"
}
}
]
},
"meta": {
"total": 10,
"coverage": "full",
"last_updated": "2026-05-15T21:05:49.659Z",
"match_status": "live"
}
}
### GET /matches/{match_id}/live-timeline
Get live match timeline
Get the events of a match while it is being played: goals, cards, substitutions, shots and more, in order. Poll it during the match to power a live feed or goal alerts. The response has the same shape as the match timeline.
- Only works while the match is in play. Otherwise it returns 409, so switch to the match timeline endpoint after full-time.
- Find matches in play with List matches and status=live.
- There are no webhooks or streams. Poll this endpoint to pick up new events.
- Responses are normally at most 5 minutes old on Free and 15 seconds old on Pro and Business. Use meta.last_updated to see how fresh the events are. If a fresh answer can't be fetched, you may get an older copy marked X-Cache: STALE instead: see [When an answer is stale](/docs/concepts/rate-limits#when-an-answer-is-stale).
- Penalties are penalty_scored and own goals are own_goal, so send event_type=goal,penalty_scored,own_goal to catch every goal. Shoot-out kicks are penalty_scored with period set to penalties.
- An unknown event_type returns 400.
Query parameters:
- `event_type` (string, one of goal, own_goal, shot_on_target, shot_off_target, shot_blocked, shot_saved, shot_woodwork, yellow_card, red_card, yellow_red_card, foul, offside, corner_kick, substitution, penalty_awarded, penalty_scored, penalty_missed, penalty_saved, var, period_start, period_end, added_time): Comma-separated list of event types to keep. Leave it out to get every event.
- `period` (string, one of first_half, second_half, extra_time_first_half, extra_time_second_half, penalties): Comma-separated list of periods to keep.
- `team_id` (string): Only return events for this team.
Response fields:
- `data.match_id` (string): The match's id.
- `data.coverage` (string): full when we have events for this match, none when we don't.
- `data.events[]` (array): Events so far, in the order they happened. Read the list as it comes; it is already sorted.
- `data.events[].sequence` (integer): Position of the event in the match, starting at 1.
- `data.events[].minute` (integer): Minute within normal time for that period (up to 45 in the first half, up to 90 in the second). Stoppage minutes go in extra_time.
- `data.events[].extra_time` (integer): Stoppage minutes on top of minute. 0 for events in normal time.
- `data.events[].period` (string): first_half, second_half, extra_time_first_half, extra_time_second_half or penalties.
- `data.events[].type` (string): What happened. One of goal, own_goal, shot_on_target, shot_off_target, shot_blocked, shot_saved, shot_woodwork (hit the post or bar), yellow_card, red_card, yellow_red_card, foul, offside, corner_kick, substitution, penalty_awarded, penalty_scored, penalty_missed, penalty_saved, var, period_start, period_end or added_time. For own_goal, player is the one who put it in their own net and team is their side, so the goal counts for the other team.
- `data.events[].team` (object|null): The team involved.
- `data.events[].team.id` (string|null): Team's id. Null when only the name is known.
- `data.events[].team.name` (string): Team's name.
- `data.events[].team.slug` (string|null): URL-friendly name, handy for building links.
- `data.events[].player` (object|null): The player involved. Same fields as team: id (null when only the name is known), name and slug.
- `meta.total` (integer): Number of events returned, after filters.
- `meta.coverage` (string): Same as data.coverage: full or none.
- `meta.reason` (string): Only present when coverage is none. match_not_started, no_events, or not_supported when we don't cover events for this competition.
- `meta.last_updated` (string|null): When the events last changed.
- `meta.match_status` (string): Always live on this endpoint.
Example: curl "https://api.soccer-data.co/v1/matches/match_9pd0ck6g2m9i/live-timeline" -H "X-Api-Key: $SOCCER_DATA_KEY"