> ## Documentation Index
> Fetch the complete documentation index at: https://www.thestatsapi.com/docs/llms.txt
> Use this file to discover all available pages before exploring further.

> ## Agent Instructions
> For the complete API reference (every endpoint, query parameter and response field), fetch https://api.thestatsapi.com/llms.txt. Only use endpoints, parameters and fields listed there.
> Base URL: https://api.thestatsapi.com/api. Auth header: Authorization: Bearer YOUR_API_KEY. Keep the key in server-side code only.

# Build live scores and in-play stats

> Find live matches, then poll live stats, the live timeline, live player stats and in-play odds. Covers polling frequency and what each endpoint returns after full time.

Use this page to show scores, stats and events while matches are being played.

<Tip>
  **Fastest way: ask your AI.** Set up your coding tool once with [Build with AI](/docs/build-with-ai), then paste:

  ```text Prompt wrap theme={null}
  Using TheStatsAPI (reference: https://api.thestatsapi.com/llms.txt), build a live scores page: a server route lists matches with status=live, and for a selected match polls /live-stats every 30 seconds until it stops being live. Keep the API key server-side.
  ```
</Tip>

## How live data works

There are no webhooks, WebSockets or push messages. You poll: ask for live matches, then call the live endpoints for the matches you show.

| You want | Call |
| - | - |
| Which matches are live, with the clock | `GET /football/matches?status=live` |
| Score and team stats | `GET /football/matches/{match_id}/live-stats` |
| Goals, cards, substitutions and other events | `GET /football/matches/{match_id}/live-timeline` |
| Per-player stats | `GET /football/matches/{match_id}/live-player-stats` |
| In-play odds | `GET /football/matches/{match_id}/odds/live` (see [Odds](/docs/guides/odds)) |

The JavaScript examples run on your server and read your key from the `THESTATSAPI_API_KEY` environment variable. The match IDs below were live when this page was written; use one that is live when you try it.

## 1. Find live matches

