For the complete documentation index, see llms.txt. This page is also available as Markdown.

Match Facts

πŸ§ͺ 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.

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

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

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.

Working with the data

Every Match Fact shares the same top-level shape:

Field
Description

id

Unique ID of this fact instance

sport_id

Sport ID

fixture_id

The fixture this fact relates to

type_id

Identifies which specific fact this is (resolve via the type include, or cache the Types endpoint)

participant

Which side the fact is about: "home", "away", or "both"

basis

Whether the fact is calculated from head-to-head history ("h2h") or one team's own recent history ("team")

category

Broad grouping: "statistics", "streaks", or "players"

scope

Whether the fact is calculated across all historical matches ("all_matches") or scoped to the current league only ("league_matches")

natural_language

A ready-to-display sentence, or null if this particular fact wasn't selected for natural-language generation on this request

data

The fact's payload. Shape depends on the fact, not fixed - see below

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:

Streaks:

Win/loss/draw breakdown with context:

Minute-bin goal distribution:

Over/under threshold tables:

Player-attached facts:

Last-match facts:

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:

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

Related tutorials

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.

Last updated

Was this helpful?