Skip to content

Start the analysis an agent proposed

POST
/api/agents/intake/{id}/confirm/
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.

id
required
string
Media typeapplication/json

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
intake_id
required
string
status
required
string
session_id
required
string
nullable
card
required
object
key
additional properties

Examples

ExampleStarted
{
"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
}
}
Media typeapplication/json
object
detail

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).

string
code

Repeats the machine code: clip_too_long / clip_too_large / clip_not_found / content_blocked / activity_mismatch / activity_mismatch_confirm / unsupported_camera_view.

string
read_activity

The activity the clip’s vision read named, on activity_mismatch / activity_mismatch_confirm (#2801).

string
can_override

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.

boolean
video_url

English sentence for logs on a clip-gate refusal — a carrier, not a field error on the URL.

string
max_duration_s

The duration ceiling that applied, on clip_too_long — the shorter original-quality one when the session renders in original quality (#2391).

integer
max_bytes

The size ceiling, on clip_too_large.

integer
trim

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.

Array<string>
source_duration_s

The clip’s real length, on trim_beyond_source — so a client can re-offer the cut against the true duration.

number format: double
supported_views

The pair’s analysable camera views, on unsupported_camera_view.

Array<string>

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"
]
}
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"
}

The caller’s tier does not include the coaching agents.

Media typeapplication/json
object
detail
required

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 0
  • subscription_suspended - Stripe dunning in flight (past_due / unpaid)
  • seat_limit_reached - Adding another team member would exceed plan.max_seats
  • client_limit_reached - Adding another Client would exceed plan.max_client_profiles
  • feature_not_in_plan - The tier does not include this whole feature surface
  • exercise_not_in_plan - The (activity, exercise) is not in the team’s exercise grants
  • trial_expired - The team is read-only: its self-serve trial ended
  • access_ended - The team is read-only: its paid plan or granted access ended
string
Allowed values: quota_exhausted subscription_suspended seat_limit_reached client_limit_reached feature_not_in_plan exercise_not_in_plan trial_expired access_ended
reset_at
required

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).

string format: date-time
nullable
current_plan
required

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.

string
current_role
required

The active plan’s role string. See current_plan.

string
feature

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).

string

Examples

Example402—AgentsNotInPlan

Free team opens an agent conversation

{
"detail": "feature_not_in_plan",
"reset_at": null,
"current_plan": "free",
"current_role": "free",
"feature": "agents"
}
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"
}

No such intake for this coach, or the intake capability is off for this deployment.

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

429 — rate limit exceeded. Honour the Retry-After response header (seconds to wait) before retrying; it is sent on every 429.

string

Example generated

{
"detail": "example"
}
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"
}