Player Report
Match report for one player: coverage, physical output, positioning, on-ball actions and interactions with other players.
Request
GET /analyses/{id}/player-report/{player_id}
| Parameter | Type | Required | Description |
|---|---|---|---|
id |
integer | Yes | Analysis ID |
player_id |
string | Yes | Player ID, e.g. 0-10 or 1-GK |
404 when the player was never detected, when the id is not <team>-<jersey>, or when the team is not 0 or 1:
{ "detail": "No report data for player 0-7" }
Summary and Team Report list the players an analysis has.
The first request computes the report and caches it server-side; later requests are fast. The response is deterministic per (analysis_id, player_id, schema_version), which is a safe client-side cache key. Measured detection coverage and interactions.link_up need engine version 0.5.44 or later; older analyses return the fallbacks described per field.
Response
{
"schema_version": 1,
"engine_version": "0.5.44",
"player_id": "0-10",
"team": 0,
"jersey": 10,
"label": "10",
"role": "player",
"coordinate_frame": "attacking",
"orientation_source": "game_meta",
"params": {
"sample_interval_ms": 40,
"sprint_min_kmh": 25.2,
"sprint_min_s": 1.0,
"top_speed_percentile": 0.99,
"reliable_seconds_floor": 600.0,
"hull_central_fraction": 0.8,
"max_sample_gap_ms": 400,
"sequence_gap_ms": 5000,
"sequence_shot_slack_ms": 1000,
"pitch": { "length_m": 105.0, "width_m": 68.0 }
},
"coverage": {
"seconds_detected": 4980.4,
"seconds_projected": 512.0,
"overlap_share": 0.081,
"first_ms": 40,
"last_ms": 5687240,
"reliable": true,
"source": "coverage_artifact"
},
"card": {
"hero_crop_index": 0,
"kit": {
"primary": "red",
"primary_rgb": [200, 16, 46],
"secondary": "white",
"secondary_rgb": [255, 255, 255],
"pattern": "solid"
}
},
"physical": {
"source": "engine",
"distance_m": 10542.5,
"top_speed_kmh": 26.81,
"speed_zones": [
{ "name": "walking", "min_kmh": 0.0, "max_kmh": 7.0, "seconds": 2922.6, "distance_m": 3110.9 },
{ "name": "jogging", "min_kmh": 7.0, "max_kmh": 14.0, "seconds": 1425.3, "distance_m": 4038.0 },
{ "name": "running", "min_kmh": 14.0, "max_kmh": 21.0, "seconds": 452.1, "distance_m": 2172.8 },
{ "name": "high_intensity", "min_kmh": 21.0, "max_kmh": 25.2, "seconds": 110.3, "distance_m": 704.8 },
{ "name": "sprinting", "min_kmh": 25.2, "max_kmh": null, "seconds": 70.1, "distance_m": 516.0 }
],
"distance_per_bucket": [
{ "start_min": 0, "end_min": 15, "distance_m": 1822.1 },
{ "start_min": 15, "end_min": 30, "distance_m": 1749.5 },
{ "start_min": 30, "end_min": 45, "distance_m": 1669.4 },
{ "start_min": 45, "end_min": 60, "distance_m": 1744.2 },
{ "start_min": 60, "end_min": 75, "distance_m": 1693.4 },
{ "start_min": 75, "end_min": 90, "distance_m": 1563.9 },
{ "start_min": 90, "end_min": 105, "distance_m": 300.0 }
],
"sprints": [
{
"t0_ms": 1002280,
"t1_ms": 1003320,
"start": { "x": 62.8, "y": 10.3 },
"end": { "x": 71.3, "y": 12.2 },
"peak_kmh": 29.2
},
{
"t0_ms": 1353760,
"t1_ms": 1355360,
"start": { "x": 50.6, "y": 35.7 },
"end": { "x": 61.1, "y": 40.9 },
"peak_kmh": 27.97
}
// ... 7 sprints in total
]
},
"position": {
"heatmap": {
"bins": [21, 14],
"extent": [[0.0, 105.0], [0.0, 68.0]],
"counts": [
[0, 0, 4, 11, 26, 38, 51, 44, 30, 19, 8, 2, 0, 0],
[0, 2, 9, 24, 47, 66, 88, 79, 55, 31, 14, 5, 1, 0]
// ... 21 rows in total, one per x bin
]
},
"heatmap_with_ball": {
"bins": [21, 14],
"extent": [[0.0, 105.0], [0.0, 68.0]],
"counts": [
[0, 0, 1, 4, 9, 14, 18, 15, 11, 6, 2, 0, 0, 0]
// ... 21 rows
]
},
"avg_position": [
{ "period": 1, "position": { "x": 58.4, "y": 39.6 }, "n_frames": 61240 },
{ "period": 2, "position": { "x": 61.9, "y": 41.6 }, "n_frames": 63270 }
],
"zone_hull": [
{ "x": 34.1, "y": 22.2 },
{ "x": 41.8, "y": 16.4 },
{ "x": 72.6, "y": 19.9 },
{ "x": 80.3, "y": 44.7 }
// ... one point per hull vertex, in order
],
"third_timeline": [
{ "minute": 0, "third": "middle" },
{ "minute": 1, "third": "attacking" },
{ "minute": 2, "third": "middle" }
// ... one entry per minute the player was detected in
],
"ball_relative_heatmap": {
"bins": [21, 14],
"extent": [[-105.0, 105.0], [-68.0, 68.0]],
"counts": [
[0, 0, 0, 2, 15, 44, 91, 88, 39, 12, 3, 0, 0, 0]
// ... 21 rows
]
}
},
"on_ball": {
"passes": [
{
"event_id": "06a0765b-1b30-7a1b-8000-1f7595b1f2f7",
"t_ms": 1631160,
"x": 44.8,
"y": 9.7,
"x_target": 61.2,
"y_target": 24.5,
"length_m": 22.1,
"receiver_player_id": "0-9",
"receiver_team": 0,
"set_piece": "open_play",
"pass_type": null,
"body_part": "unknown"
},
{
"event_id": "06a0765b-2431-7188-8000-e8224979b8ad",
"t_ms": 1945200,
"x": 80.3,
"y": 62.1,
"x_target": 92.7,
"y_target": 38.4,
"length_m": 26.8,
"receiver_player_id": "0-11",
"receiver_team": 0,
"set_piece": "open_play",
"pass_type": "cross",
"body_part": "unknown"
}
// ... 41 passes in total
],
"shots": [
{
"event_id": "06a0765b-5295-7d33-8000-b561079c5084",
"t_ms": 5811200,
"x": 92.4,
"y": 31.8,
"distance_to_goal_m": 12.8,
"body_part": "unknown"
}
],
"carries": [
{
"event_id": "06a0765b-1b2c-7f1b-8000-cda285698ff6",
"t_ms": 1641760,
"x": 60.5,
"y": 19.4,
"x_target": 68.9,
"y_target": 22.7,
"length_m": 9.0
}
// ... 9 carries in total
],
"tackles": [
{ "event_id": "06a0765b-3a11-7c04-8000-9a1e17f0b2c6", "t_ms": 2247080, "x": 38.2, "y": 23.0 }
],
"blocks": [
{ "event_id": "06a0765b-4102-7bb9-8000-2d0c6b74a1e5", "t_ms": 3631760, "x": 26.4, "y": 30.1 }
],
"network": [
{ "player_id": "0-9", "n": 12 },
{ "player_id": "0-11", "n": 8 },
{ "player_id": "0-4", "n": 5 }
// ... one entry per identified receiver, 9 in total
],
"touches": 215,
"touch_grid": {
"bins": [3, 3],
"extent": [[0.0, 105.0], [0.0, 68.0]],
"counts": [
[7, 12, 9],
[21, 46, 26],
[18, 52, 22]
]
},
"involvement": {
"n_spans": 215,
"spans_per_min": 4.56,
"spans": [
{ "t0_ms": 23240, "t1_ms": 23240 },
{ "t0_ms": 23680, "t1_ms": 23880 }
// ... 215 spans in total
]
},
"best_sequence": {
"spans": [
{ "player_id": "0-18", "t0_ms": 5794520, "t1_ms": 5794640 },
{ "player_id": "0-3", "t0_ms": 5795440, "t1_ms": 5796280 },
{ "player_id": "0-10", "t0_ms": 5796680, "t1_ms": 5799760 }
// ... one span per touch in the move, in time order
],
"shot_event_id": "06a0765b-5295-7d33-8000-b561079c5084",
"shot_t_ms": 5811200
}
},
"interactions": {
"pressure_received": {
"mean_m": 4.25,
"p10_m": 1.76,
"n_frames": 3827,
"by_third": [
{ "third": "defensive", "mean_m": 4.08, "n": 1177 },
{ "third": "middle", "mean_m": 4.62, "n": 1716 },
{ "third": "attacking", "mean_m": 3.8, "n": 934 }
]
},
"pressure_applied": {
"n_possessions": 279,
"heatmap": {
"bins": [21, 14],
"extent": [[0.0, 105.0], [0.0, 68.0]],
"counts": [
[0, 0, 0, 1, 6, 11, 14, 9, 4, 1, 0, 0, 0, 0]
// ... 21 rows
]
}
},
"direct_opponent": [
{ "player_id": "1-5", "share": 0.204 },
{ "player_id": "1-2", "share": 0.169 },
{ "player_id": "1-9", "share": 0.123 }
],
"link_up": [
{ "player_id": "0-9", "share": 0.181 },
{ "player_id": "0-4", "share": 0.142 },
{ "player_id": "0-11", "share": 0.118 }
]
}
}
Top level
| Field | Type | Description |
|---|---|---|
schema_version |
integer | Version of this response shape. See Schema version |
engine_version |
string | Pipeline version the analysis was produced with |
player_id |
string | Requested player ID |
team |
integer | 0 or 1, the shirt-cluster label. Resolve to home/away via Teams & Sides |
jersey |
int | null | Shirt number. Null for non-numeric ids such as 0-GK |
label |
string | Id suffix, e.g. "10" or "GK" |
role |
string | goalkeeper or player |
coordinate_frame |
string | attacking, attacking_first_half or camera. See Coordinates |
orientation_source |
string | null | Where the attacking direction came from: hub (the video's left_start_team), game_meta (engine record) or tracking (inferred from the ends each team occupied). Null when none could resolve it; coordinate_frame is then camera |
params
Thresholds the report was computed with. Read zone edges, bucket widths and the reliability floor from here rather than hardcoding them.
| Field | Type | Unit | Description |
|---|---|---|---|
sample_interval_ms |
integer | ms | Time one detected sample represents (40 at 25 FPS) |
sprint_min_kmh |
float | km/h | Speed a run must reach to count as a sprint |
sprint_min_s |
float | s | Time that speed must be sustained |
top_speed_percentile |
float | Percentile of the speed distribution reported as top_speed_kmh |
|
reliable_seconds_floor |
float | s | Detected time required for coverage.reliable |
hull_central_fraction |
float | Fraction of positions kept when building position.zone_hull |
|
max_sample_gap_ms |
integer | ms | A larger gap between samples is a detection break, not play |
sequence_gap_ms |
integer | ms | Maximum gap between two possessions in one on_ball.best_sequence |
sequence_shot_slack_ms |
integer | ms | Maximum time a possession may close after a shot and still precede it in on_ball.best_sequence |
pitch |
object | m | {length_m, width_m} of the pitch model |
coverage
How much of the match the player was observed for. Every other section is computed over detected time only.
| Field | Type | Unit | Description |
|---|---|---|---|
seconds_detected |
float | s | Time the player was seen on camera |
seconds_projected |
float | s | Additional time the position was estimated while off camera. 0.0 when the analysis carries no projection |
overlap_share |
float | null | Fraction (0-1) of detected time where the player overlapped another player. Null when the analysis has no overlap information (tracking without a per-row status, before 0.5.36) |
|
first_ms |
int | null | ms | First detection. Null when never seen |
last_ms |
int | null | ms | Last detection. Null when never seen |
reliable |
boolean | seconds_detected >= params.reliable_seconds_floor. False means grey the stats out. |
|
source |
string | coverage_artifact (measured) or tracking_fallback (counted from detected tracking rows, on analyses without the coverage measurement) |
Warning
Physical totals are sums over detected time. A substitute or a player far from the camera has a lower distance_m for that reason alone. Show seconds_detected next to any total.
card
| Field | Type | Description |
|---|---|---|
hero_crop_index |
int | null | Index of a representative image, for GET /analyses/{id}/exemplar-crops/individuals/{player_id}/{hero_crop_index} (Exemplar Crops). Null when the analysis ships no crops |
kit |
object | null | Kit colours of the player's team. Null when the analysis carries no kit read |
Kit object
| Field | Type | Description |
|---|---|---|
primary |
string | Primary shirt colour name, from the same palette as Preview Teams |
primary_rgb |
[int, int, int] | null | [r, g, b], 0-255. Null when the name has no RGB mapping |
secondary |
string | null | Secondary colour name. Null on a single-colour kit |
secondary_rgb |
[int, int, int] | null | [r, g, b], 0-255 |
pattern |
string | null | e.g. solid, stripes |
physical
Distance and speed over detected samples only. Distance is sum(speed) * sample_interval, so a detection gap contributes nothing. Speed units and zone definitions are shared with Summary; see Speed & Distance for how the zones map onto the Summary buckets.
| Field | Type | Unit | Description |
|---|---|---|---|
source |
string | engine (pipeline frame_state speed) or tracking_fallback (insight's own smoother over tracking positions, on analyses without frame_state; treat as indicative). Independent of coverage.source |
|
distance_m |
float | m | Total distance |
top_speed_kmh |
float | km/h | params.top_speed_percentile percentile over every detected sample, standing still included. Lower than the fastest sprints[].peak_kmh. 0.0 when no speed data exists |
speed_zones |
array | Always 5 entries, low to high | |
distance_per_bucket |
array | Distance per 15-minute bucket, contiguous from minute 0, gaps as 0.0. [] when no speed data exists |
|
sprints |
array | Runs at or above params.sprint_min_kmh for at least params.sprint_min_s. [] when none qualify |
Speed zone object
| Field | Type | Unit | Description |
|---|---|---|---|
name |
string | walking, jogging, running, high_intensity, sprinting |
|
min_kmh |
float | km/h | Lower bound, inclusive |
max_kmh |
float | null | km/h | Upper bound, exclusive. Null on the last zone |
seconds |
float | s | Time in the zone |
distance_m |
float | m | Distance in the zone |
The zones partition the speed series: distance_m over the five zones sums to physical.distance_m, and seconds sums to coverage.seconds_detected minus samples without a speed reading.
Fatigue bucket object
| Field | Type | Unit | Description |
|---|---|---|---|
start_min |
integer | min | Bucket start |
end_min |
integer | min | Bucket end |
distance_m |
float | m | Distance inside the bucket |
Sprint object
| Field | Type | Unit | Description |
|---|---|---|---|
t0_ms |
integer | ms | Start |
t1_ms |
integer | ms | End |
start |
{x, y} | null | m | Position at start. Null when that frame has no position |
end |
{x, y} | null | m | Position at end. Null when that frame has no position |
peak_kmh |
float | km/h | Fastest speed in the run |
position
| Field | Type | Description |
|---|---|---|
heatmap |
Grid | Position over all detected frames. All-zero when there are none |
heatmap_with_ball |
Grid | null | Position over frames where the player's team had possession. Null when team possession is unavailable or never overlaps the player's frames |
avg_position |
array | Mean position per half. [] when there are no frames |
zone_hull |
[{x, y}] | Convex hull of the central params.hull_central_fraction of positions, as an ordered polygon in metres. [] or fewer than 3 points when there is too little data |
third_timeline |
array | Pitch third the player spent most of each minute in. Minutes without detections are omitted |
ball_relative_heatmap |
Grid | null | Player position minus ball position over frames where both are known, extent [[-105, 105], [-68, 68]]. Null when the analysis has no ball track |
Average position object
| Field | Type | Unit | Description |
|---|---|---|---|
period |
integer | 1 or 2. A single period: 1 entry covers the whole match when coordinate_frame is attacking_first_half |
|
position |
{x, y} | m | Mean position over the period |
n_frames |
integer | Frames behind the mean |
Third timeline object
| Field | Type | Description |
|---|---|---|
minute |
integer | Match minute, from 0 |
third |
string | defensive, middle or attacking, relative to the player's attacking direction |
on_ball
Actions where this player was the identified actor. Arrays are sorted by time and [] when nothing was detected. Every action row carries t_ms and the event_id of the underlying event.
| Field | Type | Description |
|---|---|---|
passes |
array | Pass objects |
shots |
array | Shot objects |
carries |
array | Carry objects (drive events) |
tackles |
array | Point objects |
blocks |
array | Point objects |
network |
array | {player_id, n} pass counts per receiver, most first. [] when no receiver could be resolved |
touches |
integer | Times the player had the ball. Equal to involvement.n_spans |
touch_grid |
Grid | 3x3 count of touches. Sums to at most touches; a touch positioned outside the pitch is dropped |
involvement |
object | Touch rate and spans |
best_sequence |
object | null | Longest same-team move ending in a shot that includes this player. Null when there is none |
Pass object
| Field | Type | Unit | Description |
|---|---|---|---|
event_id |
string | null | Id of the underlying event. Null on analyses whose event export predates event ids | |
t_ms |
integer | ms | Time of the pass |
x, y |
float | null | m | Origin |
x_target, y_target |
float | null | m | Destination. Null when not measured |
length_m |
float | null | m | Null when the destination was not measured |
receiver_player_id |
string | null | Player that next had the ball. Null when not identified, including passes that went out of play | |
receiver_team |
int | null | 0 or 1. Differs from the top-level team on a turnover. Null when not resolved |
|
set_piece |
string | null | Restart context, e.g. open_play, throw_in, corner_kick. See Pass |
|
pass_type |
string | null | e.g. cross. Null for a plain pass |
|
body_part |
string | null | e.g. head. unknown when not measured |
Warning
receiver_player_id is an observation from the possession stream, not an adjudicated outcome. Do not derive a pass completion percentage from it.
Shot object
| Field | Type | Unit | Description |
|---|---|---|---|
event_id |
string | null | Id of the underlying event | |
t_ms |
integer | ms | Time of the shot |
x, y |
float | null | m | Origin |
distance_to_goal_m |
float | null | m | Straight-line distance to the centre of the attacked goal. Null when the shot has no position or when coordinate_frame is not attacking |
body_part |
string | null | See Shot |
Carry object
| Field | Type | Unit | Description |
|---|---|---|---|
event_id |
string | null | Id of the underlying event | |
t_ms |
integer | ms | Start of the carry |
x, y |
float | null | m | Start |
x_target, y_target |
float | null | m | End. Null when not measured |
length_m |
float | null | m | Null when the end was not measured |
Point object (tackles, blocks)
| Field | Type | Unit | Description |
|---|---|---|---|
event_id |
string | null | Id of the underlying event | |
t_ms |
integer | ms | Time |
x, y |
float | null | m | Position |
Involvement object
| Field | Type | Unit | Description |
|---|---|---|---|
n_spans |
integer | Times the player had the ball | |
spans_per_min |
float | null | Touches per minute of the player's team's possession. Null when team possession is unavailable | |
spans |
array | {t0_ms, t1_ms} per touch, in time order, half-open [t0_ms, t1_ms). A single-frame touch has t0_ms == t1_ms |
Best sequence object
| Field | Type | Description |
|---|---|---|
spans |
array | {player_id, t0_ms, t1_ms} per touch in the move, in time order, teammates included |
shot_event_id |
string | null | Id of the shot that ended the move |
shot_t_ms |
integer | Time of the shot. spans[0].t0_ms to shot_t_ms is the clip window |
interactions
Nearest-player relationships, computed per frame from the players visible on camera. Off-camera players cannot be nearest to anyone, so these numbers lean toward the camera's half of the pitch and are better compared within one match than across matches.
| Field | Type | Description |
|---|---|---|
pressure_received |
object | null | Marking distance while on the ball. Null when there is no per-frame distance data or the player never had the ball |
pressure_applied |
object | null | Frames where the player was the closest defender to an opponent in possession. Null when there is no per-frame distance data |
direct_opponent |
array | Up to 3 {player_id, share}, opponents the player was nearest to most often. [] when there is no per-frame distance data |
link_up |
array | Up to 3 {player_id, share}, teammates the player was nearest to most often. [] before engine 0.5.44 |
share is a fraction (0-1) of frames. The entries are the top of a longer distribution and do not sum to 1.
Pressure received object
| Field | Type | Unit | Description |
|---|---|---|---|
mean_m |
float | m | Mean distance to the nearest opponent over frames where the player had the ball |
p10_m |
float | m | 10th percentile of that distance |
n_frames |
integer | Frames behind the numbers | |
by_third |
array | Always 3 entries {third, mean_m, n} for defensive, middle, attacking. mean_m is null where n is 0 |
Pressure applied object
| Field | Type | Description |
|---|---|---|
n_possessions |
integer | Opponent possessions during which this player was the ball holder's nearest opponent |
heatmap |
Grid | null | Position while applying that pressure. Null when no such frames exist |
Coordinates
Coordinates are metres in x / y fields, points are {x, y}, polygons are lists of points, and span destinations are x_target / y_target. They are rotated so that the player's team attacks from x = 0 towards x = 105 in both halves. coordinate_frame states how far that rotation could be resolved:
| Value | Meaning |
|---|---|
attacking |
Both halves oriented for the player's team |
attacking_first_half |
The first-half direction applied to the whole match because the moment the teams changed ends could not be located. Second-half coordinates are mirrored |
camera |
No attacking direction. Raw camera-frame coordinates as in Pitch Coordinates |
Warning
Only attacking is safe to render as-is. In camera, thirds and distance_to_goal_m do not refer to the attacked goal. Query Intervals returns the camera frame by default; request coordinate_frame: "attacking" there to compare positions with this report. See Coordinate frames.
Time
Timestamps are milliseconds from match start in fields ending _ms. Divide by 1000 to align with the start / end seconds of Events and Query Intervals. event_id is the join key to the Events response.
Grid object
Every heatmap (position.*, on_ball.touch_grid, interactions.pressure_applied.heatmap) has this shape.
| Field | Type | Description |
|---|---|---|
bins |
[int, int] | [nx, ny] |
extent |
[[float, float], [float, float]] | [[x_min, x_max], [y_min, y_max]] in metres |
counts |
int[nx][ny] | Frame counts. Row index is the x bin, column index the y bin, low first |
Bin width is (x_max - x_min) / nx; height is (y_max - y_min) / ny. Multiply a count by params.sample_interval_ms for dwell time.
Schema version
schema_version covers the response shape and the thresholds in params; it is bumped when a field is added, removed or renamed, or a threshold changes. Currently 1. Key caches on (analysis_id, player_id, schema_version).
Warning
Actions are attributed only when the actor was identified, so passes, shots, carries, tackles and blocks are lower bounds. Present them as detected counts alongside coverage.