Start the analysis an agent proposed
const url = 'http://localhost:8000/api/agents/intake/example/confirm/';const options = {method: 'POST', headers: {Authorization: 'Bearer <token>'}};
try { const response = await fetch(url, options); const data = await response.json(); console.log(data);} catch (error) { console.error(error);}curl --request POST \ --url http://localhost:8000/api/agents/intake/example/confirm/ \ --header 'Authorization: Bearer <token>'Start the analysis a coaching agent proposed for a clip in the conversation (issue #1689).
This is where the platform commits, and the only place it does. The agent’s start_analysis tool records a proposal and spends nothing; this endpoint calls the same create_session path the upload wizard calls, so the quota check, the single append-only ledger debit and the pipeline enqueue are one implementation rather than two. A model can never reach it.
Returns as soon as the session is committed — the analysis takes minutes and this request does not wait for it. When the pipeline finishes, a new assistant turn appears in the same conversation carrying the result card and the session’s coaching summary; poll GET /api/agents/threads/<id>/ while pending_intakes is non-empty.
Idempotent: a second tap returns the first tap’s session and debits nothing further.
404 when the intake is not the caller’s own, when the id does not parse, or when agents / intake are disabled for this deployment — a dark capability is indistinguishable from a missing route. 400 intake_closed when the proposal expired (24 h) or was already dismissed. 402 quota_exhausted when the team has no analyses left, in the standard paywall envelope.
Authorizations
Section titled “Authorizations”Parameters
Section titled “Parameters”Path Parameters
Section titled “Path Parameters”Responses
Section titled “Responses”Body of the intake confirm / close responses.
card is the SAME payload the conversation persisted, rebuilt from the
updated row — so the client swaps the card it is showing for this one rather
than patching a status into a payload it holds, and the two cannot drift.
object
object
Examples
{ "intake_id": "6d2f0b3a-6f1e-4a5f-9a3b-1c2d3e4f5a6b", "status": "confirmed", "session_id": "b1c2d3e4-f5a6-4b7c-8d9e-0f1a2b3c4d5e", "card": { "kind": "analysis_proposal", "intake_id": "6d2f0b3a-6f1e-4a5f-9a3b-1c2d3e4f5a6b", "status": "confirmed", "athlete": "Anna Kim", "activity_key": "running", "activity": "Running", "exercise": "Treadmill", "exercise_key": "Treadmill", "camera_view": "side", "quota_cost": 1 }}object
Machine code where a client branches — clip_too_long, clip_too_large, clip_not_found, content_blocked, activity_mismatch, activity_mismatch_confirm, … — else a message. ABSENT on a plain DRF validation error, which is field-keyed instead (the re-cut refusal nests its messages under trim).
Repeats the machine code: clip_too_long / clip_too_large / clip_not_found / content_blocked / activity_mismatch / activity_mismatch_confirm / unsupported_camera_view.
The activity the clip’s vision read named, on activity_mismatch / activity_mismatch_confirm (#2801).
On activity_mismatch / activity_mismatch_confirm (#2801): whether the caller’s activity_mismatch_confirmed: true would get past it — always on a look-alike, only for the team owner or a partner key on a different sport.
English sentence for logs on a clip-gate refusal — a carrier, not a field error on the URL.
The duration ceiling that applied, on clip_too_long — the shorter original-quality one when the session renders in original quality (#2391).
The size ceiling, on clip_too_large.
Field errors on the re-cut range (#2250) — inverted, non-finite, too short, or running past the end of the clip. This shape carries no detail.
The clip’s real length, on trim_beyond_source — so a client can re-offer the cut against the true duration.
The pair’s analysable camera views, on unsupported_camera_view.
Example generated
{ "detail": "example", "code": "example", "read_activity": "example", "can_override": true, "video_url": "example", "max_duration_s": 1, "max_bytes": 1, "trim": [ "example" ], "source_duration_s": 1, "supported_views": [ "example" ]}object
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).
Example generated
{ "detail": "example"}The caller’s tier does not include the coaching agents.
object
One of eight canonical strings. The front-end paywall switches copy + CTA on this value (exact string equality — do not localise). quota_exhausted / seat_limit_reached / client_limit_reached / feature_not_in_plan lead to an upgrade CTA; subscription_suspended leads to a Billing Portal CTA so the buyer can update their payment method; exercise_not_in_plan leads to a contact-us CTA — the movement list is sales-managed, no tier adds to it (#2030). trial_expired / access_ended mean the team is read-only (#2901): everything opens, nothing new is created; they lead to a subscribe CTA and always carry reset_at: null.
quota_exhausted- Session quota for the period reached 0subscription_suspended- Stripe dunning in flight (past_due / unpaid)seat_limit_reached- Adding another team member would exceed plan.max_seatsclient_limit_reached- Adding another Client would exceed plan.max_client_profilesfeature_not_in_plan- The tier does not include this whole feature surfaceexercise_not_in_plan- The (activity, exercise) is not in the team’s exercise grantstrial_expired- The team is read-only: its self-serve trial endedaccess_ended- The team is read-only: its paid plan or granted access ended
ISO-8601 UTC timestamp the team’s current period ends. Null when the team has no live subscription (legacy un-migrated row). On Free + paid this is the renewal moment; quota refreshes to exactly plan.session_quota (NOT additive — leftover sessions don’t roll over).
The active plan’s role string (free, starter, professional, organization, strategic). Same value as current_role — kept as two separate fields for forward-compatibility with a future named-plan split where current_plan could carry an SKU and current_role the tier label.
The active plan’s role string. See current_plan.
Which feature surface is locked. Present ONLY on feature_not_in_plan, so one modal can name what’s gated without a detail string per feature (#773). The only value today is agents (aikynetix/agents/services.py::AGENTS_FEATURE).
Examples
Free team opens an agent conversation
{ "detail": "feature_not_in_plan", "reset_at": null, "current_plan": "free", "current_role": "free", "feature": "agents"}object
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).
Example generated
{ "detail": "example"}No such intake for this coach, or the intake capability is off for this deployment.
object
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).
Example generated
{ "detail": "example"}object
429 — rate limit exceeded. Honour the Retry-After response header (seconds to wait) before retrying; it is sent on every 429.
Example generated
{ "detail": "example"}object
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).
Example generated
{ "detail": "example"}