Building with AI? Give your coding tool the whole reference in one file: https://soccer-data.co/llms.txt
SOCCER DATA Docs
Sign in Get API key
// Matches

Get match shot map

Get every shot in a match with its pitch position, outcome and expected goals (xG). Use it to draw a shot map, compare chance quality between teams or break down one player's shooting. Pass player_id to get only that player's shots.

GET https://api.soccer-data.co/v1/matches/{match_id}/shotmap Live
  • Check shotmap_available on the match first. It is only true when at least one shot has an xG above zero.
  • It also works during a match. meta.is_final is false while the match is live, so more shots can still appear, and an empty data list just means nobody has shot yet.
  • A shot's expected_goals can be null when no xG exists for it. The match's xg_quality tells you what to expect.
  • Penalties are left out of np_xg_summary. stored can differ from live when a match-level figure has been recorded.
  • Returns 404 STATS_NOT_AVAILABLE when detailed stats were never collected for the match, and 404 NOT_FOUND when the match is unknown or has no shot data.
Path parameters
match_idstringrequired

The match's id, like match_…. Find one with the ID guide.

Query parameters
player_id string

Only return shots taken by this player.

Response fields
match_idstring

The match's id.

event.idstring

The match's id again.

event.home_team_idstring

Home team's id. Compare it with each shot's team_id to tell the sides apart.

event.away_team_idstring

Away team's id.

data[]array

Every shot in the match, or only the chosen player's shots.

data[].idstring

The shot's id.

data[].player_idstring

Id of the player who took the shot.

data[].player_namestring

Name of the player who took the shot.

data[].team_idstring

Id of the shooting team.

data[].team_namestring

Name of the shooting team.

data[].xnumber

Distance from the goal being attacked, from 0 to 100. 0 is that goal line and 100 is the shooter's own goal line. Every shot is flipped to attack the same goal, so you can plot both teams on one half. The edge of the box is at 16.5 and the penalty spot at about 11.

data[].ynumber

Position across the pitch, from 0 to 100. 50 is the middle of the goal. The penalty box runs from 20.5 to 79.5.

data[].minuteinteger

Minute the shot was taken.

data[].resultstring

What happened to the shot, for example goal, save, miss, block, blocked_off_line or post.

data[].expected_goalsnumber|null

The shot's xG: how likely a shot like this is to go in. Null when no xG exists for it.

data[].situationstring|null

How the chance came about: assisted, regular or fast_break (open play), corner, free_kick, set_piece or throw_in_set_piece (dead ball), penalty, shootout or own_goal.

data[].body_partstring|null

What the shot was taken with, for example right_foot.

data[].goal_typestring|null

Only set for goals, for example regular or own. Null otherwise.

data[].goal_mouth_locationstring|null

Which part of the goal mouth the shot went to, for example low_centre.

data[].goal_mouth_coordinatesobject|null

Where the shot reached the goal mouth, as x, y and z numbers.

data[].block_coordinatesobject|null

Where the shot was blocked, as x, y and z numbers.

data[].is_blocked_shotboolean

Whether the shot was blocked.

data[].blocked_by_player_idstring|null

Id of the player who blocked it.

data[].goalkeeperobject|null

The goalkeeper facing the shot, with id and name.

data[].is_goalboolean

Whether the shot was a goal.

data[].is_on_targetboolean

True for goals, saves and shots cleared off the line. False for own goals, which show up with goal_type own.

data[].is_headedboolean

Whether the shot was a header.

data[].is_outside_boxboolean

True when the shot was taken outside the penalty box (x above 16.5, or y outside 20.5 to 79.5).

data[].is_penaltyboolean

Whether the shot was a penalty.

np_xg_summary.liveobject

Non-penalty xG per team, added up from the shots in data. Has home_team and away_team numbers.

np_xg_summary.storedobject

The recorded match-level non-penalty xG, with home_team and away_team. Same as live when no recorded figure exists.

meta.is_finalboolean

False while the match is live and shots can still be added. True otherwise.

curl "https://api.soccer-data.co/v1/matches/match_9pd0ck6g2m9i/shotmap" \
  -H "X-Api-Key: $SOCCER_DATA_KEY"
200 · Example response
{
    "match_id": "match_9pd0ck6g2m9i",
    "event": {
        "id": "match_9pd0ck6g2m9i",
        "home_team_id": "team_4mimn5fmya2v",
        "away_team_id": "team_74p8tg2k93z4"
    },
    "data": [
        {
            "id": "shot_yb504px5a0rx",
            "player_id": "player_85xcr2xm5vxo",
            "player_name": "Ollie Watkins",
            "team_id": "team_4mimn5fmya2v",
            "team_name": "Aston Villa",
            "x": 16.3,
            "y": 45.8,
            "minute": 2,
            "result": "save",
            "expected_goals": 0.118,
            "situation": "fast_break",
            "body_part": "right_foot",
            "goal_type": null,
            "goal_mouth_location": "low_centre",
            "goal_mouth_coordinates": {
                "x": 0,
                "y": 50.1,
                "z": 5.7
            },
            "block_coordinates": {
                "x": 1.9,
                "y": 50.2,
                "z": 0
            },
            "is_blocked_shot": false,
            "blocked_by_player_id": null,
            "goalkeeper": {
                "id": "player_nyb14jgqd9cz",
                "name": "Giorgi Mamardashvili"
            },
            "is_goal": false,
            "is_on_target": true,
            "is_headed": false,
            "is_outside_box": false,
            "is_penalty": false
        }
    ],
    "np_xg_summary": {
        "live": {
            "home_team": 2.397,
            "away_team": 1.572
        },
        "stored": {
            "home_team": 2.397,
            "away_team": 1.572
        }
    },
    "meta": {
        "is_final": true
    }
}
Get a free key to try it