On this pageShow
Fundamentals

Quickstart

PitchAPI is a read-only REST API for football data.

Grab a day of fixtures, take a match ID, and drill into shots, momentum, events, player stats, lineups, and the advanced analytics built on the raw event feed. You just need an API key.

Your first request
curl https://api.pitchapi.dev/v1/date/2025-11-09 \
  -H "X-API-KEY: $PITCH_KEY"
Base URLhttps://api.pitchapi.dev

All requests must use HTTPS. Requests over plain HTTP are refused rather than redirected so that a misconfigured client never leaks its key.

Fundamentals

Authentication

Pass your API key in the X-API-KEY header on every request. Production keys use pk_live_, test keys use pk_test_. You get a key when you sign up and can view it in your dashboard.

Authenticated request
curl https://api.pitchapi.dev/v1/matches/m_B8x2K9 \
  -H "X-API-KEY: pk_live_xxxxxxxxxxxxxxxxxxxxxxxx"

Your API key is a bearer credential: anyone holding it can spend your quota. Keep it server-side and never ship it in client code or a public repository. We only store a SHA-256 hash of your key, so a lost key must be replaced.

Fundamentals

Responses

Every response is wrapped in a single JSON envelope. Success carries a data key, failure carries an error key. The two never appear together, so branch on whichever is present.

200 OK
{
  "data": {
    "id": "m_B8x2K9",
    "status": "finished",
    "score_home": 3,
    "score_away": 1
  }
}
404 Not Found
{
  "error": {
    "code": "RESOURCE_NOT_FOUND",
    "message": "match not found"
  }
}

Timestamps are RFC 3339 in UTC, dates are YYYY-MM-DD. Optional fields are omitted when unavailable. Lists return an empty array when there is nothing to report. A null is never a zero: it means the value is undefined, which for a rate means an empty denominator and for an average means nothing to average.

Fundamentals

Resource IDs

Every public ID is a short opaque token: a resource prefix, an underscore, and exactly six base62 characters. IDs are stable and non-sequential. Treat them as opaque strings and never parse them for meaning.

PrefixResourceExample
m_Matchm_B8x2K9
s_Shots_Q1pR6z
p_Playerp_7YtX4q
t_Teamt_9aB2xQ
l_Leaguel_4Kd0Wq

Shot IDs are the one exception to global uniqueness: they are scoped to their match, so the same value in different matches refers to different shots.

Fundamentals

Plans and coverage

Both plans reach every endpoint and the full history back to 2021, and neither carries a request allowance. The only difference is which competitions are in scope: the free tier serves the five big domestic divisions, Pro serves the whole catalogue.

There is no per-second or per-minute rate to design around, and no daily total to budget for. A free key asking for a league outside the Big 5 gets a 403 with the code PLAN_UPGRADE_REQUIRED — the resource exists, it is just on Pro.

FreeNo charge
Big 5leagues · unlimited requests

The five biggest domestic leagues — Premier League, LaLiga, Bundesliga, Ligue 1, Serie A. Every endpoint, full history back to 2021.

Pro$19.99 / month
42leagues · unlimited requests

The whole catalogue — every league the service covers, including second tiers, cups and UEFA competitions. No request limit either.

There are no X-RateLimit headers, because there is no allowance for them to describe. The only limit the API enforces is an unadvertised fair-use burst guard, far above anything a normal client does.

Sustaining it returns 429 with the code RATE_LIMIT_EXCEEDED and a Retry-After header. There is never an overage charge. Coverage limits are a 403 PLAN_UPGRADE_REQUIRED instead, so a client can tell a coverage limit from throttling.

Fundamentals

Error codes

Errors use conventional HTTP status codes alongside a stable machine-readable code. Branch on the code, not the message — messages are for humans and may be reworded.

Two of them are 404s and mean different things. RESOURCE_NOT_FOUND says the ID does not exist; ANALYTICS_UNAVAILABLE says the match does, and we never rated it.

StatusCodeMeaning
401UNAUTHORIZEDThe X-API-KEY header is missing or the key is invalid.
403SUBSCRIPTION_SUSPENDEDThe key is valid but its subscription is inactive (failed payment or canceled plan).
403PLAN_UPGRADE_REQUIREDThe free tier covers the five big domestic divisions only. This league (or a match, team or player outside it) needs the Pro plan. Deliberately distinct from RESOURCE_NOT_FOUND so you can tell "exists but Pro-only" from "does not exist".
429RATE_LIMIT_EXCEEDEDThe fair-use burst ceiling, shared by both plans, was hit — the API serves requests without a per-day allowance, so this only appears far above anything a normal client does. Retry-After gives the seconds to back off for.
400INVALID_PARAMETERA path or query parameter isn't in the expected format (date, ID pattern, etc.).
404RESOURCE_NOT_FOUNDNo resource exists with that ID.
404ANALYTICS_UNAVAILABLEAdvanced analytics routes only. The match exists but was never rated — event-feed coverage is not universal, so this is an ordinary answer rather than an error on your side. Kept separate from RESOURCE_NOT_FOUND so you can tell it from a bad match ID.
500INTERNAL_SERVER_ERRORSomething went wrong on our side. Retry with backoff.
Data model

Coordinate systems

Shots carry two coordinate pairs: where the ball was struck on the pitch and where it crossed the goal line. Both are in metres on a real 105 x 68 pitch, not a normalised grid.

Pitch coordinates are normalised for direction: every shot attacks the goal at x equals 105 regardless of which side took it. You never need to flip the axis for direction. Every distance in the advanced analytics uses the same pitch and the same units.

On the pitch
x0 to 105
Distance along the pitch in metres, normalised so the attacking goal is always at 105.
y0 to 68
Lateral position across the pitch in metres. The centre line is at 34.
On the goal line
goal_crossed_y0 to 68
Where the shot crossed the goal line on the same y axis as the pitch.
goal_crossed_z0 to 7.6
Height above the ground where the shot crossed the goal line.
Goal width
7.32 m
posts at y = 30.34 and 37.66
Crossbar height
2.44 m
z above this is over
Penalty area
16.5 m deep
x >= 88.5
Penalty spot
11 m out
x = 94

The goal_crossed_y field is measured across the full pitch width, not the goal opening alone.

Data model

Shot fields

Every shot response includes the fields below. A few need care: is_on_target is also true for blocked shots heading on target, and expected_goals_on_target is omitted when unavailable.

id
string
Shot identifier, scoped to this match. Not globally unique.
player
object
The player who took the shot (id, name, position_id, image_url).
team_id
string
The team that took the shot.
x
number
0 to 105
Pitch x coordinate where the shot was struck, in metres.
y
number
0 to 68
Pitch y coordinate where the shot was struck, in metres.
expected_goals
number
0 to 1
Chance quality before the shot, as a probability from 0 to 1.
expected_goals_on_target
number
0 to 1
Chance quality after the shot, given where it crossed the line. Omitted when unavailable.
is_on_target
boolean
Whether the shot was on target.
goal_crossed_y
number
0 to 68
Y coordinate where the shot crossed the goal line. Omitted when the crossing position isn't available.
goal_crossed_z
number
0 to 7.6
Height in metres where the shot crossed the goal line. Omitted when the crossing position isn't available.
is_inside_box
boolean
Whether the shot was taken from inside the penalty area.
event_type
string
The outcome: Goal, AttemptSaved, Miss, or Post.
situation
string
How the chance arose: RegularPlay, FromCorner, SetPiece, FastBreak, FreeKick, ThrowInSetPiece, Penalty, or IndividualPlay.
shot_type
string
Body part used: RightFoot, LeftFoot, Header, or OtherBodyParts.
minute
integer
Match minute the shot was taken, not counting stoppage time.
minute_added
integer
Stoppage-time minutes added to minute. Omitted when none was added.
is_blocked
boolean
Whether a defender blocked the shot before it reached the goal.
blocked_x
number
Pitch x coordinate where the ball was intercepted or blocked. Omitted when not blocked.
blocked_y
number
Pitch y coordinate where the ball was intercepted or blocked. Omitted when not blocked.
is_own_goal
boolean
Whether the goal was an own goal.
is_saved_off_line
boolean
Whether the ball was cleared off the goal line.
keeper
object
The goalkeeper facing the shot (id, name, position_id, image_url). Omitted when unavailable.
Data model

