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_languagefield gives you publishable sentences without building your own template logicBetting 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=matchfactsBest 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:
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_languageis oftennull. Not every fact gets a generated sentence on a given request - build your UI to handle a fact with structureddatabut no sentence, rather than assuming every fact has one.datashape varies per fact. Covered above, but worth repeating: this is the single most common way a Match Facts integration breaks.scopematters for interpretation. A fact withscope: "all_matches"and one withscope: "league_matches"can report very different numbers for what looks like the sametype_idandparticipant- checkscopebefore 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
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
Data features per league - confirming Match Facts coverage for a specific league
Events - the same
type/sub_typecaching 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.
Last updated
Was this helpful?