> For the complete documentation index, see [llms.txt](https://docs.sportmonks.com/v3/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.sportmonks.com/v3/tutorials-and-guides/tutorials/includes/match-facts.md).

# Match Facts

Match Facts are pre-built, ready-to-display insights compiled from historical and live data for a fixture - things like head-to-head records, scoring trends, win/loss streaks, and player-level standouts. Each fact can come with a ready-made `natural_language` sentence, so you don't have to write your own formatting logic to turn the numbers into something readable.

### When to use this

Match Facts are built for anywhere you'd otherwise have to calculate historical context yourself:

* **Match previews** - head-to-head records, recent form, and streaks going into a fixture
* **Live match context** - facts that update as a match progresses (via `havingLiveMatchFacts`)
* **Editorial and commentary tools** - the `natural_language` field gives you publishable sentences without building your own template logic
* **Betting and fantasy platforms** - streak and threshold data (over/under goals, cards) that would otherwise mean pulling and aggregating years of fixture history yourself

### How to retrieve the data

There are two ways to pull Match Facts, the same pattern as most other Sportmonks features: a dedicated endpoint, or an include on the fixture itself.

#### Option 1: the `matchfacts` include

```http
GET https://api.sportmonks.com/v3/football/fixtures/{fixture_id}
?api_token=YOUR_TOKEN&include=matchfacts
```

Best when you're already retrieving the fixture and want its facts alongside everything else in one call.

#### Option 2: the standalone endpoints

```http
GET https://api.sportmonks.com/v3/football/match-facts/{fixture_id}
?api_token=YOUR_TOKEN&include=type

GET https://api.sportmonks.com/v3/football/match-facts/leagues/{league_id}
?api_token=YOUR_TOKEN

GET https://api.sportmonks.com/v3/football/match-facts/between/{start_date}/{end_date}
?api_token=YOUR_TOKEN
```

Best for pre-fetching facts across many fixtures at once (a full matchday, or a whole league) without pulling full fixture objects for each one. Each Match Facts endpoint supports the `type`, `sport`, and `fixture` includes, up to three levels of nesting.

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

```javascript
const API_TOKEN = "YOUR_TOKEN";
const FIXTURE_ID = 19609131;

async function getMatchFacts(fixtureId) {
  const url = `https://api.sportmonks.com/v3/football/match-facts/${fixtureId}`;
  const params = new URLSearchParams({
    api_token: API_TOKEN,
    include: "type"
  });

  const response = await fetch(`${url}?${params}`);
  const data = await response.json();
  return data.data;
}
```

{% endtab %}

{% tab title="Python" %}

```python
import requests

API_TOKEN = "YOUR_TOKEN"
FIXTURE_ID = 19609131

def get_match_facts(fixture_id):
    url = f"https://api.sportmonks.com/v3/football/match-facts/{fixture_id}"
    params = {
        "api_token": API_TOKEN,
        "include": "type"
    }

    response = requests.get(url, params=params)
    data = response.json()
    return data["data"]
```

{% endtab %}

{% tab title="PHP" %}

```php
<?php
$apiToken = "YOUR_TOKEN";
$fixtureId = 19609131;

$url = "https://api.sportmonks.com/v3/football/match-facts/{$fixtureId}";
$params = http_build_query([
    "api_token" => $apiToken,
    "include" => "type"
]);