Player stats

Each stat line is an array of groups. Read entries by their inner key field, not the display label — the key is stable across matches, the label may change.

Which groups appear depends on position and involvement. Every player who featured gets top_stats. Outfielders also get attack, defense, and duels. Goalkeepers get shot-stopping figures inside top_stats instead.

GroupAppears onContains
top_statsEvery stat lineRating, minutes, goals, assists, xG, and xA. Present for every player who featured.
attackOutfieldersTouches, dribbles, crosses, passes into the final third, and non-penalty xG. Outfielders only.
defenseOutfieldersClearances, interceptions, recoveries, tackles, blocks, and times dribbled past. Outfielders only.
duelsOutfieldersAerial and ground duels, fouls committed, and fouls won. Outfielders only.
physical_metricsRarely availableDistance covered, sprints, and top speed. From tracking data.
Value shapes: branch on stat.type
integer
A whole number (goals, touches, clearances).
double
A decimal number (ratings, xG).
fractionWithPercentage
A made-attempted pair (accurate passes, duels won). Carries both value and total.
distanceWithPercentage
Metres covered out of a total distance (running splits).
distance
A distance in metres.
speed
A speed in km/h.
fantasyPoints
A fantasy football score.
boolean
A presence flag, not a measurement. Skip it when parsing.
Data model

Lineups

Formations, starters, and benches for both sides. Two fields trip people up: pitch coordinates (normalised 0 to 1, not metres) and position_id (different meaning for starters versus substitutes).

Lineup fields
formation
Formation at kick-off (e.g. 4-2-3-1).
pitch_x / pitch_y0 to 1
Position in the formation, normalised to 0 to 1. Both are 0 for every substitute.
position_id
For a starter, a slot in the formation grid. For a substitute, a positional category: 0 goalkeeper, 1 defender, 2 midfielder, 3 attacker, -1 unclassified.
is_captain
Whether the player was captain at kick-off.
coach
The coach (name). Omitted when unavailable.
Data model

Momentum

A minute-by-minute pressure curve as a single signed series. Some matches have no momentum data, so check the array length before plotting.

Momentum points
minute0 to 90.75
Match minute. Fractional values mark stoppage and added extra time.
value-100 to 100
Pressure index at this minute. Positive favours home, negative favours away.
Data model

Events

The match timeline with four event types: goals, cards, substitutions, and penalties. Shots are not here — use the shots endpoint for shot data and this one for the narrative.

The period field is derived from the minute, not reported by upstream. Treat it as a convenience around half time.

goalA goal, with the running score and own-goal / penalty flags.
substitutionA substitution. player comes off, sub_in_player comes on.
yellowcardA yellow card.
redcardA red card (straight red or second yellow).
Data model

Team stats

Team-versus-team statistics for each half and the match as a whole. Values are display strings, so parse them with format_type. An empty format_type still holds a plain number.

The Expected goals group splits xG into open play and set play, which shot-level data does not provide. Groups come back in alphabetical order. For typed numeric analytics on the same match, use the advanced endpoints instead — those return numbers, not display strings.

format_type values
integer
A whole number as a string.
integerWithPercentage
A count and its percentage in one string. The number precedes the space.
double
A decimal as a string.
distance
A distance in metres as a string.
""
An empty format_type but still a plain number.
Metrics

How to read these

Everything the advanced endpoints return, defined. Most of these metrics exist elsewhere under the same name and a different definition, so each entry states ours and, where it matters, how it diverges from the figure you would get from FBref, Opta or an independent collector.

Distances are metres on a 105 x 68 pitch, times are seconds, percentages run 0 to 100. Where a metric is a rate or an average, null means undefined — an empty denominator, or nothing to average — and never zero. A team that attempted no pass has no pass accuracy, which is not an accuracy of 0.

Two pairs that must never be added together. passing.passes already contains passing.crosses, and creation.chances_created is the same number as passing.key_passes. Both are published under two names because both names are asked for.

Player figures do not always sum to the team figure. creation.sca_breakdown.foul_drawn belongs to the team and to no player, so the players’ sca sums short by exactly that amount.

Totals

The two fields that sit at the top level of every team and player object, outside the metric groups.

actionsactionsteam + player

On-the-ball actions in the converted event stream.

Every pass, cross, carry, take-on, shot, tackle, interception, clearance and keeper action attributed to the team or player, after the raw feed has been converted into a uniform action stream.

How ours differs

Not touches, and not the raw provider event count. Carries are synthesised and counted here — roughly 400 a match that do not exist in the source feed — while events with no action equivalent are dropped. Use it as a denominator for these metrics, never as a cross-provider volume figure.

minutes_playedminutes_playedplayer

Minutes the player was on the pitch.

Derived from the lineup and substitution record for the match.

Possession value

What an action did to the probability of scoring. Both models are fitted on closed historical seasons and frozen before they rate anything, so a match is never scored by a model that saw it.

xt_totalpossession_value.xt_totalteam + player

Threat added by moving the ball into more dangerous space.

Expected Threat lays a 16 x 12 grid over the pitch and gives each of the 192 zones a value for how likely the possessing team is to score from there. A ball-moving action is worth the destination zone's value minus the origin's, and xt_total sums that across every action.

How ours differs

xT can only rate successful actions that move the ball and retain possession. Shots, tackles and failed moves come back null rather than zero, because a zero would claim they were neutral. A near-zero xt_total beside a large vaep_total is a finisher, not a bug.

vaep_totalpossession_value.vaep_totalteam + player

Value of every action, weighing what it gained against what it risked.

Two classifiers estimate the probability the team scores within the next 10 actions and the probability it concedes within the same window. An action is worth the rise in the first minus the rise in the second. The game state is three actions: the current one and the two preceding it.

How ours differs

Unlike xT this values every action type, shots and defensive work included. Not comparable with another provider's VAEP — the corpus, the feature set and the action window all differ.

vaep_offensivepossession_value.vaep_offensiveteam + player

The attacking half of the VAEP decomposition.

The change in the team's probability of scoring within the next 10 actions, measured from the state immediately before the action to the state immediately after. Positive when an action improves the attack — a line-breaking pass, a carry into the box — and negative when it squanders one.

How ours differs

Estimated in context, so the same pass is valued differently depending on where the move came from. Two identical passes on a pitch map can carry very different numbers.

vaep_defensivepossession_value.vaep_defensiveteam + player

The risk half: what the action did to the chance of conceding.

The change in the probability the team concedes within the next 10 actions, over the same before-and-after window. A loose pass across your own defensive third carries a large cost even when no goal follows; a tackle that removes danger scores well.

How ours differs

Combines with vaep_offensive so that improving the attack adds value and increasing risk subtracts it. The two do not sum to vaep_total by simple addition of magnitudes — the defensive term is subtracted.

Passing

Volume, accuracy and progression. One overlap to keep in mind throughout: passes counts crosses as well, so the two must never be added together.

passespassing.passesteam + player

Attempted passes, including crosses.

An attempted delivery of the ball from one player to another on the same team. Crosses are stored as a separate action type but are counted here as well.

How ours differs

