Exploring XG match data with Sportmonks and Elixir
Contents

What is expected goals?

Expected goals (xG) is a statistic that helps measure the quality of scoring chances in a football match. Rather than just counting goals, xG looks at how likely each shot was to score, based on factors like shot location, type, and defensive pressure. The goal is to give a better idea of a team’s performance and attacking effectiveness.

How xG works

Each shot in a match is assigned an xG value between 0 and 1. For example:

– A shot from close range with minimal defensive pressure might have an xG of 0.80 (or an 80% chance of scoring).
– A long-range effort with defenders blocking might be assigned an xG of 0.05.

The sum of all xG values for a team in a match gives an overall expectation of how many goals they should have scored, based on the quality of their chances.

Factors influencing xG

The calculation of xG includes variables such as:

– Distance from goal
– Angle of the shot
– Type of assist (e.g., through ball, cross)
– Body part used (e.g., header, foot)
– Defensive pressure
– Position of the goalkeeper

Why xG matters

xG is important for:

– Analysts who want to understand a team’s performance beyond just the final score.
– Coaches and scouts who use it to spot players who are performing better or worse than expected.
– Fans and pundits who use it to make more informed assessments of matches.
Fantasy football platforms and betting markets that use xG for deeper insights.

By comparing actual goals to expected goals, xG shows if a team was efficient, missed chances, or was just unlucky.

What is Elixir?

Elixir is a programming language used to create scalable, reliable, and easy-to-maintain applications. It is built on the Erlang Virtual Machine (BEAM), which is known for powering systems in telecoms and distributed applications.

Key features of Elixir:

Concurrency: Elixir can handle thousands of tasks at once using lightweight processes, making it great for real-time applications like chat systems, notifications, or live match updates.
Fault tolerance: Elixir follows Erlang’s “let it crash” approach, allowing applications to recover from errors without crashing the whole system.
Scalability: Elixir can handle anything from a few users to millions, and it can scale across multiple machines or processors.
Clean syntax: Inspired by Ruby, Elixir has simple and easy-to-read code, making it user-friendly for developers.
Ecosystem: Elixir has powerful tools like Phoenix (a web framework), LiveView (for real-time features), and Mix (a build tool), helping developers create fast and interactive applications with less effort.

Where Elixir excels

While Elixir is commonly used in web development, it’s also a strong choice for:

– Real-time dashboards
– Event-driven systems
APIs and backend services
– Messaging systems

In this guide, we’ll use Elixir to connect with Sportmonks’ football API, parse match data, particularly Expected goals (xG) and structure it for real-world applications.

Chapter 1. Setting up your environment

Before we begin building with Elixir and integrating Sportmonks’ Football API, we need to prepare our development environment. This includes installing Elixir, adding essential libraries, and generating an API token from Sportmonks.

1. Install Elixir

If you haven’t already installed Elixir, follow the steps below based on your operating system:

macOS (via Homebrew)

brew install elixir

Ubuntu / Debian

sudo apt update
sudo apt install elixir

Windows

Use the precompiled installer available from the official Elixir website.

You can verify the installation by running:

elixir --version

2. Create a new Elixir project

We’ll use Elixir’s build tool, Mix, to create a new project:

mix new sportmonks_xg --module SportmonksXG
cd sportmonks_xg

3. Add required dependencies

Open mix.exs and add the following libraries under the deps function:

defp deps do
  [
    {:httpoison, "~> 1.8"},   # For making HTTP requests
    {:jason, "~> 1.4"}        # For parsing JSON responses
  ]
end

Then run:

mix deps.get

4. Get your Sportmonks API token

To access the Football API, you’ll need an API token:

– Go to my.sportmonks.com.
– Register or log in.
– Navigate to the “API Tokens” section.
– Copy your personal token.

Store this token securely as we’ll use it in upcoming API calls.

5. Set up environment variables (optional but recommended)

To avoid hardcoding the API token, you can load it via environment variables. In your project root, create a .env file:

