Errors
Esta página aún no está disponible en tu idioma.
The API uses conventional HTTP status codes. Broadly: 2xx succeeded, 4xx
failed on something in your request or your account, 5xx is us.
Status codes
Section titled “Status codes”| Status | Meaning |
|---|---|
200 201 |
Worked. |
400 |
Validation failed, or a typed detail code. Fix the request; retrying unchanged never helps. |
401 |
Missing, invalid, or no-longer-entitled credential. |
402 |
Understood and well-formed, but your plan will not allow it. |
403 |
Authenticated, but the role or scheme gate refused — e.g. a human-only endpoint. |
404 |
Not found — or hidden. Another team’s data returns 404, never 403, so the API never confirms that it exists. |
409 |
State collision — a re-analysis already running, an email already in use. |
410 |
Was valid, now gone. An expired share token. |
429 |
Your API key went over its rate limit on upload-intent or session create — see Limits and capacity. Wait the seconds in Retry-After. (POST /api/me/verify-email/ also returns one after five wrong codes.) |
503 |
A dependency was unavailable. Safe to retry with backoff. |
The codes to branch on
Section titled “The codes to branch on”| Status | detail |
What happened | What to do |
|---|---|---|---|
401 |
Invalid API key |
Unknown, revoked or mistyped. | Stop. Retrying never helps — the credential is wrong. |
401 |
API keys require the Organization plan or higher |
The key is fine; the plan no longer carries API access. Every call fails from this moment. | Stop and alert the customer. This is billing, not code. Keys are not deleted — they work again when the plan is restored. |
403 |
This endpoint is not available to API-key callers. |
A human-only endpoint. | Do not retry with a key. See what a key can reach. |
402 |
quota_exhausted |
Out of analyses for the period. | Queue and retry after reset_at, or have the customer upgrade. Nothing was created — no session, no charge. |
402 |
subscription_suspended |
Payment failed and no live tier remains. Raised before quota is touched. | Stop uploading. The customer must fix payment. |
402 |
client_limit_reached |
Creating an athlete would exceed the plan’s ceiling. | Reuse an existing athlete, or upgrade. |
402 |
exercise_not_in_plan |
The (activity, exercise) exists but is not in the contract’s movement list — also for an upload with no exercise unless the whole activity is covered, and for a re-point (POST /api/sessions/<id>/reanalyze/) onto such a pair. Raised before quota is touched. |
Pick a movement from GET /api/exercises/ — it is already filtered to the contract — or have the customer extend it. Never retried as-is. |
400 |
{"exercise": ["'…' is not a supported exercise for activity '…'"]} |
Exact-match miss. | Fix the string against the catalogue. Never retry as-is. |
400 |
code: "unsupported_camera_view" |
That pair has no analyser for that plane. Carries supported_views. |
Send a supported view, or re-film. |
400 |
clip_too_long / clip_too_large |
The uploaded clip — or its trim window — is over 180 s (30 s when the session renders with original_quality), or the file is over 500 MB. Carries max_duration_s / max_bytes. Raised before quota is touched. |
Trim or compress, PUT it again to the same URL, and re-create. See Limits and capacity. |
400 |
clip_not_found |
Nothing was uploaded to that key — a create (or re-run) sent before the PUT finished, or a clip since deleted (an upload no session claims within three days is). Raised before quota is touched. |
Finish the PUT, then create again; for an old upload, ask for a new intent and PUT again. |
400 |
{"id": ["session_id_taken"]} |
That upload-intent id was already consumed — usually a retry after a dropped 201. |
Check whether the session exists before re-creating it. |
400 |
video_url must be an http(s) URL — … |
A placeholder, a bare path, or another team’s media. | Send the public_url from the upload intent, unmodified. |
409 |
source_deleted |
A re-run of a face-blurred session whose team deletes unblurred originals: the upload is gone (source_deleted: true, video_url: ""); the blurred videos and every metric stay. Raised before anything changes. |
Never retried as-is. To analyse the movement again, upload the clip as a new session. |
503 |
enqueue_failed |
Quota was debited but the queue was unreachable. | Safe to retry. |
429 |
rate_limited |
Your key went over its per-key limit on upload-intent or session create. Nothing was created or debited. |
Wait Retry-After seconds, then retry. Not a 402 — the plan is fine, the loop is too tight. |
The 402s carry context
Section titled “The 402s carry context”Every 402 returns the same shape, so one handler covers all of them:
{ "detail": "quota_exhausted", "reset_at": "2026-10-01T00:00:00Z", "current_plan": "organization", "current_role": "organization"}The distinction that matters most
Section titled “The distinction that matters most”flowchart TB
E["402 Payment Required"] --> D{"read detail"}
D -->|quota_exhausted| Q["account healthy,<br/>allowance used"]
D -->|subscription_suspended| S["account in dunning"]
D -->|exercise_not_in_plan| X["movement not in<br/>the contract"]
Q --> QA["queue the work,<br/>retry after reset_at"]
S --> SA["stop and escalate —<br/>a person must fix payment"]
X --> XA["pick from the catalogue —<br/>waiting changes nothing"]
quota_exhausted and subscription_suspended are both 402 and both read as
“payment required” from outside. They are not the same:
quota_exhausted— the account is healthy and has simply used its allowance. Waiting fixes it. Queue the work.subscription_suspended— the account is in dunning. Waiting fixes nothing; a person has to update a payment method. Stop and escalate.
Treating the second like the first means a silently stalled integration and a customer who finds out from you rather than from us.
exercise_not_in_plan is the third kind: nothing ran out and nothing is
broken — the movement is a line item the contract does not carry. Waiting
changes nothing and neither does a plan upgrade; the movement list is agreed
with the account manager. The catalogue is already filtered to the contract,
so an integration that offers only what GET /api/exercises/ returns never
sees this code.