Because crosses are inside this total, passes and crosses overlap and must never be summed. Short free kicks, throw-ins and corners are separate types and are not folded in, so this reads lower than a source that counts all set pieces as passes.

pass_accuracypassing.pass_accuracyteam + player

Successful passes over attempted, as a percentage.

A pass is successful when the next touch is by a teammate. Null rather than zero when no pass was attempted — an empty denominator has no answer.

How ours differs

The denominator includes crosses, which complete far less often than open-play passes, so this sits below a figure computed on open play alone.

progressive_passespassing.progressive_passesteam + player

Completed open-play passes that moved the ball meaningfully closer to goal.

Follows Opta's published rule: a completed open-play pass beginning in the attacking two thirds that moves the ball at least 25% closer to the centre of the goal. Being proportional it scales naturally — 25% of a 60 m gap is 15 m, 25% of a 20 m gap is 5 m — so no flat distance floor is needed to stay sensible near the box.

How ours differs

Not the FBref metric, which requires 10 yards beyond the furthest point of the last six passes or any pass into the box, and excludes the defending 40% of the pitch. Ours is a strict subset of passes; FBref's is not.

progressive_pass_distancepassing.progressive_pass_distanceteam + player

Metres of goal-ward progress over those passes.

The reduction in straight-line distance to the goal centre, accumulated over qualifying progressive passes. A pass away from goal contributes nothing rather than a negative.

How ours differs

FBref's similarly named column is in yards and sums over all completed passes rather than only progressive ones. Different quantity, different unit.

passes_into_boxpassing.passes_into_boxteam + player

Completed passes ending inside the opponent's penalty area.

Purely geometric on the end coordinate. The ball must enter: a pass beginning inside the box and staying there does not count.

How ours differs

Counts crosses alongside open-play passes, where FBref separates the two and excludes set pieces.

key_passespassing.key_passesteam + player

Passes that directly led to a teammate's shot.

A completed pass paired with the shot immediately following it for the same team, looking through any carry the receiver made in between. Open play, corners and free kicks qualify; throw-ins and goal kicks do not.

How ours differs

This lands on the FBref side of a real split. Opta's key pass is the final pass to a player who then shoots without scoring, treating passes that produced goals as assists instead; ours includes them, which is why chances_created is numerically identical to this. Measured against the underlying feed's own key-pass and assist flags over 1,000 matches it runs about 1.4 low per match.

assistspassing.assiststeam + player

Passes that directly produced a goal.

The final pass before a goal, as flagged by the source event feed rather than derived by us.

through_ballspassing.through_ballsteam + player

Passes that split the defensive line for a runner.

Arrives as a qualifier on the pass event rather than being derived from coordinates.

How ours differs

This is the provider's judgement of what splitting a line means, and cannot be recomputed or audited from the x/y data. Volumes are low and vary by source, so cross-provider comparison is unreliable.

crossespassing.crossesteam + player

Balls played from wide, targeting a teammate centrally near goal.

Stored as its own action type distinct from a pass. A cross can independently satisfy the progressive test and count toward progressive_passes.

How ours differs

Open play only. A crossed corner resolves to a corner and a crossed free kick to a free kick, so neither reaches this count. A source that counts set-piece deliveries as crosses reads roughly 40% higher.

switchespassing.switchesteam + player

Completed passes moving the ball a long way across the pitch.

Identified geometrically from lateral displacement on a 68 m-wide pitch. The threshold is 36.6 m — more than half the width — so a full-back to full-back ball qualifies and a diagonal into the channel does not.

How ours differs

Derived by us: the feed emits no switch-of-play event. FBref uses 40 yards. The threshold moves the count sharply, so it is published rather than implied. Median is about 4 per team per match.

Carrying

Carries are synthesised, not observed. The event feed contains no carry event; one is inserted wherever the ball moves between two consecutive actions by the same team, gated at a 5 m minimum. That is roughly 400 movements per match that do not exist in the source.

carriescarrying.carriesteam + player

Movements of the ball at a player's feet, over 5 metres.

From where the ball was last touched to where the next action by the same team begins, credited to the player of that next action. The 5 m floor follows Opta's published definition of a carry.

How ours differs

Sensitive to event density: a missing touch between two logged actions merges several movements into one long carry. About 95% are credited to someone other than the player of the preceding action, which is the intended case — a receiver carrying from where they received.

carry_distancecarrying.carry_distanceteam + player

Total straight-line metres covered while carrying.

Start-to-end displacement summed over a player's carries.

How ours differs

Net displacement between two recorded events, not the path actually run — a curved or doubling-back run is understated. FBref's comparable column is in yards.

progressive_carriescarrying.progressive_carriesteam + player

Carries that advanced play meaningfully towards goal.

A carry reducing the distance to the goal centre by 30 m in the own half, 15 m crossing the halfway line, or 10 m in the opponent's half.

How ours differs

This is the Wyscout rule, while progressive_passes follows Opta's. The two therefore apply different definitions of progressive — stated rather than hidden. Opta's published carry rule asks only for five metres of gain in the opposition half, which is far more permissive and would raise this count substantially.

progressive_carry_distancecarrying.progressive_carry_distanceteam + player

Metres of goal-ward progress over a player's carries.

The reduction in distance to the goal centre, with movement away from goal contributing nothing rather than a negative.

How ours differs

Inherits the synthesis caveat in full, and is reported in metres against FBref's yards.

carries_into_final_thirdcarrying.carries_into_final_thirdteam + player

Carries that crossed into the attacking third.

The carry must start outside and end inside, crossing x = 70. One that begins and ends there does not count however far it travels.

carries_into_boxcarrying.carries_into_boxteam + player

Carries that entered the opponent's penalty area.

Ends inside the box having started outside it, with the box beginning at x = 88.5 m.

How ours differs

As with every carry metric this infers from where recorded events start and end, which makes it especially sensitive to gaps in event coverage near the box.

take_onscarrying.take_onsteam + player

Attempts to beat an opponent while in possession.

Real recorded events, not reconstructions: a take-on is a duel the provider observed and tagged. Distinct from carrying the ball forward, since it requires an opponent being taken on.

take_ons_woncarrying.take_ons_wonteam + player

Take-ons where the player beat their opponent and kept the ball.

Straight from the source event outcomes. Divided by take_ons this gives the dribble success rate.

miscontrolscarrying.miscontrolsteam + player

A poor touch that lost the ball.

The loss is attributed to the player's own touch rather than to opposition pressure, which is what separates it from being dispossessed.

How ours differs

Own goals are parked on the same underlying action type internally and are excluded here by result. A source that does not filter them counts a handful of own goals as miscontrols.

dispossessedcarrying.dispossessedteam + player

Losing the ball to an opponent without attempting to beat them.

Excludes losses during an attempted take-on, which are failed take-ons, and losses from a poor first touch, which are miscontrols.

Creation

Who made the chance. The expected-goals members here attribute a per-shot xG that the event feed does not itself carry — it is joined in from the shot data, so these are attribution of a provider's number rather than a model of ours.

scacreation.scateam + player

The two offensive actions immediately preceding a shot.

Passes, take-ons, fouls drawn, rebounding shots and ball-winning defensive actions all qualify, regardless of who shoots. Only the final two count, so a player involved three passes earlier gets nothing, and one player can be credited twice in a sequence.

How ours differs

FBref's construction rather than an industry standard, with one difference: our backward walk stops at the edge of the possession containing the shot. Without that bound a shot straight from a turnover is credited to the opponent's build-up.

gcacreation.gcateam + player

The same, restricted to shots that were scored.

The two offensive actions directly leading to a goal, using identical qualifying action types to sca.

How ours differs

Goals are far rarer than shots, so this is extremely noisy over anything less than a full season. Read it alongside sca, never alone.