touch .env

Add the following line:

SPORTMONKS_API_TOKEN=your_token_here

Use the dotenv library or System.get_env(“SPORTMONKS_API_TOKEN”) in your app to access it.

6. Set up an IDE for Elixir development

Having a good development environment improves productivity, debugging, and code readability. Here are some recommended IDE options for Elixir development:

VS Code is a lightweight and flexible editor with excellent Elixir support.

Steps to set up:

1. Install VS code:: Download it here

2. Install the ElixirLS extension:
  – Open VS Code.
  – Go to the Extensions tab (Ctrl+Shift+X or Cmd+Shift+X on macOS).
  – Search for ElixirLS: Elixir support and debugger and install it.

3. Enable mix project support:
When opening your Elixir project folder, ElixirLS automatically detects it and compiles it in the background.

4. Optional: Install additional helpful extensions:

    – DotENV  for syntax highlighting in your .env file.
    – Code Spell Checker useful when writing documentation

Chapter 2. Retrieving fixture data using Elixir

Now that your Elixir environment is ready, it’s time to connect to the Sportmonks football API and retrieve detailed match fixture data  including participants, venue, league, state, scores, lineups, events, and expected goals (xG). This forms the foundation for building match visualisations, live dashboards, or performance insights.

1. Understand the fixture endpoint

We’ll use the following Sportmonks API endpoint:

GET https://api.sportmonks.com/v3/football/fixtures/{fixture_id}?include=participants;league;venue;state;scores;events.type;events.period;events.player;xGFixture;lineups.player;lineups.xGlineup;lineups.details

This is a fixture between Man Utd and Aston Villa in the English Premier League.

Replace {fixture_id} with the match ID you want to analyse eg 19135043.

The include parameter tells Sportmonks to return additional nested data in the same response.

2. Write the API request module in Elixir

In your Elixir project (e.g., sportmonks_xg/lib/sportmonks_xg.ex), create a module to fetch fixture data.

defmodule SportmonksXG do
  @moduledoc """
  Fetches and displays fixture data from Sportmonks Football API.
  """

def api_token, do: System.get_env("SPORTMONKS_API_TOKEN") || "your_token"
  @base_url "https://api.sportmonks.com/v3/football/fixtures"

  def get_fixture_data(fixture_id) do
    includes = Enum.join([
      "participants",
      "league",
      "venue",
      "state",
      "scores",
      "events.type",
      "events.period",
      "events.player",
      "xGFixture",
      "lineups.player",
      "lineups.xGlineup",
      "lineups.details"
    ], ";")

    url = "#{@base_url}/#{fixture_id}?api_token=#{api_token()}&include=#{includes}"

    case HTTPoison.get(url, [], recv_timeout: 10_000) do
      {:ok, %HTTPoison.Response{status_code: 200, body: body}} ->
        Jason.decode(body)

      {:ok, %HTTPoison.Response{status_code: code}} ->
        {:error, "Request failed with status code #{code}"}

      {:error, %HTTPoison.Error{reason: reason}} ->
        {:error, "HTTP error: #{inspect(reason)}"}
    end
  end
end

3. Test the fixture call

Create a small runner script (lib/demo.exs) or test it in IEx:

alias SportmonksXG

case SportmonksXG.get_fixture_data(19135043) do
  {:ok, %{"data" => fixture}} ->
    IO.puts("Match: #{fixture["name"]}")
    IO.inspect(fixture["participants"], label: "Participants")
    IO.inspect(fixture["xgfixture"], label: "xG Stats")

  {:error, reason} ->
    IO.puts("Error: #{reason}")
end

Then run:

iex -S mix

Or execute a one-off file:

mix run lib/demo.exs

4. What you’ll receive in the response

The response includes nested maps with data such as:

participants: Home and away teams
venue: Stadium and location info
scores: First half, second half, and full time scores
xGFixture: Expected goals per team
lineups: Player information, xG per player
events: Key match events (e.g. goals, cards, subs)
state: Live, Finished, Postponed etc.
league: Competition details

