curl --request POST \
--url https://api.hooper.gg/v1/sessions \
--header 'Authorization: Bearer <token>' \
--header 'Content-Type: application/json' \
--data '
{
"source": {
"upload_id": "up_3f9a1c2b7e5d4a08",
"url": "https://app.veo.co/matches/20260826-acme-vs-north/"
},
"group": "grp_412",
"metadata": {
"external_match_id": "abc-123"
},
"options": {
"session_type": "5v5",
"session_mode": "game",
"camera_setup": "stationary",
"jerseys": "auto",
"has_jerseys": true,
"court_type": "ncaa_men",
"point_system": 1,
"recording_setup": "tripod",
"num_players": 5,
"model": "flash",
"recording_team_name": "Acme Varsity",
"opponent_team_name": "North High"
},
"branding": {
"cover": {
"url": "https://cdn.example.com/brand/logo.png",
"upload_id": "up_7c1e4b2a9d3f6e05"
},
"logo": {
"url": "https://cdn.example.com/brand/logo.png",
"upload_id": "up_7c1e4b2a9d3f6e05"
}
},
"rosters": {}
}
'import requests
url = "https://api.hooper.gg/v1/sessions"
payload = {
"source": {
"upload_id": "up_3f9a1c2b7e5d4a08",
"url": "https://app.veo.co/matches/20260826-acme-vs-north/"
},
"group": "grp_412",
"metadata": { "external_match_id": "abc-123" },
"options": {
"session_type": "5v5",
"session_mode": "game",
"camera_setup": "stationary",
"jerseys": "auto",
"has_jerseys": True,
"court_type": "ncaa_men",
"point_system": 1,
"recording_setup": "tripod",
"num_players": 5,
"model": "flash",
"recording_team_name": "Acme Varsity",
"opponent_team_name": "North High"
},
"branding": {
"cover": {
"url": "https://cdn.example.com/brand/logo.png",
"upload_id": "up_7c1e4b2a9d3f6e05"
},
"logo": {
"url": "https://cdn.example.com/brand/logo.png",
"upload_id": "up_7c1e4b2a9d3f6e05"
}
},
"rosters": {}
}
headers = {
"Authorization": "Bearer <token>",
"Content-Type": "application/json"
}
response = requests.post(url, json=payload, headers=headers)
print(response.text)const options = {
method: 'POST',
headers: {Authorization: 'Bearer <token>', 'Content-Type': 'application/json'},
body: JSON.stringify({
source: {
upload_id: 'up_3f9a1c2b7e5d4a08',
url: 'https://app.veo.co/matches/20260826-acme-vs-north/'
},
group: 'grp_412',
metadata: {external_match_id: 'abc-123'},
options: {
session_type: '5v5',
session_mode: 'game',
camera_setup: 'stationary',
jerseys: 'auto',
has_jerseys: true,
court_type: 'ncaa_men',
point_system: 1,
recording_setup: 'tripod',
num_players: 5,
model: 'flash',
recording_team_name: 'Acme Varsity',
opponent_team_name: 'North High'
},
branding: {
cover: {
url: 'https://cdn.example.com/brand/logo.png',
upload_id: 'up_7c1e4b2a9d3f6e05'
},
logo: {
url: 'https://cdn.example.com/brand/logo.png',
upload_id: 'up_7c1e4b2a9d3f6e05'
}
},
rosters: {}
})
};
fetch('https://api.hooper.gg/v1/sessions', options)
.then(res => res.json())
.then(res => console.log(res))
.catch(err => console.error(err));{
"id": "sess_18234",
"object": "session",
"created": 1756224000,
"status": "queued",
"group": "grp_412",
"source": {
"type": "upload",
"url": "<string>"
},
"metadata": {},
"branding": {
"cover": true,
"logo": true
},
"teams": {
"recording": {
"name": "Acme Varsity",
"metadata": {}
},
"opponent": {
"name": "Acme Varsity",
"metadata": {}
}
},
"run_version": 123,
"duration_seconds": 5412,
"cost_cents": 752,
"highlights": {
"object": "list",
"has_more": true,
"total_count": 123,
"data": [
{
"id": "hl_9f2c…",
"object": "highlight",
"shot": "shot_a1b2…",
"shot_index": 123,
"length_seconds": 8.5,
"is_lowlight": true,
"is_manual": true,
"player": "plr_5",
"video": {
"default": "<string>",
"horizontal": "<string>",
"1080p": "<string>",
"720p": "<string>",
"480p": "<string>",
"360p": "<string>",
"240p": "<string>",
"144p": "<string>"
},
"preview": {
"default": "<string>",
"horizontal": "<string>"
},
"created": 123
}
],
"url": "/v1/sessions/sess_18234/highlights"
},
"shots": {
"object": "list",
"has_more": true,
"total_count": 123,
"data": [
{
"id": "shot_a1b2…",
"object": "shot",
"index": 123,
"start_seconds": 123,
"end_seconds": 123,
"made": true,
"points": 123,
"type": "fieldgoal",
"is_three_point": true,
"is_free_throw": true,
"is_fouled": true,
"location": {
"shot_image_normed": [
123
],
"shot_world_normed": [
123
],
"hoop_image_normed": [
123
]
},
"excitement_score": 123,
"players": {
"scorer": "plr_5",
"assister": "<string>",
"rebounder": "<string>"
},
"highlight": "hl_9f2c…"
}
],
"url": "/v1/sessions/sess_18234/highlights"
},
"players": {
"object": "list",
"has_more": true,
"total_count": 123,
"data": [
{
"id": "plr_5",
"object": "player",
"name": "<string>",
"jersey": "<string>",
"user_id": "<string>",
"team_id": 123,
"team_name": "Acme Varsity",
"metadata": {
"player_id": "lg-4821"
},
"stats": {
"points": 123,
"fg_2pt_att": 123,
"fg_2pt_make": 123,
"fg_3pt_att": 123,
"fg_3pt_make": 123,
"ft_att": 123,
"ft_make": 123,
"assist": 123,
"rebound": 123
}
}
],
"url": "/v1/sessions/sess_18234/highlights"
},
"totals": {
"points": 123,
"fg_2pt_att": 123,
"fg_2pt_make": 123,
"fg_3pt_att": 123,
"fg_3pt_make": 123,
"ft_att": 123,
"ft_make": 123,
"assist": 123,
"rebound": 123
},
"video": {
"url": "<string>",
"preview_url": "<string>",
"1080p": "<string>",
"720p": "<string>",
"360p": "<string>",
"width": 123,
"height": 123,
"fps": 123
},
"artifacts": {
"actions": "<string>",
"balls": "<string>",
"hoops": "<string>",
"clusters": "<string>"
},
"error": {
"code": "no_shots",
"param": "branding.cover",
"message": "<string>"
}
}{
"error": {
"type": "invalid_request_error",
"code": "parameter_invalid",
"message": "<string>",
"param": "source.url"
}
}{
"error": {
"type": "invalid_request_error",
"code": "parameter_invalid",
"message": "<string>",
"param": "source.url"
}
}{
"error": {
"type": "insufficient_credits",
"code": "insufficient_credits",
"message": "<string>",
"needed_cents": 1080,
"available_cents": 0,
"held_cents": 1000
}
}{
"error": {
"type": "invalid_request_error",
"code": "parameter_invalid",
"message": "<string>",
"param": "source.url"
}
}{
"error": {
"type": "invalid_request_error",
"code": "parameter_invalid",
"message": "<string>",
"param": "source.url"
}
}Submit a video
Places a credit hold, creates the session and queues it for processing. Responds immediately with the session in queued state; you are notified through a webhook (session.processed / session.failed) when it finishes, or you can poll GET /v1/sessions/{id}.
Send an Idempotency-Key header to make retries safe: the same key with the same body returns the original response for 24 hours.
curl --request POST \
--url https://api.hooper.gg/v1/sessions \
--header 'Authorization: Bearer <token>' \
--header 'Content-Type: application/json' \
--data '
{
"source": {
"upload_id": "up_3f9a1c2b7e5d4a08",
"url": "https://app.veo.co/matches/20260826-acme-vs-north/"
},
"group": "grp_412",
"metadata": {
"external_match_id": "abc-123"
},
"options": {
"session_type": "5v5",
"session_mode": "game",
"camera_setup": "stationary",
"jerseys": "auto",
"has_jerseys": true,
"court_type": "ncaa_men",
"point_system": 1,
"recording_setup": "tripod",
"num_players": 5,
"model": "flash",
"recording_team_name": "Acme Varsity",
"opponent_team_name": "North High"
},
"branding": {
"cover": {
"url": "https://cdn.example.com/brand/logo.png",
"upload_id": "up_7c1e4b2a9d3f6e05"
},
"logo": {
"url": "https://cdn.example.com/brand/logo.png",
"upload_id": "up_7c1e4b2a9d3f6e05"
}
},
"rosters": {}
}
'import requests
url = "https://api.hooper.gg/v1/sessions"
payload = {
"source": {
"upload_id": "up_3f9a1c2b7e5d4a08",
"url": "https://app.veo.co/matches/20260826-acme-vs-north/"
},
"group": "grp_412",
"metadata": { "external_match_id": "abc-123" },
"options": {
"session_type": "5v5",
"session_mode": "game",
"camera_setup": "stationary",
"jerseys": "auto",
"has_jerseys": True,
"court_type": "ncaa_men",
"point_system": 1,
"recording_setup": "tripod",
"num_players": 5,
"model": "flash",
"recording_team_name": "Acme Varsity",
"opponent_team_name": "North High"
},
"branding": {
"cover": {
"url": "https://cdn.example.com/brand/logo.png",
"upload_id": "up_7c1e4b2a9d3f6e05"
},
"logo": {
"url": "https://cdn.example.com/brand/logo.png",
"upload_id": "up_7c1e4b2a9d3f6e05"
}
},
"rosters": {}
}
headers = {
"Authorization": "Bearer <token>",
"Content-Type": "application/json"
}
response = requests.post(url, json=payload, headers=headers)
print(response.text)const options = {
method: 'POST',
headers: {Authorization: 'Bearer <token>', 'Content-Type': 'application/json'},
body: JSON.stringify({
source: {
upload_id: 'up_3f9a1c2b7e5d4a08',
url: 'https://app.veo.co/matches/20260826-acme-vs-north/'
},
group: 'grp_412',
metadata: {external_match_id: 'abc-123'},
options: {
session_type: '5v5',
session_mode: 'game',
camera_setup: 'stationary',
jerseys: 'auto',
has_jerseys: true,
court_type: 'ncaa_men',
point_system: 1,
recording_setup: 'tripod',
num_players: 5,
model: 'flash',
recording_team_name: 'Acme Varsity',
opponent_team_name: 'North High'
},
branding: {
cover: {
url: 'https://cdn.example.com/brand/logo.png',
upload_id: 'up_7c1e4b2a9d3f6e05'
},
logo: {
url: 'https://cdn.example.com/brand/logo.png',
upload_id: 'up_7c1e4b2a9d3f6e05'
}
},
rosters: {}
})
};
fetch('https://api.hooper.gg/v1/sessions', options)
.then(res => res.json())
.then(res => console.log(res))
.catch(err => console.error(err));{
"id": "sess_18234",
"object": "session",
"created": 1756224000,
"status": "queued",
"group": "grp_412",
"source": {
"type": "upload",
"url": "<string>"
},
"metadata": {},
"branding": {
"cover": true,
"logo": true
},
"teams": {
"recording": {
"name": "Acme Varsity",
"metadata": {}
},
"opponent": {
"name": "Acme Varsity",
"metadata": {}
}
},
"run_version": 123,
"duration_seconds": 5412,
"cost_cents": 752,
"highlights": {
"object": "list",
"has_more": true,
"total_count": 123,
"data": [
{
"id": "hl_9f2c…",
"object": "highlight",
"shot": "shot_a1b2…",
"shot_index": 123,
"length_seconds": 8.5,
"is_lowlight": true,
"is_manual": true,
"player": "plr_5",
"video": {
"default": "<string>",
"horizontal": "<string>",
"1080p": "<string>",
"720p": "<string>",
"480p": "<string>",
"360p": "<string>",
"240p": "<string>",
"144p": "<string>"
},
"preview": {
"default": "<string>",
"horizontal": "<string>"
},
"created": 123
}
],
"url": "/v1/sessions/sess_18234/highlights"
},
"shots": {
"object": "list",
"has_more": true,
"total_count": 123,
"data": [
{
"id": "shot_a1b2…",
"object": "shot",
"index": 123,
"start_seconds": 123,
"end_seconds": 123,
"made": true,
"points": 123,
"type": "fieldgoal",
"is_three_point": true,
"is_free_throw": true,
"is_fouled": true,
"location": {
"shot_image_normed": [
123
],
"shot_world_normed": [
123
],
"hoop_image_normed": [
123
]
},
"excitement_score": 123,
"players": {
"scorer": "plr_5",
"assister": "<string>",
"rebounder": "<string>"
},
"highlight": "hl_9f2c…"
}
],
"url": "/v1/sessions/sess_18234/highlights"
},
"players": {
"object": "list",
"has_more": true,
"total_count": 123,
"data": [
{
"id": "plr_5",
"object": "player",
"name": "<string>",
"jersey": "<string>",
"user_id": "<string>",
"team_id": 123,
"team_name": "Acme Varsity",
"metadata": {
"player_id": "lg-4821"
},
"stats": {
"points": 123,
"fg_2pt_att": 123,
"fg_2pt_make": 123,
"fg_3pt_att": 123,
"fg_3pt_make": 123,
"ft_att": 123,
"ft_make": 123,
"assist": 123,
"rebound": 123
}
}
],
"url": "/v1/sessions/sess_18234/highlights"
},
"totals": {
"points": 123,
"fg_2pt_att": 123,
"fg_2pt_make": 123,
"fg_3pt_att": 123,
"fg_3pt_make": 123,
"ft_att": 123,
"ft_make": 123,
"assist": 123,
"rebound": 123
},
"video": {
"url": "<string>",
"preview_url": "<string>",
"1080p": "<string>",
"720p": "<string>",
"360p": "<string>",
"width": 123,
"height": 123,
"fps": 123
},
"artifacts": {
"actions": "<string>",
"balls": "<string>",
"hoops": "<string>",
"clusters": "<string>"
},
"error": {
"code": "no_shots",
"param": "branding.cover",
"message": "<string>"
}
}{
"error": {
"type": "invalid_request_error",
"code": "parameter_invalid",
"message": "<string>",
"param": "source.url"
}
}{
"error": {
"type": "invalid_request_error",
"code": "parameter_invalid",
"message": "<string>",
"param": "source.url"
}
}{
"error": {
"type": "insufficient_credits",
"code": "insufficient_credits",
"message": "<string>",
"needed_cents": 1080,
"available_cents": 0,
"held_cents": 1000
}
}{
"error": {
"type": "invalid_request_error",
"code": "parameter_invalid",
"message": "<string>",
"param": "source.url"
}
}{
"error": {
"type": "invalid_request_error",
"code": "parameter_invalid",
"message": "<string>",
"param": "source.url"
}
}Authorizations
API key issued by Hooper for your organization. Keys are environment-tagged: hk_prod_ keys work against production, hk_stg_ keys against staging.
Headers
Any string up to 255 characters. Replaying the same key with the same body within 24h returns the original response; a different body returns 400 idempotency_key_reused.
Body
Where the video comes from. type is optional — it is derived from the URL (a Veo link is always routed to Veo).
Show child attributes
Show child attributes
A group under your organization. Omit it and the session lands in your organization's own private group.
"grp_412"
Up to 20 string key/values (keys ≤ 40 chars, values ≤ 500). Echoed on the session and inside every event — use it to carry your own ids.
Show child attributes
Show child attributes
{ "external_match_id": "abc-123" }
Describes how the video was recorded. Every field is optional. camera_setup, jerseys, session_mode and session_type change how the video is analysed. The others are saved with the session but do not change the results the API returns. Values are case-sensitive; anything outside the listed values is rejected with 400 parameter_invalid, and param names the field.
Show child attributes
Show child attributes
Your own images on the processed video. Both are optional and work with every source type. A branded video is always encoded at 1920×1080; a portrait recording gets black bars at the sides. The session object's branding field shows which images a session carries.
Show child attributes
Show child attributes
Each team's players. Hooper matches the players it detects to a side's roster by jersey number, so they come back in players with your name and metadata, and players[].team_id follows your sides (1 = recording, 2 = opponent). Send one side or both; an empty object is refused with 400 and param: rosters. Rosters need jersey numbers: they can't be sent with options.jerseys: "disabled" (400, param: options.jerseys), and with the default auto Hooper decides from the video (and keeps reading numbers if it can't tell); send enabled when you know players wear them. See Rosters.
Show child attributes
Show child attributes
Response
Session queued
One submitted game. The same shape everywhere it appears — single read, list item, webhook payload. Highlights, shots and players are embedded (first 100 of each); their url pages the rest.
"sess_18234"
"session"1756224000
queued, processing, processed, failed "grp_412"
Show child attributes
Show child attributes
Show child attributes
Show child attributes
Which branding images this session carries. Set when the session is submitted.
Show child attributes
Show child attributes
The two sides of the session, always present. players[].team_id 1 is recording, 2 is opponent.
Show child attributes
Show child attributes
5412
752
Show child attributes
Show child attributes
Show child attributes
Show child attributes
Show child attributes
Show child attributes
Show child attributes
Show child attributes
Show child attributes
Show child attributes
Raw model output (JSONL) behind the shots and highlights.
Show child attributes
Show child attributes
Set when status is failed.
Show child attributes
Show child attributes