sca_breakdowncreation.sca_breakdownteam + player

Shot-creating actions split by what kind of action created the shot.

Six categories: pass_live, pass_dead (free kick, corner, throw-in, kick-off, goal kick), take_on, shot (a rebound leading to another shot), foul_drawn and defensive. It answers how a player creates rather than how much.

How ours differs

foul_drawn appears on teams only. A foul is recorded against the offender and never names who won it, so the category belongs to no player — which is why the players' sca sums short of the team's by exactly that amount. Player breakdowns omit categories with no occurrences rather than writing zeros.

second_assistscreation.second_assiststeam + player

The pass before the assist.

A pass or cross instrumental in creating a goalscoring opportunity — a corner to a player who then assists an attempt, or a through ball into a dangerous position.

How ours differs

Anchored to creating an attempt rather than exclusively to a scored goal, and defined judgementally rather than mechanically as the second-to-last pass, so not every goal carries one. FBref publishes no equivalent column.

chances_createdcreation.chances_createdteam + player

Assists plus key passes.

The final pass to a teammate who then attempts a shot, scored or not.

How ours differs

Numerically identical to passing.key_passes, and that follows from a choice made there: our key pass already includes the passes that became goals, so adding assists would double count. Both names are published because both are asked for, but they are one number.

xagcreation.xagteam + player

The expected goals of the shots a player's passes actually produced.

Credited to the passer. On a team it is the xG of that team's assisted shots, which read against the team's total xG says how much of the threat came from combination play rather than from individual efforts, rebounds and penalties.

How ours differs

Not xa. xAG is the xG of a shot that happened; xA is the modelled likelihood that any completed pass becomes an assist, exists whether or not a shot followed, and is not implemented. Penalties are excluded here: a penalty has no assist, and its xG of around 0.76 would otherwise go to whoever passed before the foul.

xg_chaincreation.xg_chainplayer

The full xG of every possession the player took part in.

Find each possession the player touched, sum the xG of all shots in those possessions, and assign the whole sum however peripheral the involvement was.

How ours differs

The value is not divided among participants — the first pass in a thirty-pass build-up earns exactly what the shot earns, which is both the point and the main weakness. Summing players to a team total would multiply one chance by the number who touched it, so there is no team analogue. No cross-provider standard exists for where a chain begins and ends.

xg_buildupcreation.xg_buildupplayer

xg_chain with the shot and the assist removed.

The same possession-chain xG, excluding chains where the player's only contribution was taking the shot or playing the pass that created it. Intended to isolate deep-lying and midfield contribution.

How ours differs

Always less than or equal to xg_chain. StatsBomb, who introduced it, caution that it is neither a replacement for a possession-value model nor a rating of attacking skill.

Defending

Winning the ball back, and how high up the pitch. The first seven members appear on players and teams; the rest describe a team's collective behaviour and exist only at team level.

tacklesdefending.tacklesteam + player

Challenges that dispossessed an opponent.

Taken straight from the source event stream.

interceptionsdefending.interceptionsteam + player

Reading a pass and cutting it out.

Moving into the line of an intended pass and stopping it before it reaches its target.

How ours differs

Runs high against providers that count blocked passes separately: the conversion folds blocked passes into this type. Measured against an independent collector, subtracting blocked passes matched their interception count exactly in every row tested.

blocksdefending.blocksteam + player

Outfield players getting in the way of a shot.

Separated from goalkeeper saves by whether the actor started the match in goal. Both arrive on the same underlying event type, and roughly half of them are outfield blocks.

How ours differs

An external source's blocked shots usually means shots of theirs that were blocked — the opponent's figure, not this one. Compare it inverted.

clearancesdefending.clearancesteam + player

Hoofing the ball away from danger, with no intended recipient.

Counted as a defensive action here, and deliberately excluded from the PPDA denominator.

duels_wondefending.duels_wonteam + player

Contests for the ball that ended in this player's favour.

Ground and aerial contests combined.

aerialsdefending.aerialsteam + player

Contests for the ball in the air.

Every aerial duel the player contested, won or lost.

aerials_wondefending.aerials_wonteam + player

Aerial duels won.

Divided by aerials this gives the aerial success rate.

ppdadefending.ppdateam

Opponent passes allowed per defensive action. Lower is more intense pressing.

Opponent passes attempted divided by fouls, tackles, interceptions, challenges and blocked passes, both counted outside the pressing team's own defensive third.

How ours differs

Clearances and shot blocks are excluded: they are reactions to pressure, not applications of it, and including them inflates the score for deep-sitting teams. The numerator is passes attempted, not completed — the difference is about 18% and a completed-pass numerator reports every team as pressing harder than it does. The pressing zone follows Opta's defensive third rather than the three fifths of the original 2014 definition.

opponent_passesdefending.opponent_passesteam

The PPDA numerator, published on its own.

Passes the opponent attempted inside the pressing zone. Exposed so the ratio can be checked rather than trusted.

defensive_actionsdefending.defensive_actionsteam

The PPDA denominator, published on its own.

Fouls, tackles, interceptions, challenges and blocked passes inside the pressing zone.

challengesdefending.challengesteam

Attempts to win the ball that did not result in a tackle.

Counted off the raw event stream, because this event type has no equivalent action in the converted stream and would otherwise vanish.

avg_defensive_action_xdefending.avg_defensive_action_xteam

How high up the pitch the team defended, in metres.

The mean x coordinate of tackles, interceptions, fouls and clearances, on a 105 m pitch measured from the team's own goal.

How ours differs

This set includes clearances, unlike the PPDA denominator. The two disagree on purpose: a clearance says something about where you defended and nothing about whether you pressed.

high_turnoversdefending.high_turnoversteam

Possessions won within 40 metres of the opponent's goal.

A regain high enough to be an attacking event in its own right.

counterpress_regains_5sdefending.counterpress_regains_5steam

Possessions won back within five seconds of losing them.

Measured on the wall clock, because within five seconds plainly means five real ones rather than five seconds of ball-in-play time.

ball_recovery_timedefending.ball_recovery_timeteam

Average seconds to win the ball back after losing it.

Measured in ball-in-play time, so a long stoppage between the loss and the regain does not count against the team. Null when possession was never lost and regained.

Territory

Where the match was played. Team level only.

possession_pctterritory.possession_pctteam

Share of the ball, as a share of attempted deliveries.

Each team's attempted passes as a proportion of both teams' — the method the API reports as pass share.

How ours differs

Not share of ball-in-play time. Timing possessions runs from first action to last, so a long ball in the air is credited entirely to whoever launched it; measured against published figures that method missed by 4.0 points and inverted which team was dominant in a third of matches. Pass share lands within 0.24 points on the same comparison.

field_tiltterritory.field_tiltteam

Share of final-third possession, not of the whole pitch.

How much of the play in dangerous areas belonged to this team. A side can hold the ball for 60% of a match and still be behind on field tilt.

How ours differs

Null when neither team entered the final third.

final_third_entriesterritory.final_third_entriesteam

Times the ball was carried or passed into the attacking third.

The action must cross x = 70. An action already starting in the final third is not an entry no matter how far it travels.

box_entriesterritory.box_entriesteam

Times the ball entered the opponent's penalty area.

The box is treated as a real rectangle — 16.5 m deep and 40.32 m wide — not as everything beyond x = 88.5.

avg_action_xterritory.avg_action_xteam

The average position of the team's actions, in metres.

Measured from the team's own goal on a 105 m pitch. A blunt but honest summary of where a team operated.

Tempo

How a team moved the ball, and how quickly. Team level only. Sequences are cut inside possessions rather than on the raw event stream — cutting on the raw stream ends a sequence at every opposition touch and roughly halves passes_per_sequence.