"xgfixture": [
    {
      "id": 413065374,
      "fixture_id": 19135043,
      "type_id": 5304,
      "participant_id": 15,
      "data": {
        "value": 0.5302
      },
      "location": "away",
      "type": {
        "id": 5304,
        "name": "Expected Goals (xG)",
        "code": "expected-goals",
        "developer_name": "EXPECTED_GOALS",
        "model_type": "statistic",
        "stat_group": "offensive"
      }
    },
    {
      "id": 413065373,
      "fixture_id": 19135043,
      "type_id": 5304,
      "participant_id": 14,
      "data": {
        "value": 2.8283
      },
      "location": "home",
      "type": {
        "id": 5304,
        "name": "Expected Goals (xG)",
        "code": "expected-goals",
        "developer_name": "EXPECTED_GOALS",
        "model_type": "statistic",
        "stat_group": "offensive"
      }
    },

Chapter 3. Working with expected goals (xG) data

Expected goals (xG) gives a clearer picture of how effective a team or player was during a match. With the Sportmonks API, xG data is available at both the fixture level and player level within the fixture.

In this chapter, I’ll show you how to parse that xG data using Elixir and turn it into useful outputs.

1. Understanding the xG data structure

When you include xgfixture, lineups.xglineup, and lineups.player in your request, the API will return:

Fixture xG: Overall expected goals for each team.
Player xG: Individual xG contributions, found within each team’s player list.

Example JSON paths:

{
  "data": {
    ...
    "xgfixture": [
      {
        "team_id": 1530,
        "value": 1.67
      },
      {
        "team_id": 1531,
        "value": 0.95
      }
    ],
    "lineups": [
      {
        "team_id": 1530,
        "players": [
          {
            "player": {
              "display_name": "Mason Mount"
            },
            "xglineup": [
              {
                "value": 0.50
              }
            ]
          }
        ]
      }
    ]
  }
}

2. Extract and print team xG in Elixir

Let’s write a helper function to format the xGFixture data:

def print_team_xg(%{"xgfixture" => xg_data, "participants" => participants}) do
  Enum.each(xg_data, fn %{"team_id" => team_id, "value" => value} ->
    team = Enum.find(participants, fn %{"id" => id} -> id == team_id end)
    team_name = team["name"]
    IO.puts("#{team_name} xG: #{value}")
  end)
end

Usage:

{:ok, %{"data" => fixture}} = SportmonksXG.get_fixture_data(19135043)
print_team_xg(fixture)

3. Extract and display player-level xG

To explore xG per player:

def print_player_xg(%{"lineups" => lineups}) do
  Enum.each(lineups, fn lineup ->
    team_id = lineup["team_id"]
    players = lineup["players"] || []

    IO.puts("Team ID: #{team_id} Player xG stats:")

    Enum.each(players, fn %{"player" => player, "xglineup" => [xg | _]} ->
      name = player["display_name"]
      IO.puts("- #{name}: xG #{xg["value"]}")
    end)
  end)
end

This function shows a breakdown of how much xG each player contributed.

4. What you can build with this

With this xG data, you can:

Create xG dashboards for tactical analysis.
Compare real goals vs expected to identify under/overperformers.
Enrich fantasy football platforms with deeper player stats.
Build coaching or scouting tools with player efficiency insights.

Chapter 4. Retrieving xG data for a team across a date range

If you’re building a tool to analyse team performance over time, one match isn’t enough. You need trends, and that’s where the fixtures/between endpoint becomes powerful. With it, you can retrieve all fixtures for a team within a specific date range, then extract and compare expected goals (xG) data across those matches.

1. What the endpoint does

Sportmonks provides the following endpoint:

GET https://api.sportmonks.com/v3/football/fixtures/between/{start_date}/{end_date}/{team_id}

start_date: Format YYYY-MM-DD (beginning of the range).
end_date: Format YYYY-MM-DD (end of the range)
team_id: The team you want to analyse.

This returns all matches that the team played within the date range.

2. Why it’s useful for xG analysis

By adding xgfixture as an include, you’ll get the expected goals values per team for every fixture. You can then:

– Visualise a team’s attacking performance trend
– Compare actual goals vs xG over time
– Identify matches where the team overperformed or underperformed

3. Fetching and parsing with Elixir

Here’s how to implement this in your project:

a) Function to fetch fixtures

