Skip to content

Analyse an uploaded clip

POST
/api/portal/{client_id}/uploads/
curl --request POST \
--url http://localhost:8000/api/portal/2489E9AD-2EE2-8E00-8EC9-32D5F69181C0/uploads/ \
--header 'Authorization: Bearer <token>' \
--header 'Content-Type: application/json' \
--data '{ "session_id": "7c1e2d3f-4a5b-4c6d-8e7f-90a1b2c3d4e5", "activity": "running", "exercise": "Treadmill" }'

Debits one analysis from the TEAM’s quota and queues it; the coach is told in the conversation, and the result reaches the portal once a coach shares it. 404 upload_not_found for an id this person’s intent did not reserve on this card (or one lapsed after 45 minutes, or refused for good); 409 upload_used (sent already), upload_cap_reached, exercise_not_offered (outside the allowance), team_quota_exhausted (the coach is told; never the 402 paywall) — the cap and the analyses are checked before the clip is read; 400 for the clip itself — clip_too_long, clip_too_large, content_blocked (these three free the slot: the id cannot be sent again), clip_not_found, activity_mismatch_confirm (send again with activity_mismatch_confirmed: true), activity_mismatch; 403 access_ended team_plan while the team cannot run analyses; 503 enqueue_failed (nothing was debited; send again).

The Contribute level only: a View grant gets 403 contribute_required.

The signed-in person’s grant on the card in the path. Every route answers 404 while client access is switched off on the environment. 404 card_not_found for a card the account never held — another athlete’s, a card of the account’s own team, or an id that names nothing. 403 access_ended (reason: revoked · team_plan · minor) for a card it held and can no longer open, consent_required when the consent texts changed since the person accepted them (consent again with POST /api/portal/{client_id}/consent/). A partner API key gets 403. 429 past the account’s rate (client_portal, 120/min; the PDFs client_portal_pdf, 20/min).

Only what a coach released is visible, curated on the server: the team’s hidden metrics are absent, the coach’s AI text and internal fields are not sent, and media URLs are signed for two hours.

client_id
required
string format: uuid

PortalUploadInput: the clip is in the bucket — analyse it.

object
session_id
required

The intent’s upload id.

string format: uuid
activity
required
string
>= 1 characters <= 40 characters
exercise
string
"" <= 120 characters
camera_view
One of:
  • side - Side
  • front - Front
  • back - Back
string
Allowed values: side front back
activity_mismatch_confirmed

The person confirmed the clip is what they said after a activity_mismatch_confirm 400, as the coach’s create takes it.

boolean

Examples

ExampleATreadmillRun

A treadmill run

{
"session_id": "7c1e2d3f-4a5b-4c6d-8e7f-90a1b2c3d4e5",
"activity": "running",
"exercise": "Treadmill"
}
Media typeapplication/json
object
session_id
required
string format: uuid
status
required

pending: the analysis is queued.

string

Examples

ExampleQueued
{
"session_id": "7c1e2d3f-4a5b-4c6d-8e7f-90a1b2c3d4e5",
"status": "pending"
}
Media typeapplication/json
object
detail
required

Human-readable message, or a stable machine code for the cases a client branches on. The standard envelope for 400 (validation — a field-keyed object may appear instead), 401 (missing / invalid credentials), 403 (authenticated but not permitted), and 404 (absent — cross-team records are collapsed to 404 so the API never leaks the existence of another team’s data).

string

Examples

Example400—TooLong

400 — too long

{
"detail": "clip_too_long",
"max_duration_s": 60
}
Media typeapplication/json
object
detail
required

Human-readable message, or a stable machine code for the cases a client branches on. The standard envelope for 400 (validation — a field-keyed object may appear instead), 401 (missing / invalid credentials), 403 (authenticated but not permitted), and 404 (absent — cross-team records are collapsed to 404 so the API never leaks the existence of another team’s data).

string

Example generated

{
"detail": "example"
}
Media typeapplication/json
object
detail
required

access_ended, consent_required, or DRF’s message for a partner API key.

string
reason

With access_ended: why the card no longer opens — revoked, team_plan or minor.

string
client_name
string
team_name
string
consent_version

With consent_required: the version to accept.

string
kinds

With consent_required: the texts to show.

Array<string>

Examples

403 — access ended

{
"detail": "access_ended",
"reason": "team_plan",
"client_name": "Sofia Lebedeva",
"team_name": "North Track Academy"
}
Media typeapplication/json
object
detail
required

Human-readable message, or a stable machine code for the cases a client branches on. The standard envelope for 400 (validation — a field-keyed object may appear instead), 401 (missing / invalid credentials), 403 (authenticated but not permitted), and 404 (absent — cross-team records are collapsed to 404 so the API never leaks the existence of another team’s data).

string

Examples

Example404—NotThisPerson'sUpload

404 — not this person's upload

{
"detail": "upload_not_found"
}
Media typeapplication/json
object
detail
required

409 — the request conflicts with current state. detail is a stable code (e.g. reanalyze_in_flight, already_on_plan, email_in_use). Some 409s carry extra context fields alongside detail; those are documented on the specific endpoint that emits them.

string

Examples

409 — not offered

{
"detail": "exercise_not_offered"
}
Media typeapplication/json
object
detail
required

429 — throttled: this account’s (or address’s) rate for the route is used up; try again after wait seconds, which Retry-After repeats. A route with a limit of its own answers its own code instead, documented there.

string
wait

With throttled: whole seconds until the next request can pass.

integer
nullable

Example generated

{
"detail": "example",
"wait": 1
}
Media typeapplication/json
object
detail
required

503 — a downstream dependency was unavailable and the request could not be served (e.g. enqueue_failed when the analysis queue can’t accept the job). Safe to retry with backoff.

string

Examples

Example503—NotQueued

503 — not queued

{
"detail": "enqueue_failed"
}