passes_per_sequencetempo.passes_per_sequenceteam

Average passes in an uninterrupted passage of play.

A direct read on whether a team builds or goes long. Null when there was no open-play sequence.

sequence_timetempo.sequence_timeteam

Average seconds a passage of play lasted.

Read beside passes_per_sequence: the same pass count over more time is a slower build.

direct_speedtempo.direct_speedteam

Metres of goal-ward progress per second of possession.

How quickly a team moves the ball towards goal once it has it.

How ours differs

Reads at or slightly above the top of the published band for this metric. Stated rather than corrected, because the definition behind the published band is not fully specified.

buildup_attackstempo.buildup_attacksteam

Attacks built through at least 10 passes.

The patient end of the distribution.

direct_attackstempo.direct_attacksteam

Attacks that covered at least half the distance to goal quickly.

The counterpart to buildup_attacks. A match with high counts of both is a match of transitions rather than a contradiction.

Goalkeeping

Present only for the two players who started in goal; null for everyone else. Distinguish an outfield player from a keeper who did nothing by the null, not by the zero — a quiet keeper has zeros.

claimsgoalkeeping.claimsplayer

Crosses the keeper came for and caught.

Success when the keeper holds it, failure when dropped.

How ours differs

Punches, crosses not claimed, smothers and pick-ups are each their own event type and none of them is a claim.

claims_wongoalkeeping.claims_wonplayer

Claims the keeper held.

The successful subset of claims.

claim_rategoalkeeping.claim_rateplayer

Handling reliability under aerial pressure, as a percentage.

Claims won over claims attempted. Null when the keeper came for no cross.

How ours differs

Deliberately not Opta's Catch Success, which puts catches plus punches over every high ball the keeper came for. Our denominator is claims alone. A keeper who rarely comes for crosses can post a high rate on a very small sample.

sweeper_actionsgoalkeeping.sweeper_actionsplayer

Defensive actions taken outside the penalty area.

The keeper acting as the last line behind a high defensive line.

How ours differs

Roughly ten times the count of the provider's dedicated sweeper event, because this is a broader set by definition rather than a disagreement about the same set.

distributionsgoalkeeping.distributionsplayer

Passes and kicks made by the keeper.

Every attempted distribution, from a short roll to a goal kick.

launchesgoalkeeping.launchesplayer

Distributions longer than 36.6 metres.

Goal kicks are included.

launch_pctgoalkeeping.launch_pctplayer

Share of distributions that went long.

Launches over distributions. Null when the keeper attempted no distribution.

avg_pass_lengthgoalkeeping.avg_pass_lengthplayer

Average distribution length in metres.

Read beside launch_pct: a low average with a high launch share means a keeper doing both.

distribution_accuracygoalkeeping.distribution_accuracyplayer

Share of distributions that found a teammate.

Null when nothing was attempted.

How ours differs

Includes long kicks, which complete far less often than short ones, so it sits well below an outfielder's pass accuracy by construction.

Shooting

The one group that does not come from the event feed. These arrive with the underlying match data and are served through the shots and team-stats endpoints rather than through advanced analytics, so the locators below point at those responses.

xgshots[].expected_goalsteam + player

The probability a shot becomes a goal.

Estimated from shot characteristics: location, angle, body part, assist type, pattern of play and defensive pressure.

How ours differs

Every provider trains a different model on different features, so the same shot can carry materially different values elsewhere. Our totals must never be mixed into one calculation with another source's.

npxgstats → Expected goalsteam

The same total with penalties excluded.

A penalty carries a near-fixed 0.76 to 0.79 xG and is won rather than created, so including them inflates and flattens a chance-quality profile.

shotsstats → Shotsteam

Total goal attempts.

On target, off target and blocked by an outfield player.

How ours differs

Own goals are not shots by the scoring player. Providers vary on deflected attempts and on whether a blocked attempt counts as a shot at all.

shots_on_targetstats → Shotsteam

Attempts that would have gone in but for the keeper, plus goals.

Follows the source's convention.

How ours differs

There is no single standard. Opta also counts efforts stopped on the line by a last-man defender while excluding ordinary blocks, and broadcasters draw the line differently again, so this may not match a figure shown elsewhere.

xg_per_shotstats → Expected goalsteam

Average quality of the chances taken.

Total xG divided by shots. It separates volume from selectivity: two sides with identical xG can differ sharply in how they got there.

goals_minus_xgstats → Expected goalsteam

Finishing over- or underperformance.

Goals scored minus expected goals over the same shot set. Positive means more goals than the model expected.

How ours differs

Numerator and denominator must cover the same shots. If the xG figure includes penalties, the goals must too.

big_chancesstats → Big chancesteam

Situations a player would be expected to score from.

Typically a one-on-one, or a close-range attempt with a clear path to goal. Penalties are always big chances.

How ours differs

Not a model output but an editorial classification applied by analysts — a binary human judgement rather than a continuous probability.

big_chances_missedstats → Big chancesteam

Big chances that did not produce a goal.

Uses the broad reading: any big chance not converted.

How ours differs

The convention is unsettled. Opta's published wording is narrower, covering only cases where the player fails to get a shot away at all. Inherits the subjectivity of big_chances either way.

conversion_ratestats → Shotsteam

Goals divided by shots.

Over all attempts, which matches Opta's Shot Conversion and makes this one of the few figures here directly comparable to a published one.

How ours differs

Some sources compute it over shots on target instead, which measures how often an on-frame attempt beats the keeper — a different question.

savesplayers[].stats → top_statsplayer

Shots the keeper stopped from entering the goal.

Any part of the body, facing an intentional attempt from an opponent. Penalties are included.

How ours differs

An intervention by a defender is a block, not a save, and routine collections of harmless balls are excluded.

API reference

Matches

Everything attached to one fixture: the summary, shots, momentum, events, player stats, lineups, head-to-head, and team stats.

GET/v1/matches/{id}

Retrieve a match

The match summary: league, teams, score, status, referee, and stadium.

Path parameters
idstringrequired
Match ID, for example m_B8x2K9
Request
curl https://api.pitchapi.dev/v1/matches/{id} \
  -H "X-API-KEY: $PITCH_KEY"
Response
{
  "data": {
    "id": "m_B8x2K9",
    "league": {
      "id": "l_4Kd0Wq",
      "name": "Premier League",
      "image_url": "https://cdn.pitchapi.dev/leagues/47.webp"
    },
    "home_team": {
      "id": "t_9aB2xQ",
      "name": "Manchester City",
      "image_url": "https://cdn.pitchapi.dev/teams/8456.webp"
    },
    "away_team": {
      "id": "t_2mLp7C",
      "name": "Manchester United",
      "image_url": "https://cdn.pitchapi.dev/teams/10260.webp"
    },
    "date": "2025-11-09",
    "time_utc": "2025-11-09T16:30:00Z",
    "status": "finished",
    "score_home": 3,
    "score_away": 1,
    "round_name": "11",
    "has_playoff": false,
    "referee": "Michael Oliver",
    "stadium": "Etihad Stadium"
  }
}
GET/v1/matches/{id}/shots

List shots

Every shot in the match, grouped by half. Each shot has pitch coordinates for the strike point plus goal-line coordinates for where it crossed or missed.

Path parameters
idstringrequired
Match ID, for example m_B8x2K9
Request
curl https://api.pitchapi.dev/v1/matches/{id}/shots \
  -H "X-API-KEY: $PITCH_KEY"
