# Statistics

> **Plan availability varies**
>
> Statistics availability depends on your plan and the specific league. Some statistics types (e.g. expected goals, advanced player stats) are only available for select leagues and higher-tier plans.
>
> [Compare plans →](https://www.sportmonks.com/football-api/plans-pricing) [Data features per league →](https://docs.sportmonks.com/v3/tutorials-and-guides/tutorials/data-features-per-league)

**Quick summary:** This tutorial explains how statistics work in API v3, introduces the type-based system that underpins all stats data, and directs you to the appropriate sub-tutorial for fixture, team, player, and season-level statistics.

**What you'll learn:**

* How the statistics type system works and why it matters
* Which statistics endpoints to use for different scopes (fixture, team, player, season)
* How to retrieve and resolve statistics types efficiently
* Best practices for working with stats data at scale

**Time to complete:** 15 minutes (overview) + time per sub-tutorial **Skill level:** Intermediate **Prerequisites:** [Authentication](https://docs.sportmonks.com/v3/welcome/authentication), [Includes](https://docs.sportmonks.com/v3/tutorials-and-guides/tutorials/includes), [Filter and Select Fields](https://docs.sportmonks.com/v3/tutorials-and-guides/tutorials/filter-and-select-fields), [Fixtures](https://docs.sportmonks.com/v3/tutorials-and-guides/tutorials/livescores-and-fixtures/fixtures)

### 1. When to use this feature

Statistics are one of the most requested data types in the API. They cover a wide range from basic match metrics (shots, corners, possession) to advanced per-player data (expected goals, progressive passes, pressures). Statistics in API v3 are scoped to four entities: fixtures, teams, players, and seasons.

#### Real-world use cases

1. **Use case 1: Match stats overlay** You're building a live match page and want to show possession, shots on target, and fouls side-by-side. Fixture statistics via `include=statistics.type` on a fixture endpoint gives you this in one request.
2. **Use case 2: Player performance profile** You want a page per player showing their goals, assists, and expected goals across a season. Player statistics endpoints return per-player seasonal aggregates.
3. **Use case 3: Team form analysis** You want to show a team's average shots per game over the last five matches. Team statistics endpoints return season-level aggregates per team.
4. **Use case 4: Season comparison dashboard** You want to compare aggregate stats across two seasons for the same team. Season statistics endpoints provide the per-season rollups.

#### When NOT to use this feature

1. **Simple standings or points data:** Use the [Standings](https://docs.sportmonks.com/v3/tutorials-and-guides/tutorials/standings) endpoints, which are optimised for table displays.
2. **Goal events (scorers, minute of goal):** Use `include=events` on a fixture, not statistics. Events cover discrete in-match incidents; statistics cover accumulated metrics.

### 2. Understanding statistics types

Before working with statistics in API v3, you need to understand the **type system**. This is the most important concept in this tutorial.

Every statistic is identified by a `type_id` rather than a named field. For example, there is no `shots_on_target` key in the response, instead you get an array of stat objects, each with a `type_id` and a `value`.

```json
{
  "data": [
    {
      "id": 99001,
      "fixture_id": 18535517,
      "type_id": 41,
      "participant_id": 53,
      "data": {
        "value": 7
      }
    }
  ]
}
```

To know that `type_id: 41` means "Shots Total", you need the types reference data. See the [Statistics Types tutorial](https://docs.sportmonks.com/v3/tutorials-and-guides/tutorials/statistics/statistics-types) for the full list.

#### The right way to work with types

Fetch all types **once** from the Types endpoint and store them in your database or application cache. Never include `.type` on large or frequently called endpoints.

```javascript
// Step 1: fetch types once at startup and build a lookup map
async function buildTypeMap(apiToken) {
  const r = await fetch(
    `https://api.sportmonks.com/v3/core/types?api_token=${apiToken}`
  );
  const data = await r.json();
  return new Map(data.data.map(t => [t.id, t.name]));
}

// Step 2: use the map to resolve stats without including .type
const typeMap = await buildTypeMap('YOUR_API_TOKEN');

function resolveStats(statistics) {
  return statistics.map(stat => ({
    name: typeMap.get(stat.type_id) || `Type ${stat.type_id}`,
    value: stat.data?.value
  }));
}
```

{% hint style="warning" %}
Including `.type` on statistics is allowed but not recommended in production. It triggers a type join for every stat on every entity in the response, use it for exploration and testing only.
{% endhint %}

### 3. Choosing the right statistics endpoint

Statistics are available on multiple entities. Choose based on what you're building:

| What you want                    | Entity  | Where to get it                          | Sub-tutorial                                                                                                      |
| -------------------------------- | ------- | ---------------------------------------- | ----------------------------------------------------------------------------------------------------------------- |
| Stats for a single match         | Fixture | `include=statistics` on fixture endpoint | [Fixture Statistics](https://docs.sportmonks.com/v3/tutorials-and-guides/tutorials/statistics/fixture-statistics) |
| Season aggregates per team       | Team    | Team statistics endpoint                 | [Team Statistics](https://docs.sportmonks.com/v3/tutorials-and-guides/tutorials/statistics/team-statistics)       |
| Per-player stats across a season | Player  | Player statistics endpoint               | [Player Statistics](https://docs.sportmonks.com/v3/tutorials-and-guides/tutorials/statistics/players-statistics)  |
| Season-level rollups             | Season  | Season statistics endpoint               | [Season Statistics](https://docs.sportmonks.com/v3/tutorials-and-guides/tutorials/statistics/season-statistics)   |

Each is covered in a dedicated sub-tutorial. This page provides the foundation concepts that apply to all of them.

### 4. How to retrieve statistics

#### Method A: Via fixture includes (most common)

The simplest way to get match statistics is to include them directly on a fixture request.

```http
https://api.sportmonks.com/v3/football/fixtures/18535517
?api_token=YOUR_API_TOKEN&include=statistics
```

{% tabs %}
{% tab title="Javascript" %}

```javascript
const apiToken = 'YOUR_API_TOKEN'

async function getFixtureWithStats(fixtureId) {
  const query = new URLSearchParams({
    api_token: apiToken,
    include: 'statistics;participants'
  });
  const r = await fetch(
    `https://api.sportmonks.com/v3/football/fixtures/${fixtureId}?${query}`
  );
  if (!r.ok) throw new Error(`HTTP ${r.status}`);
  return r.json();
}
```

{% endtab %}

{% tab title="Python" %}

```python
import requests

def get_fixture_with_stats(fixture_id, api_token):
    r = requests.get(
        f'https://api.sportmonks.com/v3/football/fixtures/{fixture_id}',
        params={
            'api_token': api_token,
            'include': 'statistics;participants'
        },
        timeout=30
    )
    r.raise_for_status()
    return r.json()
```

{% endtab %}

{% tab title="PHP" %}

```php
<?php
function getFixtureWithStats($fixtureId, $apiToken) {
    $params = [
        'api_token' => $apiToken,
        'include'   => 'statistics;participants'
    ];
    $url = "https://api.sportmonks.com/v3/football/fixtures/{$fixtureId}?" . http_build_query($params);
    $response = file_get_contents($url);
    return json_decode($response, true);
}

$data = getFixtureWithStats(18535517, 'YOUR_API_TOKEN');
print_r($data);
?>
```

{% endtab %}
{% endtabs %}

#### Method B: Via dedicated statistics endpoints

For team-level, player-level, or season-level aggregates, use the dedicated statistics endpoints. These are covered in full in the sub-tutorials linked above.

**Team statistics example:**

```http
https://api.sportmonks.com/v3/football/statistics/seasons/teams/{team_id}
?api_token=YOUR_API_TOKEN
```

**Player statistics example:**

```http
https://api.sportmonks.com/v3/football/statistics/seasons/players/{player_id}
?api_token=YOUR_API_TOKEN
```

#### Step-by-step: Fixture statistics with type resolution

```javascript
class FootballAPI {
  constructor(apiToken) {
    this.token = apiToken;
    this.baseURL = 'https://api.sportmonks.com/v3/football';
    this.typeMap = null;
  }

  async initTypes() {
    const r = await fetch(
      `https://api.sportmonks.com/v3/core/types?api_token=${this.token}`
    );
    const data = await r.json();
    this.typeMap = new Map(data.data.map(t => [t.id, t.name]));
  }

  async getFixtureStats(fixtureId) {
    if (!this.typeMap) await this.initTypes();

    const query = new URLSearchParams({
      api_token: this.token,
      include: 'statistics;participants'
    });
    const r = await fetch(`${this.baseURL}/fixtures/${fixtureId}?${query}`);
    if (!r.ok) throw new Error(`HTTP ${r.status}`);
    const data = await r.json();

    const stats = (data.data.statistics || []).map(stat => ({
      name: this.typeMap.get(stat.type_id) || `Type ${stat.type_id}`,
      value: stat.data?.value,
      participantId: stat.participant_id
    }));

    return { fixture: data.data, stats };
  }
}

// Usage
const api = new FootballAPI('YOUR_API_TOKEN');
const { fixture, stats } = await api.getFixtureStats(18535517);

stats.forEach(s => console.log(`${s.name}: ${s.value}`));
```

### 5. Working with the data

#### Understanding the response structure

Statistics returned via fixture include:

```json
{
  "data": {
    "id": 18535517,
    "name": "Celtic vs Rangers",
    "statistics": [
      {
        "id": 99001,
        "fixture_id": 18535517,
        "type_id": 41,
        "participant_id": 53,
        "data": {
          "value": 7
        }
      },
      {
        "id": 99002,
        "fixture_id": 18535517,
        "type_id": 41,
        "participant_id": 62,
        "data": {
          "value": 4
        }
      }
    ]
  }
}
```

**Key fields:**

| Field            | Type          | Description                                          |
| ---------------- | ------------- | ---------------------------------------------------- |
| `type_id`        | integer       | Identifies the stat (resolve via the types endpoint) |
| `participant_id` | integer       | Which team or player this stat belongs to            |
| `data.value`     | number/string | The stat value                                       |
| `fixture_id`     | integer       | The fixture this stat is associated with             |

#### Processing the data

**Split stats by team and display side-by-side:**

```javascript
function buildMatchStatsDisplay(stats, homeTeamId, awayTeamId, typeMap) {
  const home = {};
  const away = {};

  for (const stat of stats) {
    const name = typeMap.get(stat.type_id) || `Type ${stat.type_id}`;
    if (stat.participant_id === homeTeamId) {
      home[name] = stat.data?.value;
    } else if (stat.participant_id === awayTeamId) {
      away[name] = stat.data?.value;
    }
  }

  return { home, away };
}
```

**Find a specific stat by type ID:**

```javascript
function getStat(statistics, typeId, participantId) {
  return statistics.find(
    s => s.type_id === typeId && s.participant_id === participantId
  )?.data?.value ?? null;
}

// type_id 45 = Ball Possession
const homePossession = getStat(stats, 45, homeTeamId);
```

### 6. Common pitfalls

#### Pitfall 1: Including `.type` in production

This is the most common performance mistake when working with statistics.

```javascript
// ❌ Wrong: triggers a type join on every stat, every entity
const data = await api.getFixtures({ include: 'statistics.type' });

// ✅ Correct: resolve types locally via a cached map
const typeMap = await buildTypeMap(apiToken);
const data = await api.getFixtures({ include: 'statistics' });
// resolve: typeMap.get(stat.type_id)
```

#### Pitfall 2: Assuming all type IDs are always present

Type IDs are consistent across the API, but not all types are available for all leagues or all stat scopes. A `type_id` present in one fixture may not appear in another.

```javascript
// ❌ Assumption: a stat will always be present
const xg = stats.find(s => s.type_id === 117).data.value;

// ✅ Safe access
const xgEntry = stats.find(s => s.type_id === 117);
const xg = xgEntry?.data?.value ?? 'N/A';
```

#### Pitfall 3: Using statistics instead of events for goal data

Statistics store aggregated totals (e.g. "5 goals scored"). For who scored, when they scored, and which type of goal, use `include=events` on the fixture.

```javascript
// ❌ Wrong: statistics won't tell you who scored
const goals = stats.filter(s => s.type_id === 52);

// ✅ Correct: events give you goal details
const fixture = await api.getFixture(id, { include: 'events' });
const goals = fixture.data.events.filter(e => e.type_id === 14);
```

#### Pitfall 4: Fetching statistics independently when fixture includes cover it

If you only need stats for one fixture, fetching them via the fixture's `include` parameter is more efficient than a separate statistics endpoint call.

```javascript
// ❌ Two requests when one will do
const fixture = await api.getFixture(id);
const stats = await api.getFixtureStatistics(id);

// ✅ One request
const fixture = await api.getFixture(id, { include: 'statistics;participants' });
```

### 7. Advanced usage

#### Advanced technique 1: Building a stats comparison dashboard

Fetch stats for multiple fixtures in parallel and aggregate them per team.

```javascript
async function getTeamStatsOverMatches(fixtureIds, teamId, targetTypeId) {
  const requests = fixtureIds.map(id => api.getFixtureStats(id));
  const results = await Promise.all(requests);

  return results.map((result, i) => ({
    fixture: fixtureIds[i],
    value: getStat(result.stats, targetTypeId, teamId)
  }));
}

// Celtic's shots total across 5 matches (type_id 41 = Shots Total)
const data = await getTeamStatsOverMatches(
  [18535517, 18535518, 18535519, 18535520, 18535521],
  53,
  41
);
```

#### Advanced technique 2: Preloading types at app start

Make types available globally so any component can resolve them without additional API calls.

```javascript
let globalTypeMap = null;

async function initTypes(apiToken) {
  const r = await fetch(
    `https://api.sportmonks.com/v3/core/types?api_token=${apiToken}`
  );
  const data = await r.json();
  globalTypeMap = new Map(data.data.map(t => [t.id, { name: t.name, code: t.code }]));
}

function getTypeName(typeId) {
  return globalTypeMap?.get(typeId)?.name ?? `Unknown (${typeId})`;
}
```

### 8. Common errors

#### 401 Unauthorised

**Cause:** Missing or invalid `api_token`. **Fix:** Confirm the token is appended to every request. Retrieve it from [MySportmonks](https://my.sportmonks.com/).

#### Empty statistics array

**Cause:** Statistics are not available for this fixture or league. Not all leagues have full statistics coverage. **Fix:** Check [Data Features per League](https://docs.sportmonks.com/v3/tutorials-and-guides/tutorials/data-features-per-league) to confirm coverage.

#### 429 Too Many Requests

**Cause:** Statistics requests with `.type` includes are expensive and can hit rate limits quickly. **Fix:** Switch to the type-map pattern and remove `.type` from your includes. See the [Rate Limiting guide](https://docs.sportmonks.com/v3/api/rate-limit).

### See also

**Prerequisites**

* [Authentication](https://docs.sportmonks.com/v3/welcome/authentication) : Setting up API access
* [Includes](https://docs.sportmonks.com/v3/tutorials-and-guides/tutorials/includes) : Attaching stats to fixture responses
* [Fixtures](https://docs.sportmonks.com/v3/tutorials-and-guides/tutorials/livescores-and-fixtures/fixtures) : The most common way to access fixture-level stats

**Statistics by scope**

* [Statistics Types](https://docs.sportmonks.com/v3/tutorials-and-guides/tutorials/statistics/statistics-types) : Full reference of type IDs and names
* [Fixture Statistics](https://docs.sportmonks.com/v3/tutorials-and-guides/tutorials/statistics/fixture-statistics) : Per-match stats
* [Team Statistics](https://docs.sportmonks.com/v3/tutorials-and-guides/tutorials/statistics/team-statistics) : Season-level aggregates per team
* [Player Statistics](https://docs.sportmonks.com/v3/tutorials-and-guides/tutorials/statistics/players-statistics) : Per-player stats across a season
* [Season Statistics](https://docs.sportmonks.com/v3/tutorials-and-guides/tutorials/statistics/season-statistics) : Season-level rollups

**Related data**

* [Lineups and Formations](https://docs.sportmonks.com/v3/tutorials-and-guides/tutorials/lineups-and-formations) : Combine with player stats for a full match analysis view
* [Expected Goals (xG)](https://docs.sportmonks.com/v3/tutorials-and-guides/tutorials/expected) : Advanced expected stats alongside actual statistics
* [Standings](https://docs.sportmonks.com/v3/tutorials-and-guides/tutorials/standings) : Contextualise team stats within the league table

### 10. FAQ

**Q: Where do I find the full list of type IDs?**

See the [Statistics Types tutorial](https://docs.sportmonks.com/v3/tutorials-and-guides/tutorials/statistics/statistics-types) or fetch them directly from `GET /v3/core/types`.

**Q: Why are statistics for some leagues empty?**

Not every league has full statistics coverage. Advanced stats like expected goals are only available for select competitions. Check [Data Features per League](https://docs.sportmonks.com/v3/tutorials-and-guides/tutorials/data-features-per-league) for a league-by-league breakdown.

**Q: Can I get live in-match statistics?**

Yes. Use `include=statistics` on the livescores endpoint to get real-time stats during a match. These update as events are processed.

**Q: What's the difference between statistics and events?**

Events represent discrete in-match incidents (goal at minute 37, yellow card at minute 52, substitution). Statistics represent accumulated totals (7 shots, 55% possession). Use events for scorers and incidents; use statistics for metrics.

**Q: Are player statistics the same as player events?**

No. Player events (via `include=events`) show what a player did in a specific match. Player statistics (via the player statistics endpoint) show aggregate totals for a player over a season or date range.

**Q: Can I compare statistics across multiple fixtures?**

Yes. Fetch statistics for each fixture and aggregate them. Use `Promise.all` to fetch in parallel and see the advanced usage section above.

#### Best practices summary

✅ DO:

* Fetch types once from `/v3/core/types` and cache them in a Map at startup
* Use `include=statistics` on fixture requests for match-level stats
* Use the dedicated statistics endpoints for team and player seasonal aggregates
* Check data feature coverage per league before building stats-heavy features

❌ DON'T:

* Include `.type` on statistics in production
* Assume a type ID will always be present in a response
* Use statistics to get goal scorer data and use events instead
* Make separate requests for stats when fixture includes cover the same data