def get_team_xg_trend(start_date, end_date, team_id) do
  includes = "xgfixture"
  url = "https://api.sportmonks.com/v3/football/fixtures/between/#{start_date}/#{end_date}/#{team_id}?api_token=#{@api_token}&include=#{includes}"

  case HTTPoison.get(url) do
    {:ok, %HTTPoison.Response{status_code: 200, body: body}} ->
      Jason.decode(body)

    {:ok, %HTTPoison.Response{status_code: code}} ->
      {:error, "Failed with status code #{code}"}

    {:error, %HTTPoison.Error{reason: reason}} ->
      {:error, "HTTP Error: #{inspect(reason)}"}
  end
end

b) Display xG values per match

def print_team_xg_trend(fixtures, team_id) do
  Enum.each(fixtures, fn fixture ->
    xg_data = fixture["xgfixture"] || []
    date = fixture["starting_at"]

    case Enum.find(xg_data, fn xg -> xg["team_id"] == team_id end) do
      %{"value" => value} ->
        IO.puts("[#{date}] xG: #{value}")

      _ ->
        IO.puts("[#{date}] xG data not available")
    end
  end)
end

Example usage:

{:ok, %{"data" => fixtures}} = SportmonksXG.get_team_xg_trend("2024-08-01", "2024-12-01", 53)
print_team_xg_trend(fixtures, 53)

Example output

[2024-08-02] xG: 1.45
[2024-08-08] xG: 0.88
[2024-08-15] xG: 2.10

You can take this a step further and:

– Calculate average xG per match
– Plot xG trends using a graphing library or external tools
– Compare xG vs actual goals for efficiency analysis

This gives you quantitative insight into how threatening a team has been across a series of matches beyond just final scores. It’s especially useful for scouting, predictive modelling, or fan-driven analytics dashboards.

Power your Elixir app with xG data from Sportmonks

Bring deeper football intelligence into your Elixir projects by integrating Sportmonks’ football API. With access to advanced metrics like expected goals (xG), you can analyse match performance, player efficiency, and tactical trends all from real-time, structured data.

Whether you’re building analytics dashboards, scouting tools, or fantasy platforms, Sportmonks makes it easy to access and use xG data directly in your Elixir application.

Start your free trial today and explore football like never before.

Faqs about Sportmonks’ fixture API

What is the Sportmonks fixture API?
The Sportmonks fixture API is part of their football API, allowing developers to access detailed data about football matches. This includes information on upcoming games, past results, and details such as match ID, league, season, start time, result, venue, and the teams involved.
What kind of fixture data can I get from the Sportmonks API?
You can get various types of fixture data, including: - All fixtures available in your subscription. - Information about a single fixture using its unique ID. - Multiple fixtures using a list of IDs. - Fixtures within a specific date range or for a particular date. - Fixtures for a specific team within a date range. - Head-to-head fixtures between two teams. - Fixtures that match a search term by name. - Fixtures that have been recently updated.
Does the fixture API provide live scores or in-play data?
The Fixture API provides details about fixtures, but for live scores and real-time in-play data (like goals, cards, and substitutions), you need to use Sportmonks' dedicated Livescores endpoint. The Fixture API is mainly for pre-match and historical fixture information.

Written by David Jaja

David Jaja is a technical content manager at Sportmonks, where he makes complex football data easier to understand for developers and businesses. With a background in frontend development and technical writing, he helps bridge the gap between technology and sports data. Through clear, insightful content, he ensures Sportmonks' APIs are accessible and easy to use, empowering developers to build standout football applications