Response
{
  "data": {
    "match_id": "m_B8x2K9",
    "periods": [
      {
        "period": "FirstHalf",
        "shots": [
          {
            "id": "s_Q1pR6z",
            "player": {
              "id": "p_7YtX4q",
              "name": "E. Haaland",
              "position_id": 4,
              "image_url": "https://cdn.pitchapi.dev/players/994226.webp"
            },
            "team_id": "t_9aB2xQ",
            "x": 88.5,
            "y": 42.3,
            "expected_goals": 0.38,
            "expected_goals_on_target": 0.32,
            "is_on_target": true,
            "goal_crossed_y": 35.98,
            "goal_crossed_z": 0.8,
            "is_inside_box": true,
            "shot_type": "RightFoot",
            "situation": "RegularPlay",
            "minute": 34,
            "event_type": "Goal"
          }
        ]
      }
    ]
  }
}
GET/v1/matches/{id}/shots/{shot_id}

Retrieve a shot

One shot. Shot IDs are match-scoped, so the same value in a different match is a different shot.

Path parameters
idstringrequired
Match ID, for example m_B8x2K9
shot_idstringrequired
Shot ID scoped to the match, for example s_Q1pR6z
Request
curl https://api.pitchapi.dev/v1/matches/{id}/shots/{shot_id} \
  -H "X-API-KEY: $PITCH_KEY"
Response
{
  "data": {
    "id": "s_Q1pR6z",
    "player": { "id": "p_7YtX4q", "name": "E. Haaland" },
    "team_id": "t_9aB2xQ",
    "x": 88.5,
    "y": 42.3,
    "expected_goals": 0.38,
    "is_on_target": true,
    "shot_type": "RightFoot",
    "situation": "RegularPlay",
    "minute": 34,
    "event_type": "Goal"
  }
}
GET/v1/matches/{id}/momentum

Retrieve momentum

A minute-by-minute pressure curve. Positive values favour home, negative favour away. Some matches return an empty array.

Path parameters
idstringrequired
Match ID, for example m_B8x2K9
Request
curl https://api.pitchapi.dev/v1/matches/{id}/momentum \
  -H "X-API-KEY: $PITCH_KEY"
Response
{
  "data": {
    "match_id": "m_B8x2K9",
    "points": [
      { "minute": 1.00, "value": 0.02 },
      { "minute": 2.00, "value": 0.15 },
      { "minute": 34.00, "value": 0.87 },
      { "minute": 45.75, "value": 0.18 }
    ]
  }
}
GET/v1/matches/{id}/events

List events

Goals, cards, subs, and penalties in order, each with the running score at that moment. `period` is inferred from the minute, not reported by upstream, so treat it as a convenience.

Path parameters
idstringrequired
Match ID, for example m_B8x2K9
Request
curl https://api.pitchapi.dev/v1/matches/{id}/events \
  -H "X-API-KEY: $PITCH_KEY"
Response
{
  "data": {
    "match_id": "m_B8x2K9",
    "events": [
      {
        "event_type": "Goal",
        "minute": 34,
        "minute_added": 0,
        "period": "FirstHalf",
        "team_id": "t_9aB2xQ",
        "player": { "id": "p_7YtX4q", "name": "E. Haaland" },
        "score_home": 1,
        "score_away": 0,
        "is_own_goal": false,
        "is_penalty": false
      },
      {
        "event_type": "Substitution",
        "minute": 62,
        "minute_added": 0,
        "period": "SecondHalf",
        "team_id": "t_2mLp7C",
        "player": { "id": "p_3XvN8w", "name": "M. Mount" },
        "sub_in_player": { "id": "p_5RtY2k", "name": "A. Garnacho" },
        "is_own_goal": false,
        "is_penalty": false
      }
    ]
  }
}
GET/v1/matches/{id}/players

List player stats

Per-player stats grouped by category. Which groups appear depends on position and involvement. Read stats by `stat.key`, not the label. When parsing, check `stat.type`: integer and double carry a `value`, fraction types also carry a `total`.

Path parameters
idstringrequired
Match ID, for example m_B8x2K9
Request
curl https://api.pitchapi.dev/v1/matches/{id}/players \
  -H "X-API-KEY: $PITCH_KEY"
Response
{
  "data": [
    {
      "player": {
        "id": "p_7YtX4q",
        "name": "E. Haaland",
        "position_id": 4,
        "image_url": "https://cdn.pitchapi.dev/players/994226.webp"
      },
      "team_id": "t_9aB2xQ",
      "stats": [
        {
          "key": "top_stats",
          "stats": {
            "Rating": {
              "key": "rating_title",
              "stat": { "type": "double", "value": 8.9 }
            },
            "Goals": {
              "key": "goals",
              "stat": { "type": "integer", "value": 2 }
            },
            "Assists": {
              "key": "assists",
              "stat": { "type": "integer", "value": 0 }
            },
            "xG + xA": {
              "key": "xg_and_xa",
              "stat": { "type": "double", "value": 1.95 }
            },
            "Total shots": {
              "key": "total_shots",
              "stat": { "type": "integer", "value": 5 }
            },
            "Shot accuracy": {
              "key": "shot_accuracy",
              "stat": { "type": "fractionWithPercentage", "value": 3, "total": 5 }
            }
          }
        },
        {
          "key": "attack",
          "stats": {
            "Touches": {
              "key": "touches",
              "stat": { "type": "integer", "value": 41 }
            }
          }
        },
        { "key": "duels", "stats": {} },
        { "key": "defense", "stats": {} }
      ]
    }
  ]
}
GET/v1/matches/{id}/players/{player_id}

Retrieve a player's match stats

The stat line for one player. Same grouped shape as the list endpoint.

Path parameters
idstringrequired
Match ID, for example m_B8x2K9
player_idstringrequired
Player ID, for example p_7YtX4q
Request
curl https://api.pitchapi.dev/v1/matches/{id}/players/{player_id} \
  -H "X-API-KEY: $PITCH_KEY"
Response
{
  "data": {
    "player": { "id": "p_7YtX4q", "name": "E. Haaland", "position_id": 4 },
    "team_id": "t_9aB2xQ",
    "position_id": 4,
    "stats": [
      {
        "key": "top_stats",
        "stats": {
          "Rating": {
            "key": "rating_title",
            "stat": { "type": "double", "value": 8.9 }
          },
          "Goals": {
            "key": "goals",
            "stat": { "type": "integer", "value": 2 }
          }
        }
      }
    ]
  }
}
GET/v1/matches/{id}/players/{player_id}/shots

List a player's shots

All of one player's shots in a match. Use this to build a single-player shotmap without filtering the full shot list.

Path parameters
idstringrequired
Match ID, for example m_B8x2K9
player_idstringrequired
Player ID, for example p_7YtX4q
Request
curl https://api.pitchapi.dev/v1/matches/{id}/players/{player_id}/shots \
  -H "X-API-KEY: $PITCH_KEY"
Response
{
  "data": {
    "player": { "id": "p_7YtX4q", "name": "E. Haaland" },
    "shots": [
      {
        "id": "s_Q1pR6z",
        "x": 88.5,
        "y": 42.3,
        "expected_goals": 0.38,
        "is_on_target": true,
        "goal_crossed_y": 35.98,
        "goal_crossed_z": 0.8,
        "shot_type": "RightFoot",
        "minute": 34,
        "event_type": "Goal"
      }
    ]
  }
}
GET/v1/matches/{id}/lineups

Retrieve lineups

Formations, starters, and benches for both sides. Starters carry normalised coordinates for drawing the formation; subs are always zero.

Path parameters
idstringrequired
Match ID, for example m_B8x2K9
Request
curl https://api.pitchapi.dev/v1/matches/{id}/lineups \
  -H "X-API-KEY: $PITCH_KEY"