$response = file_get_contents("{$url}?{$params}");
$data = json_decode($response, true);
$matchFacts = $data["data"];
```

{% endtab %}
{% endtabs %}

### Working with the data

Every Match Fact shares the same top-level shape:

```json
{
  "id": 52951712,
  "sport_id": 1,
  "fixture_id": 19609131,
  "type_id": 76095,
  "participant": "home",
  "basis": "h2h",
  "data": {
    "all": { "streak": 1, "matches": 1 },
    "home": { "streak": 0, "matches": 0 },
    "away": { "streak": 1, "matches": 1 }
  },
  "natural_language": null,
  "category": "streaks",
  "scope": "all_matches"
}
```

<table data-search="false"><thead><tr><th>Field</th><th>Description</th></tr></thead><tbody><tr><td><code>id</code></td><td>Unique ID of this fact instance</td></tr><tr><td><code>sport_id</code></td><td>Sport ID</td></tr><tr><td><code>fixture_id</code></td><td>The fixture this fact relates to</td></tr><tr><td><code>type_id</code></td><td>Identifies which specific fact this is (resolve via the <code>type</code> include, or cache the Types endpoint)</td></tr><tr><td><code>participant</code></td><td>Which side the fact is about: <code>"home"</code>, <code>"away"</code>, or <code>"both"</code></td></tr><tr><td><code>basis</code></td><td>Whether the fact is calculated from head-to-head history (<code>"h2h"</code>) or one team's own recent history (<code>"team"</code>)</td></tr><tr><td><code>category</code></td><td>Broad grouping: <code>"statistics"</code>, <code>"streaks"</code>, or <code>"players"</code></td></tr><tr><td><code>scope</code></td><td>Whether the fact is calculated across all historical matches (<code>"all_matches"</code>) or scoped to the current league only (<code>"league_matches"</code>)</td></tr><tr><td><code>natural_language</code></td><td>A ready-to-display sentence, or <code>null</code> if this particular fact wasn't selected for natural-language generation on this request</td></tr><tr><td><code>data</code></td><td>The fact's payload. <strong>Shape depends on the fact</strong>, not fixed - see below</td></tr></tbody></table>

#### `data` is not one shape - it depends on the fact

This is the part that trips people up: unlike most Sportmonks includes, `data` doesn't have one consistent structure across all Match Facts. What you get depends on what kind of fact it is. Real examples seen in a live response:

**Simple counts:**

```json
"data": { "count": 1 }
```

**Streaks:**

```json
"data": { "streak": 1, "matches": 1 }
```

**Win/loss/draw breakdown with context:**

```json
"data": {
  "win": {
    "all": { "count": 0, "percentage": 0 },
    "context": { "n_match_checked": 9, "similar_matches": 2, "first_match": "2018-06-15" }
  },
  "loss": { "...": "..." },
  "draw": { "...": "..." }
}
```

**Minute-bin goal distribution:**

```json
"data": { "0-15": 0, "15-30": 1, "30-45": 0, "45-60": 0, "60-75": 0, "75-90": 1 }
```

**Over/under threshold tables:**

```json
"data": {
  "over": { "0_5": { "count": 91, "percentage": 91.92 }, "1_5": { "...": "..." } },
  "under": { "0_5": { "count": 8, "percentage": 8.08 }, "1_5": { "...": "..." } }
}
```

**Player-attached facts:**

```json
"data": {
  "value": 0.96,
  "related_player": {
    "player_id": 37559109,
    "display_name": "Bilal El Khannouss",
    "sidelined": false
  }
}
```

**Last-match facts:**

```json
"data": {
  "date": "2026-06-07",
  "home_team": 18551,
  "away_team": 18578,
  "home_score": 1,
  "away_score": 1
}
```

Don't write a single parser assuming one `data` shape across all facts. Branch on `category` (and realistically, on `type_id`) before reading into `data`.

#### Resolving `type_id` to a human-readable name

Add `include=type` (standalone endpoints) to get a nested `type` object per fact:

```json
"type": {
  "id": 76115,
  "name": "Total H2H Matches",
  "code": "total-h2h-matches",
  "developer_name": "MATCH_FACT_TOTAL_H2H_MATCHES",
  "model_type": "match_fact",
  "stat_group": null
}
```

If you're processing many fixtures, fetch and cache all types from the Types endpoint once rather than including `type` on every request, the same recommended pattern used for event sub-types.

### Common pitfalls

* **`natural_language` is often `null`.** Not every fact gets a generated sentence on a given request - build your UI to handle a fact with structured `data` but no sentence, rather than assuming every fact has one.
* **`data` shape varies per fact.** Covered above, but worth repeating: this is the single most common way a Match Facts integration breaks.
* **`scope` matters for interpretation.** A fact with `scope: "all_matches"` and one with `scope: "league_matches"` can report very different numbers for what looks like the same `type_id` and `participant` - check `scope` before you compare or display two facts side by side.

### Advanced usage

**Filter to only live-relevant facts.** On the `matchfacts` include (via a fixtures endpoint), use `havingLiveMatchFacts` to restrict results to fixtures with facts that update mid-match, such as outcome probabilities that shift after a red card or goal.

**Pre-fetch a full matchday.** Use `GET /match-facts/between/{start_date}/{end_date}` to pull facts for every fixture in a date range in one pass, rather than calling the fixture endpoint per match.

**Group by `category` for display.** Since `category` cleanly separates `statistics`, `streaks`, and `players`, it's a natural way to organise a match preview into sections (e.g. a "Form & Streaks" panel vs a "Players to Watch" panel) without needing to inspect `type_id` for every fact.

### Common errors

| Status | Cause                                                                                                  |
| ------ | ------------------------------------------------------------------------------------------------------ |
| 400    | Malformed request - an unsupported parameter or filter was passed                                      |
| 403    | "Not authorized" - your plan doesn't include Match Facts, or the league isn't in your current coverage |
| 429    | Rate limit exceeded for your subscription                                                              |
| 500    | Internal error                                                                                         |

An uncovered league or a fixture with no available facts returns an empty `matchfacts` array rather than an error.

### See also

**Reference**

* [Match Facts endpoint reference](https://docs.sportmonks.com/v3/endpoints-and-entities/endpoints/match-facts-beta)
* [Types Reference](https://docs.sportmonks.com/v3/definitions/types/events)

**Related tutorials**

* [Data features per league](https://docs.sportmonks.com/v3/tutorials-and-guides/tutorials/data-features-per-league) - confirming Match Facts coverage for a specific league
* [Events](https://docs.sportmonks.com/v3/tutorials-and-guides/tutorials/includes/events) - the same `type`/`sub_type` caching pattern applies here

### FAQ

**Why is `natural_language`** **`null` on most facts?** Not every fact is selected for sentence generation on a given request. Build your display logic to fall back to the structured `data` fields when it's missing.

**Why does the same `type_id` sometimes show completely different numbers?** Check `scope` first. `"all_matches"` and `"league_matches"` calculate over different historical windows, so the same fact type can legitimately report different numbers depending on scope.

**Is there one consistent `data` structure I can rely on?** No. `data` varies by `category` and effectively by `type_id`. Branch your parsing logic accordingly rather than assuming one shape.


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

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

Perform an HTTP GET request on the following URL with the `ask` and `goal` query parameters:

```
GET https://docs.sportmonks.com/v3/tutorials-and-guides/tutorials/includes/match-facts.md?ask=<question>&goal=<user_goal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is what the user is ultimately trying to achieve, the reason they need the answer. Sharing it helps GitBook give you a better, more relevant answer. A goal is most helpful when it describes the outcome the user wants rather than restating the question. For example, with `ask=how do I create an API token`, a goal like `automate deployments from our CI pipeline` lets GitBook tailor the answer to that use case.

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.