<CodeGroup>
  ```bash curl theme={null}
  curl "https://api.thestatsapi.com/api/football/matches?status=live" \
    -H "Authorization: Bearer YOUR_API_KEY"
  ```

  ```javascript JavaScript theme={null}
  const res = await fetch(
    "https://api.thestatsapi.com/api/football/matches?status=live",
    { headers: { Authorization: `Bearer ${process.env.THESTATSAPI_API_KEY}` } }
  );
  const { data } = await res.json();
  for (const m of data) {
    const minute = m.live?.elapsed_minutes ?? "-";
    console.log(`${minute}' ${m.home_team.name} ${m.score.home}-${m.score.away} ${m.away_team.name}`);
  }
  ```
</CodeGroup>

Response (one match, trimmed):

```json Response theme={null}
{
  "data": [
    {
      "id": "mt_899703080",
      "competition_id": "comp_8321",
      "status": "live",
      "utc_date": "2026-10-09T19:00:00.000Z",
      "home_team": { "id": "tm_3809", "name": "West Ham United" },
      "away_team": { "id": "tm_2949", "name": "Queens Park Rangers" },
      "score": { "home": 0, "away": 1 },
      "live": {
        "match_status": "first_half",
        "elapsed_minutes": 29,
        "period": "first_half",
        "updated_at": "2026-10-09T19:29:14.093Z"
      },
      "live_odds_available": true
    }
  ],
  "meta": { "page": 1, "per_page": 20, "total": 45, "total_pages": 3 }
}
```

`live` is `null` unless `status` is `live`. Just after kickoff, every field inside `live` can be `null` until the first clock update arrives. On a busy evening there can be dozens of live matches, so add `per_page=100`, or filter with `competition_id` or `team_id` to watch only the matches you care about.

## 2. Poll live stats

<CodeGroup>
  ```bash curl theme={null}
  curl "https://api.thestatsapi.com/api/football/matches/mt_899703080/live-stats" \
    -H "Authorization: Bearer YOUR_API_KEY"
  ```

  ```javascript JavaScript theme={null}
  const headers = { Authorization: `Bearer ${process.env.THESTATSAPI_API_KEY}` };
  const url = "https://api.thestatsapi.com/api/football/matches/mt_899703080/live-stats";

  async function poll() {
    const res = await fetch(url, { headers });
    if (res.status === 429) {
      const wait = Number(res.headers.get("Retry-After") ?? 60);
      return setTimeout(poll, wait * 1000);
    }
    if (!res.ok) return; // 409: the match isn't live (any more). Use /stats for final totals
    const { data } = await res.json();
    console.log(data.meta.elapsed_minutes, data.meta.home_goals, data.meta.away_goals);
    if (data.meta.match_status === "finished") return;
    setTimeout(poll, 30_000);
  }
  poll();
  ```
</CodeGroup>

Response (trimmed):

```json Response theme={null}
{
  "data": {
    "match_id": "mt_899703080",
    "meta": {
      "match_status": "first_half",
      "elapsed_minutes": 29,
      "home_goals": 0,
      "away_goals": 1,
      "period": "first_half"
    },
    "stats": {
      "ball_possession": { "all": { "home": 55, "away": 45 } },
      "total_shots": { "all": { "home": 1, "away": 2 } },
      "shots_on_target": { "all": { "home": 0, "away": 1 } },
      "expected_goals": { "all": { "home": 0.06, "away": 0.85 } }
    }
  }
}
```

Which stats appear depends on the match. Check that a key exists before you read it.

## 3. Poll the live timeline

Filter with `event_type` (e.g. `goal`, `penalty_scored`, `yellow_card`, `substitution`), `period` or `team_id`.

<Warning>
  Penalty goals are `penalty_scored` events, not `goal`. To get every goal, ask for both: `event_type=goal,penalty_scored`. Penalty shoot-out kicks are also `penalty_scored`, with `"period": "penalties"`, so leave those out when you count match goals.
</Warning>

<CodeGroup>
  ```bash curl theme={null}
  curl "https://api.thestatsapi.com/api/football/matches/mt_899703080/live-timeline?event_type=goal,penalty_scored,yellow_card" \
    -H "Authorization: Bearer YOUR_API_KEY"
  ```

  ```javascript JavaScript theme={null}
  const res = await fetch(
    "https://api.thestatsapi.com/api/football/matches/mt_899703080/live-timeline?event_type=goal,penalty_scored,yellow_card",
    { headers: { Authorization: `Bearer ${process.env.THESTATSAPI_API_KEY}` } }
  );
  const { data, meta } = await res.json();
  for (const e of data.events) console.log(`${e.minute}' ${e.type} ${e.player?.name ?? ""}`);
  ```
</CodeGroup>

Response (trimmed):

```json Response theme={null}
{
  "data": {
    "match_id": "mt_899703080",
    "coverage": "full",
    "events": [
      {
        "sequence": 1,
        "minute": 17,
        "extra_time": 0,
        "period": "first_half",
        "type": "yellow_card",
        "team": { "id": "tm_3809", "name": "West Ham United" },
        "player": { "id": "pl_30432621", "name": "Arne Engels" }
      },
      {
        "sequence": 2,
        "minute": 18,
        "extra_time": 0,
        "period": "first_half",
        "type": "penalty_scored",
        "team": { "id": "tm_2949", "name": "Queens Park Rangers" },
        "player": { "id": "pl_61568182", "name": "Nicolas Madsen" }
      }
    ]
  },
  "meta": {
    "total": 6,
    "coverage": "full",
    "last_updated": "2026-10-09T20:30:03.372Z",
    "match_status": "live"
  }
}
```

Stoppage time is kept separate: 45+3' comes back as `"minute": 45, "extra_time": 3`.

## 4. Live player stats

`/live-player-stats` returns one entry per player with rating, minutes, passing, shooting (including xG), duels and defending. Touches, fouls and cards are under `general`. Add `player_ids=pl_...,pl_...` to get only some players.

<CodeGroup>
  ```bash curl theme={null}
  curl "https://api.thestatsapi.com/api/football/matches/mt_899703080/live-player-stats" \
    -H "Authorization: Bearer YOUR_API_KEY"
  ```

  ```javascript JavaScript theme={null}
  const res = await fetch(
    "https://api.thestatsapi.com/api/football/matches/mt_899703080/live-player-stats",
    { headers: { Authorization: `Bearer ${process.env.THESTATSAPI_API_KEY}` } }
  );
  const { data, meta } = await res.json(); // meta.match_status === "live"
  ```
</CodeGroup>

One entry (trimmed):

```json Response theme={null}
{
  "player_id": "pl_45450148",
  "player_name": "Ronnie Edwards",
  "team_id": "tm_2949",
  "position": "M",
  "rating": 6.84,
  "started": true,
  "minutes_played": 25,
  "passing": { "total_passes": 7, "accurate_passes": 7, "key_passes": 0 },
  "shooting": { "goals": 0, "total_shots": 0, "expected_assists": 0.01 }
}
```

## How often to poll

* Every 30 seconds is enough. Live stats responses can be cached for up to 30 seconds, so polling faster doesn't get you fresher data.
* Only poll matches that are live and on screen. Every call counts against your [rate limits and quota](/docs/concepts/rate-limits).
* On `429`, wait the number of seconds in the `Retry-After` header before trying again.
* Refresh the live match list every minute or two to pick up kickoffs and full-time whistles.

## Before kickoff and after full time

| Endpoint | Before kickoff | While live | After full time |
| - | - | - | - |
| `/live-stats` | `409` | `200` | `200` with `meta.match_status: "finished"` for up to 5 minutes, then `409` |
| `/live-timeline`, `/live-player-stats` | `409` | `200` | `409` |
| `/odds/live` | `404` | `200` when `live_odds_available` is `true` | `404` ("Match is already completed") |
| `/stats`, `/player-stats`, `/timeline` | No data yet: `/stats` and `/player-stats` return `404`, `/timeline` returns an empty `events` list | `409` (code `MATCH_IS_LIVE` on `/stats`) | `200` once the stats are in |

The `409` for a match that isn't live looks like this:

```json theme={null}
{ "error": { "code": "CONFLICT", "message": "Match is not live", "status_code": 409 } }
```

When you get it after a match, switch to the post-match endpoints: [match stats](/docs/api-reference/matches/stats), [player stats](/docs/api-reference/matches/player-stats) and [timeline](/docs/api-reference/matches/timeline).

<Note>
  Lineups come before kickoff: [Get match lineups](/docs/api-reference/matches/lineups) returns a predicted XI, then the official team sheet once it's announced (about an hour before kickoff). Check `type` and `confirmed` to know which you have.
</Note>

## API reference

* [List matches](/docs/api-reference/matches/list) (`status=live`)
* [Get live match stats](/docs/api-reference/matches/live-stats)
* [Get live match timeline](/docs/api-reference/matches/live-timeline)
* [Get live match player stats](/docs/api-reference/matches/live-player-stats)
* [Get live match odds](/docs/api-reference/matches/live-odds)


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.