Response
{
  "data": {
    "match_id": "m_B8x2K9",
    "home_team": { "id": "t_9aB2xQ", "name": "Manchester City" },
    "away_team": { "id": "t_2mLp7C", "name": "Manchester United" },
    "home": {
      "formation": "4-2-3-1",
      "coach": { "name": "Pep Guardiola" },
      "starters": [
        {
          "player_id": "p_7YtX4q",
          "name": "E. Haaland",
          "shirt_number": "9",
          "position_id": 4,
          "is_captain": false,
          "pitch_x": 0.5,
          "pitch_y": 0.89
        }
      ],
      "subs": []
    },
    "away": { "formation": "4-3-3", "starters": [], "subs": [] }
  }
}
GET/v1/matches/{id}/h2h

Retrieve head-to-head

Head-to-head history plus recent and upcoming meetings. Unplayed fixtures have null scores and `finished: false` — check `finished` before trusting a score.

Path parameters
idstringrequired
Match ID, for example m_B8x2K9
Request
curl https://api.pitchapi.dev/v1/matches/{id}/h2h \
  -H "X-API-KEY: $PITCH_KEY"
Response
{
  "data": {
    "match_id": "m_B8x2K9",
    "home_team": { "id": "t_9aB2xQ", "name": "Manchester City" },
    "away_team": { "id": "t_2mLp7C", "name": "Manchester United" },
    "home_wins": 12,
    "draws": 5,
    "away_wins": 7,
    "total_matches": 24,
    "recent_matches": [
      {
        "time_utc": "2025-04-06T15:30:00.000Z",
        "home": { "id": "t_2mLp7C", "name": "Manchester United" },
        "away": { "id": "t_9aB2xQ", "name": "Manchester City" },
        "score_home": 1,
        "score_away": 2,
        "finished": true
      },
      {
        "time_utc": "2026-01-25T14:00:00.000Z",
        "home": { "id": "t_9aB2xQ", "name": "Manchester City" },
        "away": { "id": "t_2mLp7C", "name": "Manchester United" },
        "score_home": null,
        "score_away": null,
        "finished": false
      }
    ]
  }
}
GET/v1/matches/{id}/stats

Retrieve team stats

Team-by-team totals per half and for the full match. Values are strings, so parse them with `format_type`. An empty `format_type` means a plain number. Groups come back in alphabetical order.

Path parameters
idstringrequired
Match ID, for example m_B8x2K9
Request
curl https://api.pitchapi.dev/v1/matches/{id}/stats \
  -H "X-API-KEY: $PITCH_KEY"
Response
{
  "data": {
    "match_id": "m_B8x2K9",
    "home_team": { "id": "t_9aB2xQ", "name": "Marseille" },
    "away_team": { "id": "t_2mLp7C", "name": "Rennes" },
    "periods": [
      {
        "period": "All",
        "groups": [
          {
            "group_name": "Expected goals (xG)",
            "items": [
              {
                "title": "Expected goals (xG)",
                "key": "expected_goals",
                "home": "2.92",
                "away": "2.49",
                "format_type": "double"
              },
              {
                "title": "xG on target (xGOT)",
                "key": "expected_goals_on_target",
                "home": "2.79",
                "away": "1.17",
                "format_type": "double"
              },
              {
                "title": "xG open play",
                "key": "expected_goals_open_play",
                "home": "2.85",
                "away": "2.23",
                "format_type": "double"
              },
              {
                "title": "xG set play",
                "key": "expected_goals_set_play",
                "home": "0.07",
                "away": "0.26",
                "format_type": "double"
              }
            ]
          },
          {
            "group_name": "Top stats",
            "items": [
              {
                "title": "Ball possession",
                "key": "BallPossesion",
                "home": "45",
                "away": "55",
                "format_type": "integer"
              },
              {
                "title": "Accurate passes",
                "key": "accurate_passes",
                "home": "329 (82%)",
                "away": "394 (85%)",
                "format_type": "integerWithPercentage"
              },
              {
                "title": "Touches in opposition box",
                "key": "touches_opp_box",
                "home": "37",
                "away": "51",
                "format_type": ""
              }
            ]
          }
        ]
      }
    ]
  }
}
GET/v1/matches/{id}/advanced

Retrieve advanced team analytics

Team-level analytics derived from the raw event feed: possession value, passing, carrying, creation, defending, territory, and tempo. Exactly two objects, home first. Returns ANALYTICS_UNAVAILABLE for a match we hold but never rated.

Path parameters
idstringrequired
Match ID, for example m_B8x2K9
Request
curl https://api.pitchapi.dev/v1/matches/{id}/advanced \
  -H "X-API-KEY: $PITCH_KEY"
Response
{
  "data": {
    "match_id": "m_B8x2K9",
    "teams": [
      {
        "team": { "id": "t_9aB2xQ", "name": "Manchester City" },
        "actions": 1357,
        "possession_value": {
          "xt_total": 1.216,
          "vaep_total": 0.2989,
          "vaep_offensive": 0.5976,
          "vaep_defensive": -0.2987
        },
        "passing": {
          "passes": 896,
          "pass_accuracy": 89.5,
          "progressive_passes": 62,
          "progressive_pass_distance": 859.9,
          "passes_into_box": 33,
          "key_passes": 26,
          "assists": 1,
          "through_balls": 4,
          "crosses": 21,
          "switches": 8
        },
        "carrying": {
          "carries": 259,
          "carry_distance": 2833.5,
          "progressive_carries": 34,
          "progressive_carry_distance": 1754.7,
          "carries_into_final_third": 28,
          "carries_into_box": 15,
          "take_ons": 30,
          "take_ons_won": 14,
          "miscontrols": 20,
          "dispossessed": 12
        },
        "creation": {
          "sca": 38,
          "gca": 2,
          "chances_created": 26,
          "second_assists": 3,
          "sca_breakdown": {
            "pass_live": 30,
            "pass_dead": 4,
            "take_on": 1,
            "shot": 1,
            "foul_drawn": 2,
            "defensive": 0
          },
          "xag": 1.21
        },
        "defending": {
          "tackles": 11,
          "interceptions": 16,
          "blocks": 5,
          "clearances": 12,
          "duels_won": 20,
          "aerials": 24,
          "aerials_won": 12,
          "ppda": 5.10,
          "opponent_passes": 279,
          "defensive_actions": 55,
          "challenges": 6,
          "avg_defensive_action_x": 33.4,
          "high_turnovers": 10,
          "counterpress_regains_5s": 14,
          "ball_recovery_time": 16.7
        },
        "territory": {
          "possession_pct": 74.2,
          "field_tilt": 75.6,
          "final_third_entries": 126,
          "box_entries": 41,
          "avg_action_x": 55.3
        },
        "tempo": {
          "passes_per_sequence": 5.96,
          "sequence_time": 22.12,
          "direct_speed": 1.35,
          "buildup_attacks": 16,
          "direct_attacks": 9
        }
      }
    ]
  }
}
GET/v1/matches/{id}/advanced/players

List advanced player analytics

The same groups per player, minus the members that only exist for a team. Sorted by possession value descending, falling back to actions. goalkeeping is null for outfield players.

Path parameters
idstringrequired
Match ID, for example m_B8x2K9
Request
curl https://api.pitchapi.dev/v1/matches/{id}/advanced/players \
  -H "X-API-KEY: $PITCH_KEY"
