# 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


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://docs.sportmonks.com/v3/tutorials-and-guides/tutorials/statistics.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
