Statbox Partner API
Complete public reference for the Stats API. This page is static HTML so ChatGPT and other readers can load every endpoint without JavaScript.
Styled UI: https://www.statbox.co/docs/api · Markdown: https://www.statbox.co/docs/api.md · llms.txt · Request a key: WhatsApp
What this API does
A partner sends a football, padel, or basketball game as a public Google Drive, YouTube, or Veo link, or asks Statbox to email an upload page. When the match is ready, Statbox emails a CSV of player stats plus highlight, screenshot, and game-highlight links. The partner then pulls that same game into their own app.
There is no venue camera step. Send location as the club or court name and pitch as the court name. Do not send a Statbox venue id. The ready email does not include a Statbox join link.
Base URL
https://pfmdviiiwbpcutebhlkw.supabase.co/functions/v1/statbox-partner-api
Every call is this URL plus ?endpoint=NAME.
Authentication
Authorization: Bearer sbx_live_YOUR_API_KEY Content-Type: application/json
Keys begin with sbx_live_. A key can be unlimited or capped to a number of games per calendar month (UTC). You can only read games created with that key.
Endpoints
| Method | Endpoint | Purpose |
|---|---|---|
| POST | ?endpoint=stats-request | Create a game from Drive/YouTube or ask for an upload-box email |
| GET or POST | ?endpoint=stats-status | needs_upload, uploaded, or confirmed for that game |
| GET or POST | ?endpoint=stats-result | Full match JSON for that game only |
| GET or POST | ?endpoint=player-stats | One player from that game |
| GET or POST | ?endpoint=stats-csv | Spreadsheet of that game |
| GET or POST | ?endpoint=games-left | How many games this key can still create this month |
1. POST ?endpoint=stats-request
Creates the Statbox game. Does not wait for stats.
Required
notify_email(string). Statbox emails this address the upload box (if requested), a confirmation after footage is uploaded, and the CSV plus highlight links when the game is ready.location(string). Club, venue, or court name for this game. You choose this label.venueis accepted as an alias. Do not send a Statbox venue id.pitch(string). Court or pitch name for this game, for example Court 2.pitch_nameis accepted as an alias.
One of these two footage options (not both)
footage_url(string). Public Google Drive, YouTube, or Veo match https link. A Veo link looks likehttps://app.veo.co/matches/MATCH_ID/. Other URLs are rejected.upload_via_email(boolean true). Optional. Use this only when you do not have a public Drive, YouTube, or Veo link. Statbox emails notify_email a one-time upload page. That page is not an inbox. If you send footage_url, omit this.
Optional
players(array). Each item: name, team A or B, external_id, optional jersey_number 0-99. Set external_id if you will fetch that player later.external_reference(string, max 80). Your booking or match id.date(YYYY-MM-DD),start_time/end_time(HH:MM 24h),sport(football, padel, or basketball),court_size(half or full, required for basketball),contact_name,contact_phone.
Example: YouTube
curl -X POST "https://pfmdviiiwbpcutebhlkw.supabase.co/functions/v1/statbox-partner-api?endpoint=stats-request" \
-H "Authorization: Bearer sbx_live_YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"notify_email": "ops@partner.com",
"location": "City Padel Club",
"pitch": "Court 2",
"footage_url": "https://youtu.be/VIDEO_ID"
}'
Example: Veo
curl -X POST "https://pfmdviiiwbpcutebhlkw.supabase.co/functions/v1/statbox-partner-api?endpoint=stats-request" \
-H "Authorization: Bearer sbx_live_YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"notify_email": "ops@partner.com",
"location": "City Padel Club",
"pitch": "Court 2",
"footage_url": "https://app.veo.co/matches/MATCH_ID/"
}'
Example: Google Drive
curl -X POST "https://pfmdviiiwbpcutebhlkw.supabase.co/functions/v1/statbox-partner-api?endpoint=stats-request" \
-H "Authorization: Bearer sbx_live_YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"notify_email": "ops@partner.com",
"location": "City Padel Club",
"pitch": "Court 2",
"footage_url": "https://drive.google.com/file/d/FILE_ID/view",
"players": [
{ "name": "Ahmed Khalil", "team": "A", "external_id": "player-88" },
{ "name": "Omar Haddad", "team": "B", "external_id": "player-91" }
],
"external_reference": "BOOKING-123"
}'
Example: email an upload box
curl -X POST "https://pfmdviiiwbpcutebhlkw.supabase.co/functions/v1/statbox-partner-api?endpoint=stats-request" \
-H "Authorization: Bearer sbx_live_YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"notify_email": "ops@partner.com",
"location": "City Padel Club",
"pitch": "Court 2",
"upload_via_email": true
}'
Success response (HTTP 201)
{
"match_id": "uuid",
"key_code": "ABC123",
"status": "uploaded",
"notify_email": "ops@partner.com",
"venue": { "name": "City Padel Club" },
"pitch": { "name": "Court 2" },
"footage_url": "https://youtu.be/VIDEO_ID",
"footage_source": "youtube",
"upload_link": null,
"players_accepted": 2,
"status_url": "?endpoint=stats-status&match_id=uuid",
"poll_url": "?endpoint=stats-result&match_id=uuid",
"csv_url": "?endpoint=stats-csv&match_id=uuid",
"player_stats_url": "?endpoint=player-stats&match_id=uuid",
"message": "Game created. When analysis is done we email the CSV, highlight links, and screenshot links."
}
Save match_id. You need it for the other endpoints. If upload_via_email was true, status is needs_upload. After footage is on the game, status is uploaded. After the game is confirmed, status is confirmed.
2. GET or POST ?endpoint=stats-status
Check the game you created. Identify it with match_id, key_code, or external_reference.
curl -X GET "https://pfmdviiiwbpcutebhlkw.supabase.co/functions/v1/statbox-partner-api?endpoint=stats-status&match_id=MATCH_ID_FROM_STEP_1" \ -H "Authorization: Bearer sbx_live_YOUR_API_KEY"
| status | Meaning |
|---|---|
needs_upload | Waiting for the MP4. Response includes upload_link. |
uploaded | Footage is on the game. Drive/YouTube requests start here. |
confirmed | The game is confirmed. Call stats-result until status is ready. |
3. Ready email
There is no 24-hour cutoff. The email to notify_email includes a CSV of every player’s stats (player name first), each player’s highlight link and screenshot link, and the game highlight link. It does not include a Statbox join link, match_id, or key_code.
4. GET or POST ?endpoint=stats-result
Returns the specific game you requested. Identify it with match_id, key_code, or external_reference.
curl -X GET "https://pfmdviiiwbpcutebhlkw.supabase.co/functions/v1/statbox-partner-api?endpoint=stats-result&match_id=MATCH_ID_FROM_STEP_1" \ -H "Authorization: Bearer sbx_live_YOUR_API_KEY"
Display the match only when status is ready. Before that, this call returns match_id, status, and a not-ready message. game_status: confirmed means the game is confirmed. That does not release scores or media. A player with no highlightable actions does not keep the match in processing_highlights.
| Field | Values |
|---|---|
game_status | needs_upload, uploaded, confirmed. Same as stats-status. |
status | under_review, processing_highlights, ready, cancelled, pending_payment. |
Football stats
Every value is an integer. A missing key means 0. There is no per-player rating. The match score is score. Do not rebuild it by summing goals.
| Key | Meaning |
|---|---|
goals | Goals scored |
assists | Assists |
shots_on_target | Shots on target |
shots_off_target | Shots off target |
chances_created | Chances created for a teammate |
successful_dribbles | Successful dribbles |
successful_tib | Successful tackles, interceptions, and blocks |
saves | Goalkeeper saves |
wrong_pass | Misplaced passes. A missing key means 0. |
Padel stats
Keys are winner and ace, not winners or aces. There is no points or forced_errors field. Use score for the match result.
| Key | Meaning |
|---|---|
ace | Service aces |
serve | Serve points won |
return | Return points won |
winner | Winners |
smash | Smashes |
lob | Lobs |
dropshot | Dropshots |
long_rally | Long rallies |
double_faults | Double faults |
unforced_errors | Unforced errors |
Basketball stats
Send sport: "basketball" and court_size of half or full. Every value is an integer. A missing key means 0. There is no points total. fg_made already includes made threes. Do not add three_pts_made on top of it. The turnover key is singular: turnover.
| Key | Meaning |
|---|---|
fg_made | Field goals made, including three-pointers |
fg_attempted | Field goal attempts, including three-point attempts |
three_pts_made | Three-pointers made |
three_pts_attempted | Three-point attempts |
free_throws_scored | Free throws made |
free_throws_attempted | Free throw attempts |
offensive_rebound | Offensive rebounds |
defensive_rebound | Defensive rebounds |
assists | Assists |
blocks | Blocks |
turnover | Turnovers |
Points, if you need them: 2 * (fg_made - three_pts_made) + 3 * three_pts_made + free_throws_scored. A completed basketball JSON example is in api.md.
Each player object also has name, team (A or B), jersey_number (integer or null), external_id (string or null, exact match), slot_number, highlight_url, and screenshot_url. stats is {} when nothing has been saved.
Example: completed football match
Illustrative values. These URLs are samples and do not resolve. A real match has one player object per person, up to 40, with the same keys. The full lineup example is in the Markdown reference.
{
"match_id": "7c2a9e14-1b0d-4f3a-9c88-0a11b2c3d4e5",
"key_code": "FB29KX",
"join_url": "https://www.statbox.co/create/join?code=FB29KX",
"external_reference": "BOOKING-123",
"status": "ready",
"game_status": "confirmed",
"payment_status": "paid",
"highlights_ready": true,
"sport": "football",
"date": "2026-10-12",
"start_time": "18:00",
"end_time": "19:00",
"venue": { "id": null, "name": "Riverside Pitch" },
"pitch": { "name": "Pitch 1", "slug": null },
"score": { "team_a": 3, "team_b": 1 },
"game_highlight_url": "https://d1nvampodjxeg6.cloudfront.net/match_clips/7c2a9e14-1b0d-4f3a-9c88-0a11b2c3d4e5/game/highlight.mp4",
"players": [
{
"name": "Ahmed Khalil",
"team": "A",
"jersey_number": 9,
"external_id": "player-88",
"slot_number": 1,
"stats": {
"goals": 2, "assists": 0, "shots_on_target": 4, "shots_off_target": 1,
"chances_created": 1, "successful_dribbles": 3, "successful_tib": 1,
"saves": 0, "wrong_pass": 2
},
"highlight_url": "https://d1nvampodjxeg6.cloudfront.net/match_clips/7c2a9e14-1b0d-4f3a-9c88-0a11b2c3d4e5/1/highlight.mp4",
"screenshot_url": "https://pfmdviiiwbpcutebhlkw.supabase.co/storage/v1/object/public/match_photos/temp_stats/example_slot_1.jpg"
}
],
"csv_endpoint": "?endpoint=stats-csv&match_id=7c2a9e14-1b0d-4f3a-9c88-0a11b2c3d4e5",
"player_stats_endpoint": "?endpoint=player-stats&match_id=7c2a9e14-1b0d-4f3a-9c88-0a11b2c3d4e5"
}
Example: completed padel match
{
"match_id": "9f1e8d70-4a2b-4c6d-8e90-112233445566",
"key_code": "PD88QA",
"status": "ready",
"game_status": "confirmed",
"highlights_ready": true,
"sport": "padel",
"date": "2026-10-12",
"score": { "team_a": 6, "team_b": 4 },
"game_highlight_url": "https://d1nvampodjxeg6.cloudfront.net/match_clips/9f1e8d70-4a2b-4c6d-8e90-112233445566/game/highlight.mp4",
"players": [
{
"name": "Maya Nassar",
"team": "A",
"jersey_number": null,
"external_id": "padel-user-1",
"slot_number": 1,
"stats": {
"ace": 3, "serve": 8, "return": 5, "winner": 6, "smash": 2,
"lob": 4, "dropshot": 1, "long_rally": 3, "double_faults": 1, "unforced_errors": 2
},
"highlight_url": "https://d1nvampodjxeg6.cloudfront.net/match_clips/9f1e8d70-4a2b-4c6d-8e90-112233445566/1/highlight.mp4",
"screenshot_url": "https://pfmdviiiwbpcutebhlkw.supabase.co/storage/v1/object/public/match_photos/temp_stats/example_padel_1.jpg"
}
]
}
A doubles match has four player objects. Each uses the same padel keys. The full four-player example is in api.md.
Example: results not ready
HTTP 200 from stats-result, player-stats, and stats-csv until the full package is ready. No scores, stats, CSV, screenshots, or highlight URLs.
{
"match_id": "7c2a9e14-1b0d-4f3a-9c88-0a11b2c3d4e5",
"status": "processing_highlights",
"message": "Match results are not ready yet."
}
external_id is the player id in your app. Players do not need a Statbox account. A player with no highlightable actions does not keep the match processing. When ready, that player's highlight_url is null and stats are {} or zeros.
5. GET or POST ?endpoint=player-stats
One player from that same game. Send the game id plus one of external_id, slot_number, or player_name.
curl -X GET "https://pfmdviiiwbpcutebhlkw.supabase.co/functions/v1/statbox-partner-api?endpoint=player-stats&match_id=MATCH_ID_FROM_STEP_1&external_id=player-88" \ -H "Authorization: Bearer sbx_live_YOUR_API_KEY"
The body is match_id, key_code, join_url, status, sport, date, score, game_highlight_url, and player (the same player object as in stats-result). It does not return other players or that player’s other Statbox games.
If the external_id was never sent, or that player is not on the game, the response is HTTP 404:
{ "error": "Player not found in this game" }
6. GET or POST ?endpoint=stats-csv
Same match as a CSV file. Same data as the email attachment.
curl -X GET "https://pfmdviiiwbpcutebhlkw.supabase.co/functions/v1/statbox-partner-api?endpoint=stats-csv&match_id=MATCH_ID_FROM_STEP_1" \ -H "Authorization: Bearer sbx_live_YOUR_API_KEY" \ -o statbox-stats.csv
GET or POST ?endpoint=games-left
How many games this key can still create this calendar month. This call does not create a game and does not use up the monthly cap.
curl -X GET "https://pfmdviiiwbpcutebhlkw.supabase.co/functions/v1/statbox-partner-api?endpoint=games-left" \ -H "Authorization: Bearer sbx_live_YOUR_API_KEY"
{
"used": 12,
"limit": 100,
"remaining": 88,
"resets_at": "2026-11-01T00:00:00.000Z"
}
used is successful game creates since 00:00 UTC on the first of this month. remaining is limit - used. resets_at is 00:00 UTC on the first day of next month. An unlimited key returns "limit": null and "remaining": null. used is still the number created this month.
Errors
All errors are JSON: { "error": "message" }.
| HTTP | Meaning |
|---|---|
| 401 | Bad or missing API key |
| 403 | This key cannot use this product, or the key is disabled |
| 400 | Missing or invalid fields (for example missing location or pitch) |
| 404 | That game was not requested with this key, or that external_id is not on the game |
| 409 | player_name matches more than one player. Use external_id or slot_number |
| 429 | Too many requests this minute, or this key’s monthly game cap is reached |
429 this minute
Wait 60 seconds, then retry the same call. Do not create another game. The default limit is 60 requests per key per rolling minute.
{ "error": "Rate limit exceeded. Try again in a minute." }
429 monthly cap
Returned by stats-request only. Unlimited keys do not return this. Call games-left to see used, limit, and remaining before you hit the cap. It resets at 00:00 UTC on the first day of the month.
{
"error": "Monthly game limit reached (100/100). Try again next month.",
"used": 100,
"limit": 100
}
Media links
highlight_url and game_highlight_url are direct HTTPS URLs to MP4 files. Play them in a video player. They are not web pages. screenshot_url is a direct HTTPS JPEG or PNG. All of these URLs are public. Do not send the API key. They do not expire. Before status is ready, the API does not return stats or media. On a ready match, null means that file does not exist. A player with no highlightable actions has highlight_url null and still has stats.
Webhook: match.ready
Send webhook_url on stats-request. HTTPS port 443 only. The 201 response includes webhook_secret. Store it. It is not the API key. When the full match is ready, Statbox POSTs one match.ready event. The body includes event_id, event, timestamp, match_id, external_reference, and result. Retries keep the same event_id and the same raw body. Each attempt sends a new X-Statbox-Timestamp and a new HMAC. Reject a timestamp more than five minutes from your clock, and reject a signature that is missing or not 64 hex characters before you compare it. The full create example and receiver are in /docs/api.md. There is no separate stats.ready or highlights.ready event.
What this key can read
A key returns only games created with that key. There is no list endpoint, no way to fetch an existing Statbox game by a public code, and no way to download a player’s other Statbox matches.
Integration notes
- Store the API key as a server-side secret. Do not put it in a mobile app binary.
- Call stats-request once per game. Save match_id. Send external_id for every player you will show later.
- Call stats-status with that match_id until confirmed, then stats-result until ready.
- Call stats-result until status is ready. Until then the body has no scores, stats, or media. Polling still works if you do not set webhook_url.
- Call games-left to see how many games this key can still create. On a per-minute 429, wait 60 seconds and retry. On a monthly-cap 429, wait until next month before creating another game.
- Play highlight URLs as MP4 files. Do not attach the API key. They do not expire.
- This key cannot read games it did not create, and it cannot read a player’s other Statbox matches.