Response
{
  "data": {
    "match_id": "m_B8x2K9",
    "sorted_by": "possession_value.vaep_total",
    "players": [
      {
        "player": { "id": "p_7YtX4q", "name": "E. Haaland", "shirt_number": 9 },
        "team_id": "t_9aB2xQ",
        "minutes_played": 90,
        "actions": 33,
        "possession_value": {
          "xt_total": 0.0051,
          "vaep_total": 0.8104,
          "vaep_offensive": 0.8257,
          "vaep_defensive": -0.0154
        },
        "passing": {
          "passes": 12,
          "pass_accuracy": 75.0,
          "progressive_passes": 1,
          "progressive_pass_distance": 8.4,
          "passes_into_box": 0,
          "key_passes": 2,
          "assists": 0,
          "through_balls": 0,
          "crosses": 0,
          "switches": 0
        },
        "carrying": {
          "carries": 7,
          "carry_distance": 61.3,
          "progressive_carries": 1,
          "progressive_carry_distance": 34.8,
          "carries_into_final_third": 1,
          "carries_into_box": 2,
          "take_ons": 3,
          "take_ons_won": 1,
          "miscontrols": 2,
          "dispossessed": 1
        },
        "creation": {
          "sca": 3,
          "gca": 1,
          "chances_created": 2,
          "second_assists": 0,
          "sca_breakdown": { "pass_live": 2, "take_on": 1 },
          "xag": 0.34,
          "xg_chain": 1.02,
          "xg_buildup": 0.18
        },
        "defending": {
          "tackles": 1,
          "interceptions": 0,
          "blocks": 0,
          "clearances": 1,
          "duels_won": 2,
          "aerials": 6,
          "aerials_won": 3
        },
        "goalkeeping": null
      }
    ]
  }
}
GET/v1/matches/{id}/advanced/players/{player_id}

Retrieve one player's advanced analytics

The same player object as the list endpoint, unwrapped. The example below is a goalkeeper, so it carries the goalkeeping group; the passing, carrying, creation and defending groups are shortened here and hold exactly the fields shown in the list endpoint above. A player who took no part in a match we did rate returns RESOURCE_NOT_FOUND, which is distinct from ANALYTICS_UNAVAILABLE for a match that was never rated.

Path parameters
idstringrequired
Match ID, for example m_B8x2K9
player_idstringrequired
Player ID, for example p_7YtX4q
Request
curl https://api.pitchapi.dev/v1/matches/{id}/advanced/players/{player_id} \
  -H "X-API-KEY: $PITCH_KEY"
Response
{
  "data": {
    "match_id": "m_B8x2K9",
    "player": { "id": "p_3Nc8Vw", "name": "Ederson", "shirt_number": 31 },
    "team_id": "t_9aB2xQ",
    "minutes_played": 90,
    "actions": 41,
    "possession_value": {
      "xt_total": 0.0142,
      "vaep_total": 0.0311,
      "vaep_offensive": 0.0418,
      "vaep_defensive": -0.0107
    },
    "passing": { "passes": 34, "pass_accuracy": 79.4 },
    "carrying": { "carries": 4, "carry_distance": 22.8 },
    "creation": { "sca": 1, "gca": 0, "xag": null },
    "defending": { "tackles": 0, "interceptions": 1 },
    "goalkeeping": {
      "claims": 5,
      "claims_won": 5,
      "claim_rate": 100.0,
      "sweeper_actions": 9,
      "distributions": 38,
      "launches": 14,
      "launch_pct": 36.8,
      "avg_pass_length": 31.4,
      "distribution_accuracy": 64.9
    }
  }
}
API reference

Leagues

The leagues in the catalogue. The free tier serves the five big domestic divisions; Pro serves the rest.

GET/v1/leagues

List leagues

Every league, each with the seasons available and an is_free flag marking the Big 5 that the free tier can reach.

Request
curl https://api.pitchapi.dev/v1/leagues \
  -H "X-API-KEY: $PITCH_KEY"
Response
{
  "data": {
    "leagues": [
      {
        "id": "l_4Kd0Wq",
        "name": "Premier League",
        "country_code": "ENG",
        "image_url": "https://cdn.pitchapi.dev/leagues/47.webp",
        "seasons": ["2025/2026", "2024/2025"],
        "is_free": true
      }
    ]
  }
}
GET/v1/leagues/{id}

Retrieve a league

One league and its current season.

Path parameters
idstringrequired
League ID, for example l_4Kd0Wq
Request
curl https://api.pitchapi.dev/v1/leagues/{id} \
  -H "X-API-KEY: $PITCH_KEY"
Response
{
  "data": {
    "id": "l_4Kd0Wq",
    "name": "Premier League",
    "country_code": "ENG",
    "season": "2025/2026",
    "image_url": "https://cdn.pitchapi.dev/leagues/47.webp"
  }
}
GET/v1/leagues/{id}/matches

List league matches

Fixtures and results for a season. Pass `season` to pick one: 2024/2025 for fall-spring leagues, 2024 for calendar-year leagues. Defaults to the current season.

Path parameters
idstringrequired
League ID, for example l_4Kd0Wq
Query parameters
seasonstring
Season to fetch, for example 2024/2025. Defaults to the current season.
Request
curl https://api.pitchapi.dev/v1/leagues/{id}/matches \
  -H "X-API-KEY: $PITCH_KEY"
Response
{
  "data": {
    "league": {
      "id": "l_4Kd0Wq",
      "name": "Premier League",
      "season": "2025/2026",
      "image_url": "https://cdn.pitchapi.dev/leagues/47.webp"
    },
    "matches": [
      {
        "id": "m_B8x2K9",
        "date": "2025-11-09",
        "time_utc": "2025-11-09T16:30:00Z",
        "status": "finished",
        "home_team": { "id": "t_9aB2xQ", "name": "Manchester City" },
        "away_team": { "id": "t_2mLp7C", "name": "Manchester United" },
        "score_home": 3,
        "score_away": 1
      }
    ]
  }
}
API reference

Teams & players

Reference records for teams and players, resolvable from any ID returned elsewhere in the API.

GET/v1/teams/{id}

Retrieve a team

A team's name and image URL.

Path parameters
idstringrequired
Team ID, for example t_9aB2xQ
Request
curl https://api.pitchapi.dev/v1/teams/{id} \
  -H "X-API-KEY: $PITCH_KEY"
Response
{
  "data": {
    "id": "t_9aB2xQ",
    "name": "Manchester City",
    "image_url": "https://cdn.pitchapi.dev/teams/8456.webp"
  }
}
GET/v1/players/{id}

Retrieve a player

A player's name, shirt number, position, country, and image URL.

Path parameters
idstringrequired
Player ID, for example p_7YtX4q
Request
curl https://api.pitchapi.dev/v1/players/{id} \
  -H "X-API-KEY: $PITCH_KEY"
Response
{
  "data": {
    "id": "p_7YtX4q",
    "name": "Erling Haaland",
    "short_name": "E. Haaland",
    "shirt_number": "9",
    "position_id": 4,
    "country_code": "NOR",
    "image_url": "https://cdn.pitchapi.dev/players/994226.webp"
  }
}
API reference

Schedule

Find matches by date when you don't know the match ID in advance.

GET/v1/date/{date}

List matches by date

The covered matches kicking off that day. The usual entry point for browsing: take a match ID and follow it into the match endpoints. A free key sees only Big-5 matches; a Pro key sees every match.

Path parameters
datestringrequired
Calendar date as YYYY-MM-DD
Request
curl https://api.pitchapi.dev/v1/date/{date} \
  -H "X-API-KEY: $PITCH_KEY"
Response
{
  "data": {
    "date": "2025-11-09",
    "matches": [
      {
        "id": "m_B8x2K9",
        "league": { "id": "l_4Kd0Wq", "name": "Premier League" },
        "home_team": { "id": "t_9aB2xQ", "name": "Manchester City" },
        "away_team": { "id": "t_2mLp7C", "name": "Manchester United" },
        "time_utc": "2025-11-09T16:30:00Z",
        "status": "finished",
        "score_home": 3,
        "score_away": 1
      }
    ]
  }
}

Something missing?

If a field you need is not documented here or a response does not match this reference, let us know. The documentation is part of the product and we treat any mismatch as a bug.

Get your API key