> 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

{% hint style="info" %}
**🧪 Beta feature**

Match Facts is in public beta, included on Starter, Growth, Pro, and Enterprise plans. League coverage expands during the beta period - check the coverage sheet linked from the Match Facts reference page, or use the `havingLiveMatchFacts` filter and your own MySportmonks subscription details to confirm coverage for a specific league before relying on it in production.
{% endhint %}

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="Second Tab" %}

```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.
* **Coverage is beta-limited.** Not every league returns facts yet. A fixture in an uncovered league will return `matchfacts: []` rather than an error.

### 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 dynamically by asking a question.

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

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

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is optional and describes the broader end goal you are ultimately trying to accomplish on behalf of the user. GitBook uses it to tailor the answer towards what is most useful for that goal.

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.
