Query Intervals
Query time intervals matching specific conditions using filters. This is the main endpoint for building custom video clips and analysis.
Request
POST /analyses/{id}/query-intervals
Request Body
{
"operator": "AND",
"pad_sec": 5,
"filters": [
{
"type": "team_possession",
"teams": ["team_0"]
},
{
"type": "ball_location",
"x_min": 70,
"x_max": 105
}
],
"existing_filters": [],
"gap_fill_sec": 5
}
Parameters
| Parameter | Type | Default | Description |
|---|---|---|---|
operator |
string | "AND" |
How to combine filters: "AND" or "OR" |
pad_sec |
integer | 5 |
Padding around each interval (seconds) |
merge_results |
boolean | true |
Merge overlapping intervals. Set to false to keep each event as a separate interval. |
include_pixel_coords |
boolean | false |
Include player pixel coordinates for filters that return an actor (ball_action, event_property), sampled at 4 FPS. |
gap_fill_sec |
integer | 5 |
Merge intervals within this gap (seconds) |
preserve_labels |
string | null |
Preserve labels from this filter type when combining with AND. See Preserve Labels. |
filters |
array | [] |
List of filter conditions |
existing_filters |
array | [] |
Pre-defined time windows |
Response
[
{
"start": 125.5,
"end": 142.3,
"label": "Team A",
"type": "team_possession"
},
{
"start": 380.2,
"end": 395.8,
"label": "Team A",
"type": "team_possession"
}
]
Response Fields
| Field | Type | Description |
|---|---|---|
start |
float | Start time in seconds |
end |
float | End time in seconds |
label |
string | Description of the interval |
type |
string | Primary filter type |
Filter Types
| Type | Description | Documentation |
|---|---|---|
event_property |
Query events by metadata (v3+) | Details |
frame_state |
Player physical state (v3+) | Details |
ball_location |
Ball in pitch zone | Details |
player_ball_proximity |
Player near ball | Details |
team_possession |
Ball controlled by team | Details |
player_possession |
Player has ball | Details |
match_timeline |
Game period | Details |
scene |
Analysable footage | Details |
ball_action |
Pass, drive, shot (legacy) | Details |
Combining Filters
AND (Intersection)
All conditions must be true simultaneously:
{
"operator": "AND",
"filters": [
{ "type": "team_possession", "teams": ["team_0"] },
{ "type": "ball_location", "x_min": 70, "x_max": 105 }
]
}
Result: Moments when Team 0 has the ball AND it's in their attacking third.
OR (Union)
Any condition can be true:
{
"operator": "OR",
"filters": [
{ "type": "ball_location", "x_min": 0, "x_max": 16.5 },
{ "type": "ball_location", "x_min": 88.5, "x_max": 105 }
]
}
Result: Moments when ball is in either penalty area.
Existing Filters
Constrain results to specific time windows:
{
"filters": [
{ "type": "team_possession", "teams": ["team_0"] }
],
"existing_filters": [
{ "start": 0, "end": 600, "name": "First 10 minutes" },
{ "start": 2700, "end": 3300, "name": "Last 10 min of first half" }
]
}
Padding
The pad_sec parameter adds context around each interval:
pad_sec: 5→ Each clip starts 5 seconds early, ends 5 seconds late- Adjacent intervals are merged after padding (unless
merge_results: false) - Padding is clamped to video boundaries
Merge Results
By default, overlapping intervals are merged into a single clip. Set merge_results: false to keep each event as a separate interval:
{
"merge_results": false,
"filters": [
{ "type": "ball_action", "actions": ["pass"] }
]
}
This is particularly useful for ball action filters where you want each pass, drive, or shot as a distinct interval rather than having consecutive actions merged together.
Preserve Labels
When combining multiple filters with AND, all results are normally labelled "combined" / "filter" because the original filter identity is lost during intersection. Set preserve_labels to the filter type whose labels you want to keep.
{
"operator": "AND",
"preserve_labels": "ball_action",
"filters": [
{ "type": "ball_action", "actions": ["shot", "drive", "pass"], "teams": ["team_1"] },
{ "type": "ball_location", "team_side_perspective": "team_1", "x_min": 52.5, "x_max": 105 }
]
}
Without preserve_labels — every interval returns "label": "filter", "type": "combined".
With preserve_labels: "ball_action" — each interval keeps its original label and metadata:
[
{ "start": 12.5, "end": 12.5, "label": "pass", "type": "ball_action", "player_id": "0-7", "team_id": "team_0" },
{ "start": 38.2, "end": 38.2, "label": "shot", "type": "ball_action", "player_id": "0-9", "team_id": "team_0" },
{ "start": 90.1, "end": 90.1, "label": "drive", "type": "ball_action", "player_id": "0-7", "team_id": "team_0" }
]
This works with any filter type as the primary label source (e.g. "team_possession", "event_property"). Only one filter type can be specified. When using OR operator, labels are already preserved by default so this parameter has no effect.
Examples
Team Attacks
Find all moments when Team A attacks in the final third:
{
"operator": "AND",
"pad_sec": 3,
"filters": [
{ "type": "team_possession", "teams": ["team_0"] },
{ "type": "ball_location", "x_min": 70, "x_max": 105, "y_min": 0, "y_max": 68 },
{ "type": "scene", "is_analysable": true }
]
}
Player Highlight Reel
Find all moments when player #10 has the ball:
{
"filters": [
{ "type": "player_possession", "players": ["0-10"] }
],
"pad_sec": 5
}
Penalty Area Action
Find all action in both penalty areas:
{
"operator": "OR",
"pad_sec": 3,
"filters": [
{ "type": "ball_location", "x_min": 0, "x_max": 16.5, "y_min": 13.85, "y_max": 54.15 },
{ "type": "ball_location", "x_min": 88.5, "x_max": 105, "y_min": 13.85, "y_max": 54.15 }
]
}
Example Requests
curl -X POST "https://aiontheball.nl/api/v1/analyses/1246/query-intervals" \
-H "Authorization: Bearer YOUR_API_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"operator": "AND",
"filters": [
{"type": "team_possession", "teams": ["team_0"]},
{"type": "ball_location", "x_min": 70, "x_max": 105}
]
}'
import requests
response = requests.post(
'https://aiontheball.nl/api/v1/analyses/1246/query-intervals',
headers={
'Authorization': f'Bearer {token}',
'Content-Type': 'application/json'
},
json={
'operator': 'AND',
'pad_sec': 5,
'filters': [
{'type': 'team_possession', 'teams': ['team_0']},
{'type': 'ball_location', 'x_min': 70, 'x_max': 105}
]
}
)
intervals = response.json()
total_time = sum(i['end'] - i['start'] for i in intervals)
print(f"Found {len(intervals)} clips, {total_time:.1f}s total")
const response = await fetch(
'https://aiontheball.nl/api/v1/analyses/1246/query-intervals',
{
method: 'POST',
headers: {
'Authorization': `Bearer ${token}`,
'Content-Type': 'application/json'
},
body: JSON.stringify({
operator: 'AND',
filters: [
{ type: 'team_possession', teams: ['team_0'] },
{ type: 'ball_location', x_min: 70, x_max: 105 }
]
})
}
);
const intervals = await response.json();