Create a session and enqueue inference
curl -X POST https://api.ai.aikynetix.app/api/sessions/ \ -H 'Authorization: Bearer aik_yourPartnerTokenHere' \ -H 'Content-Type: application/json' \ -d '{ "id": "5c836d7d-3301-49df-bfa0-9cff0550fd0e", "activity": "running", "exercise": "Treadmill", "video_url": "https://media.ai.aikynetix.app/.../clip.source.mp4", "client": "f6d52e64-3aa3-4a26-bac2-0fc9d718448d", "camera_view": "side" }'import requestsr = requests.post( "https://api.ai.aikynetix.app/api/sessions/", headers={"Authorization": "Bearer aik_yourPartnerTokenHere"}, json={ "id": "5c836d7d-3301-49df-bfa0-9cff0550fd0e", "activity": "running", "exercise": "Treadmill", "video_url": "https://media.ai.aikynetix.app/.../clip.source.mp4", "client": "f6d52e64-3aa3-4a26-bac2-0fc9d718448d", "camera_view": "side", }, timeout=30,)if r.status_code == 402: paywall = r.json() # quota_exhausted / subscription_suspended raise RuntimeError(paywall["detail"])r.raise_for_status()session = r.json() # status == "pending" — poll GET /api/sessions/<id>/const r = await fetch("https://api.ai.aikynetix.app/api/sessions/", { method: "POST", headers: { Authorization: "Bearer aik_yourPartnerTokenHere", "Content-Type": "application/json" }, body: JSON.stringify({ id: "5c836d7d-3301-49df-bfa0-9cff0550fd0e", activity: "running", exercise: "Treadmill", video_url: "https://media.ai.aikynetix.app/.../clip.source.mp4", client: "f6d52e64-3aa3-4a26-bac2-0fc9d718448d", camera_view: "side", }),});if (r.status === 402) { const paywall = await r.json(); // quota_exhausted / subscription_suspended throw new Error(paywall.detail);}if (!r.ok) throw new Error(`HTTP ${r.status}`);const session = await r.json(); // status == "pending"Records a new Session pointing at the just-uploaded video, debits the team’s session quota, and enqueues the analysis pipeline. Pass id if you reserved one via POST /api/sessions/upload-intent/ (recommended — keeps the storage object key and the Session.id consistent). activity is required; exercise selects the specific movement within it (e.g. Treadmill for running). camera_view (side / front / back) tells the pose pipeline which analyzers to run. client is the athlete UUID — without it, body-aware adjustments fall back to defaults. Returns 201 with the new Session in pending status; poll GET /api/sessions/<id>/ until status == 'completed'.
402 when quota is exhausted or the subscription is suspended — no debit happens, the upload object stays orphaned until the team-wide GC. 503 enqueue_failed if the quota debit succeeded but the task broker was unreachable; safe to retry.
Auth: team-scoped — reachable with a partner aik_… API key. 403 if the caller has no active team selected (a transient state, e.g. mid team-switch).
Authorizations
Section titled “Authorizations”Request Bodyrequired
Section titled “Request Bodyrequired”object
The session UUID reserved by POST /api/sessions/upload-intent/. Pass it back here so the row id matches the storage object key. Optional for ad-hoc creation without an upload-intent roundtrip.
Activity family — running, walking, jumps, weightlifting, agility, mobility, fencing, ergonomics. Required.
running- Runningwalking- Walkingjump- Vertical Jumpweightlifting- Weightliftingmobility- Mobility Assessmentworkspace_wellness- Workspace Wellnessagility- Agilityfencing- Fencingcycling- Cyclingtennis- Tennispadel- Padel Tennisbowling- Bowlingbaseball- Baseballfootball- Footballbadminton- Badmintoncricket- Cricketbasketball- Basketballscuba_diving- Scuba Divingice_hockey- Ice Hockeyamerican_football- American Footballfri- Falls Risk Indicator
Specific movement (e.g. Treadmill, Back Squat). Pick from GET /api/exercises/?activity=.... Optional but strongly recommended — without it the analyzer uses activity-default settings and metrics may be less specific.
UUID of the Client this session is about. Recommended — without it body-aware metric adjustments fall back to defaults.
Optional dominant-hand hint (right by default). Read by handedness-aware analyzers — fencing maps raw left/right pose-landmark sides to semantic front/rear by lead leg; tennis and bowling map them to dominance-relative keys so a left-handed forehand (or left-handed bowler) reads identically to the right-handed case. Silently ignored by activities whose analyzers don’t read this field.
right- Right-handedleft- Left-handed
Camera plane for the recording. side (sagittal, default) runs the stride / GCT / speed / sagittal-angle analyzer. front / back swap to the frontal analyzer (pelvic drop, hip adduction, step width, trunk sway, L/R asymmetry). When exercise is set, the value must be one of the pair’s supported views — an unsupported plane is a 400 (#1829). See views_by_exercise in GET /api/exercises/?activity=… for each pair’s supported set.
side- Sidefront- Frontback- Back
Scuba diving session-meta sidecar — required when activity == scuba_diving, silently ignored for every other activity. Validates side_involved, condition, arms_engaged, and the optional clinical fields (Borg CR-10, fin model, calibration scale bar). Stored on Session.meta.scuba so the analyzer can read it via the X-Aikmodels-Contract 3→4 plumbing (P0.5). See project_context/architecture/DESIGN_SCUBA_V1.md §“Session metadata sidecar”.
object
L- LeftR- Right
Side of the amputation / involved limb. L or R for amputee divers; null for intact-control rows. The analyzer uses this to map raw left/right kinematics into involved/uninvolved semantics (TR-4.2 verbatim — never inferred from the video).
L- LeftR- Right
§5.2 condition tag. Used for cohort tagging on the dashboard + the report PDF cover. Must match the trial design — not derived from other fields, because the same diver shoots multiple conditions per session.
control_no_arms- Control — no armscontrol_with_arms- Control — with armstranstibial- Transtibialtransfemoral- Transfemoral
Confound flag (§4.6). True when the diver was sculling / using arms for propulsion during the bout. Surfaces in the report so coaches know to filter symmetry numbers.
Fin model — free-form (manufacturer + model).
Fin stiffness. Free-form: either a qualitative grade (soft / medium / stiff) or a numeric string with units (12 N·m/rad). Analyzer doesn’t consume it in V1; captured for the report + future fin-deflection v2 work.
Borg CR-10 perceived exertion (residual side), 0–10.
Borg CR-10 perceived exertion (sound side), 0–10.
Length of the in-frame calibration scale bar (§6.1), in metres. Optional — without it, body-aware distance metrics use body_scale_px(ctx) per the standard fallback.
Damian’s 12-question ergonomics survey payload — required for the workspace_wellness:Ergonomics Assessment exercise, silently ignored for every other activity/exercise. Gated on the (activity, exercise) pair, so a future workspace_wellness exercise does not inherit the mandatory survey. Lands on Session.meta.workstation_assessment after validation. See #303 for the full spec + biomech rationale.
object
Reusable workstation setup — posture + the six desk/seat/screen geometry measurements (Damian Q2–Q7). Snapshotted onto the session and persisted on the client for reuse.
object
Anton’s sit/stand toggle. Drives which measurement fields are required (seat-related fields are seated-only). When both apply to the subject’s workstation, the spec is to upload two separate sessions per CEO routing — one row per posture.
seated- Seatedstanding- Standing
Damian Q2–Q7. Six desk/seat/screen geometry measurements; three are seated-only.
object
Damian Q2. Desk-top height in cm (floor → desk surface).
Damian Q3. Seat-top height in cm (floor → top of seat pan). Required when posture is seated.
Damian Q4. Seat-pan depth in cm — measured from the point where the back rest meets the seat pan to the front edge of the seat pan. Required when posture is seated.
Damian Q5. Back-rest height in cm (seat pan → top of back rest). Required when posture is seated.
Damian Q6. Eye-level height in cm from the floor. Carries the seated OR standing variant depending on posture — the frontend UI prompt switches wording, the field value lands here either way.
Damian Q7. Top-of-screen height in cm from the floor. Same seated/standing variant pattern as eye_level_height.
Subject anthropometry (Damian Q1, physical height). Optional — only sent when the client profile lacks a height; the create path writes it back to Client.height_cm.
object
Damian Q1. Subject’s physical height in cm (floor → top of head). Asked once when the client profile lacks a height; written back to Client.height_cm on session create. Omit when the profile already carries the height — the analyzer reads it from there.
Damian Q8–Q9. Pain assessment + body-part dropdown.
object
Damian Q8. True if the subject reports pain/discomfort at the workstation.
head_neck- Head / Neckshoulder_upper_back- Shoulder / Upper Backtorso- Torsohip_lower_back- Hip / Lower Backlower_body- Lower Bodyarms_wrists- Arms / Wrists
Damian Q9. Required when has_pain is true. One of the six anatomical groups from the survey dropdown.
head_neck- Head / Neckshoulder_upper_back- Shoulder / Upper Backtorso- Torsohip_lower_back- Hip / Lower Backlower_body- Lower Bodyarms_wrists- Arms / Wrists
Damian Q10. How long the subject sits at the desk without a posture change, in minutes. Damian’s biomech grounding: stretches >30 min create MSK tension (per occupational-health research). The analyzer-side risk score weights this alongside the pose readings.
Damian Q11. Image or short video showing the workstation subject in their typical posture. The uploaded URL comes from the second presign returned by the upload-intent endpoint.
object
Canonical URL of the uploaded posture media (image or short video). Returned as posture_media.public_url by the upload-intent endpoint. Validated against the storage public-origin allowlist — the URL is later fetched by the ergonomist render path + the analyzer, so accepting an arbitrary host would be an SSRF gadget.
Whether the upload is a still image or a short (1–3 s) video. Both are accepted per Damian’s spec — coaches at the Pegel Padel pilot site default to image; short video is the fallback when the static frame is ambiguous (e.g. mid-stride).
image- Imagevideo- Video
Optional coach-assigned label for the session (#584), shown in the journal, the player header and shared views — handy for telling apart same-day, same-exercise sessions of one participant (e.g. Kevin – Baseline – Side View). Editable later via PATCH /api/sessions/{id}/. Omit for none.
Optional time range to trim the clip to, in seconds ({start_s, end_s}). Applied server-side during normalization, so the cut is accurate at any source frame rate. Omit to process the full clip.
object
Trim start in seconds from the clip origin.
Trim end in seconds; must be greater than start_s.
Opt-in face anonymisation. true → the subject’s face is blurred in the analysed (annotated) video — the artifact that is stored long-term, shown in the player and shared with the client. Metrics are unaffected (analysis runs on the clean frames first). The raw upload itself is NOT anonymised. Omit for the default (no blur).
Treadmill incline in degrees for running / walking treadmill sessions (0 = overground / level belt, the default). Positive = uphill, negative = decline treadmill. The analyzer re-references the foot-strike contact angle to the belt plane (legacy compute_fs_angles) and reports signed elevation change (gain uphill / loss on a decline), so an inclined run reads its true strike angle rather than one inflated by the slope. Stored on Session.meta.treadmill_incline_deg and forwarded to aikmodels. Silently ignored for non-gait activities. Omit (or 0) for overground.
Coach’s platform UI language (en / ru / es) at upload time. Snapshotted onto Session.meta.lang and forwarded to aikmodels so the joint-angle chips baked onto the annotated video render in that language (#1194); the angle values (°) are unchanged. Draw-stage only — no metric value depends on it. Omit (or en) for English, the default. Because the annotated mp4 is rendered once at processing, this reflects the language at that time; switching the UI language later does not retro-localize an already-rendered video.
en- Englishru- Русскийes- Español
Bowling approach step count — 4 or 5. The bowler knows their own pattern, so this is authoritative: the analyzer reports it as the step count and cross-checks it against auto-detection (a mismatch flags low phase-detection confidence). Stored on Session.meta.approach_step_count and forwarded to aikmodels. Silently ignored for non-bowling activities. Omit to auto-detect.
4- 4-step5- 5-step
Barbell mass in kg for weightlifting sessions — total load on the bar including the bar itself. The lifter declares it at upload (it is not derivable from video: the plate detector tracks bar position, not load). Feeds the GRF / power formulas (F = (m_athlete + m_barbell) × (a + g)). Stored on Session.meta.barbell_mass_kg and forwarded to aikmodels. Silently ignored for non-weightlifting activities. Omit when unknown — mass-dependent lift metrics are then suppressed rather than computed from an assumed load.
Measured course length in metres for a fixed-distance walk test — the 10 m walk, or the 6 m / 4 m gait-speed variants. The distance is a property of the protocol (marked on the floor, measured with a tape), so it cannot be recovered from the video: declare it here and the analyzer reports walking speed as distance ÷ traversal time, bypassing the height-derived pixel scale the standard speed estimate depends on. Stored on Session.meta.walk_test_distance_m and forwarded to aikmodels. Silently ignored for non-walking activities. Omit for a free walk with no marked course.
Examples
Running, with reserved session_id from upload-intent
{ "id": "5c836d7d-3301-49df-bfa0-9cff0550fd0e", "activity": "running", "exercise": "Treadmill", "video_url": "https://s3.ai.aikynetix.app/aikynetix-media/teams/2648a781-8751-47c0-836d-2c0189792d71/sessions/2026/04/5c836d7d-3301-49df-bfa0-9cff0550fd0e.source.mp4", "client": "f6d52e64-3aa3-4a26-bac2-0fc9d718448d", "camera_view": "side"}object
The session UUID reserved by POST /api/sessions/upload-intent/. Pass it back here so the row id matches the storage object key. Optional for ad-hoc creation without an upload-intent roundtrip.
Activity family — running, walking, jumps, weightlifting, agility, mobility, fencing, ergonomics. Required.
running- Runningwalking- Walkingjump- Vertical Jumpweightlifting- Weightliftingmobility- Mobility Assessmentworkspace_wellness- Workspace Wellnessagility- Agilityfencing- Fencingcycling- Cyclingtennis- Tennispadel- Padel Tennisbowling- Bowlingbaseball- Baseballfootball- Footballbadminton- Badmintoncricket- Cricketbasketball- Basketballscuba_diving- Scuba Divingice_hockey- Ice Hockeyamerican_football- American Footballfri- Falls Risk Indicator
Specific movement (e.g. Treadmill, Back Squat). Pick from GET /api/exercises/?activity=.... Optional but strongly recommended — without it the analyzer uses activity-default settings and metrics may be less specific.
UUID of the Client this session is about. Recommended — without it body-aware metric adjustments fall back to defaults.
Optional dominant-hand hint (right by default). Read by handedness-aware analyzers — fencing maps raw left/right pose-landmark sides to semantic front/rear by lead leg; tennis and bowling map them to dominance-relative keys so a left-handed forehand (or left-handed bowler) reads identically to the right-handed case. Silently ignored by activities whose analyzers don’t read this field.
right- Right-handedleft- Left-handed
Camera plane for the recording. side (sagittal, default) runs the stride / GCT / speed / sagittal-angle analyzer. front / back swap to the frontal analyzer (pelvic drop, hip adduction, step width, trunk sway, L/R asymmetry). When exercise is set, the value must be one of the pair’s supported views — an unsupported plane is a 400 (#1829). See views_by_exercise in GET /api/exercises/?activity=… for each pair’s supported set.
side- Sidefront- Frontback- Back
Scuba diving session-meta sidecar — required when activity == scuba_diving, silently ignored for every other activity. Validates side_involved, condition, arms_engaged, and the optional clinical fields (Borg CR-10, fin model, calibration scale bar). Stored on Session.meta.scuba so the analyzer can read it via the X-Aikmodels-Contract 3→4 plumbing (P0.5). See project_context/architecture/DESIGN_SCUBA_V1.md §“Session metadata sidecar”.
object
L- LeftR- Right
Side of the amputation / involved limb. L or R for amputee divers; null for intact-control rows. The analyzer uses this to map raw left/right kinematics into involved/uninvolved semantics (TR-4.2 verbatim — never inferred from the video).
L- LeftR- Right
§5.2 condition tag. Used for cohort tagging on the dashboard + the report PDF cover. Must match the trial design — not derived from other fields, because the same diver shoots multiple conditions per session.
control_no_arms- Control — no armscontrol_with_arms- Control — with armstranstibial- Transtibialtransfemoral- Transfemoral
Confound flag (§4.6). True when the diver was sculling / using arms for propulsion during the bout. Surfaces in the report so coaches know to filter symmetry numbers.
Fin model — free-form (manufacturer + model).
Fin stiffness. Free-form: either a qualitative grade (soft / medium / stiff) or a numeric string with units (12 N·m/rad). Analyzer doesn’t consume it in V1; captured for the report + future fin-deflection v2 work.
Borg CR-10 perceived exertion (residual side), 0–10.
Borg CR-10 perceived exertion (sound side), 0–10.
Length of the in-frame calibration scale bar (§6.1), in metres. Optional — without it, body-aware distance metrics use body_scale_px(ctx) per the standard fallback.
Damian’s 12-question ergonomics survey payload — required for the workspace_wellness:Ergonomics Assessment exercise, silently ignored for every other activity/exercise. Gated on the (activity, exercise) pair, so a future workspace_wellness exercise does not inherit the mandatory survey. Lands on Session.meta.workstation_assessment after validation. See #303 for the full spec + biomech rationale.
object
Reusable workstation setup — posture + the six desk/seat/screen geometry measurements (Damian Q2–Q7). Snapshotted onto the session and persisted on the client for reuse.
object
Anton’s sit/stand toggle. Drives which measurement fields are required (seat-related fields are seated-only). When both apply to the subject’s workstation, the spec is to upload two separate sessions per CEO routing — one row per posture.
seated- Seatedstanding- Standing
Damian Q2–Q7. Six desk/seat/screen geometry measurements; three are seated-only.
object
Damian Q2. Desk-top height in cm (floor → desk surface).
Damian Q3. Seat-top height in cm (floor → top of seat pan). Required when posture is seated.
Damian Q4. Seat-pan depth in cm — measured from the point where the back rest meets the seat pan to the front edge of the seat pan. Required when posture is seated.
Damian Q5. Back-rest height in cm (seat pan → top of back rest). Required when posture is seated.
Damian Q6. Eye-level height in cm from the floor. Carries the seated OR standing variant depending on posture — the frontend UI prompt switches wording, the field value lands here either way.
Damian Q7. Top-of-screen height in cm from the floor. Same seated/standing variant pattern as eye_level_height.
Subject anthropometry (Damian Q1, physical height). Optional — only sent when the client profile lacks a height; the create path writes it back to Client.height_cm.
object
Damian Q1. Subject’s physical height in cm (floor → top of head). Asked once when the client profile lacks a height; written back to Client.height_cm on session create. Omit when the profile already carries the height — the analyzer reads it from there.
Damian Q8–Q9. Pain assessment + body-part dropdown.
object
Damian Q8. True if the subject reports pain/discomfort at the workstation.
head_neck- Head / Neckshoulder_upper_back- Shoulder / Upper Backtorso- Torsohip_lower_back- Hip / Lower Backlower_body- Lower Bodyarms_wrists- Arms / Wrists
Damian Q9. Required when has_pain is true. One of the six anatomical groups from the survey dropdown.
head_neck- Head / Neckshoulder_upper_back- Shoulder / Upper Backtorso- Torsohip_lower_back- Hip / Lower Backlower_body- Lower Bodyarms_wrists- Arms / Wrists
Damian Q10. How long the subject sits at the desk without a posture change, in minutes. Damian’s biomech grounding: stretches >30 min create MSK tension (per occupational-health research). The analyzer-side risk score weights this alongside the pose readings.
Damian Q11. Image or short video showing the workstation subject in their typical posture. The uploaded URL comes from the second presign returned by the upload-intent endpoint.
object
Canonical URL of the uploaded posture media (image or short video). Returned as posture_media.public_url by the upload-intent endpoint. Validated against the storage public-origin allowlist — the URL is later fetched by the ergonomist render path + the analyzer, so accepting an arbitrary host would be an SSRF gadget.
Whether the upload is a still image or a short (1–3 s) video. Both are accepted per Damian’s spec — coaches at the Pegel Padel pilot site default to image; short video is the fallback when the static frame is ambiguous (e.g. mid-stride).
image- Imagevideo- Video
Optional coach-assigned label for the session (#584), shown in the journal, the player header and shared views — handy for telling apart same-day, same-exercise sessions of one participant (e.g. Kevin – Baseline – Side View). Editable later via PATCH /api/sessions/{id}/. Omit for none.
Optional time range to trim the clip to, in seconds ({start_s, end_s}). Applied server-side during normalization, so the cut is accurate at any source frame rate. Omit to process the full clip.
object
Trim start in seconds from the clip origin.
Trim end in seconds; must be greater than start_s.
Opt-in face anonymisation. true → the subject’s face is blurred in the analysed (annotated) video — the artifact that is stored long-term, shown in the player and shared with the client. Metrics are unaffected (analysis runs on the clean frames first). The raw upload itself is NOT anonymised. Omit for the default (no blur).
Treadmill incline in degrees for running / walking treadmill sessions (0 = overground / level belt, the default). Positive = uphill, negative = decline treadmill. The analyzer re-references the foot-strike contact angle to the belt plane (legacy compute_fs_angles) and reports signed elevation change (gain uphill / loss on a decline), so an inclined run reads its true strike angle rather than one inflated by the slope. Stored on Session.meta.treadmill_incline_deg and forwarded to aikmodels. Silently ignored for non-gait activities. Omit (or 0) for overground.
Coach’s platform UI language (en / ru / es) at upload time. Snapshotted onto Session.meta.lang and forwarded to aikmodels so the joint-angle chips baked onto the annotated video render in that language (#1194); the angle values (°) are unchanged. Draw-stage only — no metric value depends on it. Omit (or en) for English, the default. Because the annotated mp4 is rendered once at processing, this reflects the language at that time; switching the UI language later does not retro-localize an already-rendered video.
en- Englishru- Русскийes- Español
Bowling approach step count — 4 or 5. The bowler knows their own pattern, so this is authoritative: the analyzer reports it as the step count and cross-checks it against auto-detection (a mismatch flags low phase-detection confidence). Stored on Session.meta.approach_step_count and forwarded to aikmodels. Silently ignored for non-bowling activities. Omit to auto-detect.
4- 4-step5- 5-step
Barbell mass in kg for weightlifting sessions — total load on the bar including the bar itself. The lifter declares it at upload (it is not derivable from video: the plate detector tracks bar position, not load). Feeds the GRF / power formulas (F = (m_athlete + m_barbell) × (a + g)). Stored on Session.meta.barbell_mass_kg and forwarded to aikmodels. Silently ignored for non-weightlifting activities. Omit when unknown — mass-dependent lift metrics are then suppressed rather than computed from an assumed load.
Measured course length in metres for a fixed-distance walk test — the 10 m walk, or the 6 m / 4 m gait-speed variants. The distance is a property of the protocol (marked on the floor, measured with a tape), so it cannot be recovered from the video: declare it here and the analyzer reports walking speed as distance ÷ traversal time, bypassing the height-derived pixel scale the standard speed estimate depends on. Stored on Session.meta.walk_test_distance_m and forwarded to aikmodels. Silently ignored for non-walking activities. Omit for a free walk with no marked course.
object
The session UUID reserved by POST /api/sessions/upload-intent/. Pass it back here so the row id matches the storage object key. Optional for ad-hoc creation without an upload-intent roundtrip.
Activity family — running, walking, jumps, weightlifting, agility, mobility, fencing, ergonomics. Required.
running- Runningwalking- Walkingjump- Vertical Jumpweightlifting- Weightliftingmobility- Mobility Assessmentworkspace_wellness- Workspace Wellnessagility- Agilityfencing- Fencingcycling- Cyclingtennis- Tennispadel- Padel Tennisbowling- Bowlingbaseball- Baseballfootball- Footballbadminton- Badmintoncricket- Cricketbasketball- Basketballscuba_diving- Scuba Divingice_hockey- Ice Hockeyamerican_football- American Footballfri- Falls Risk Indicator
Specific movement (e.g. Treadmill, Back Squat). Pick from GET /api/exercises/?activity=.... Optional but strongly recommended — without it the analyzer uses activity-default settings and metrics may be less specific.
UUID of the Client this session is about. Recommended — without it body-aware metric adjustments fall back to defaults.
Optional dominant-hand hint (right by default). Read by handedness-aware analyzers — fencing maps raw left/right pose-landmark sides to semantic front/rear by lead leg; tennis and bowling map them to dominance-relative keys so a left-handed forehand (or left-handed bowler) reads identically to the right-handed case. Silently ignored by activities whose analyzers don’t read this field.
right- Right-handedleft- Left-handed
Camera plane for the recording. side (sagittal, default) runs the stride / GCT / speed / sagittal-angle analyzer. front / back swap to the frontal analyzer (pelvic drop, hip adduction, step width, trunk sway, L/R asymmetry). When exercise is set, the value must be one of the pair’s supported views — an unsupported plane is a 400 (#1829). See views_by_exercise in GET /api/exercises/?activity=… for each pair’s supported set.
side- Sidefront- Frontback- Back
Scuba diving session-meta sidecar — required when activity == scuba_diving, silently ignored for every other activity. Validates side_involved, condition, arms_engaged, and the optional clinical fields (Borg CR-10, fin model, calibration scale bar). Stored on Session.meta.scuba so the analyzer can read it via the X-Aikmodels-Contract 3→4 plumbing (P0.5). See project_context/architecture/DESIGN_SCUBA_V1.md §“Session metadata sidecar”.
object
L- LeftR- Right
Side of the amputation / involved limb. L or R for amputee divers; null for intact-control rows. The analyzer uses this to map raw left/right kinematics into involved/uninvolved semantics (TR-4.2 verbatim — never inferred from the video).
L- LeftR- Right
§5.2 condition tag. Used for cohort tagging on the dashboard + the report PDF cover. Must match the trial design — not derived from other fields, because the same diver shoots multiple conditions per session.
control_no_arms- Control — no armscontrol_with_arms- Control — with armstranstibial- Transtibialtransfemoral- Transfemoral
Confound flag (§4.6). True when the diver was sculling / using arms for propulsion during the bout. Surfaces in the report so coaches know to filter symmetry numbers.
Fin model — free-form (manufacturer + model).
Fin stiffness. Free-form: either a qualitative grade (soft / medium / stiff) or a numeric string with units (12 N·m/rad). Analyzer doesn’t consume it in V1; captured for the report + future fin-deflection v2 work.
Borg CR-10 perceived exertion (residual side), 0–10.
Borg CR-10 perceived exertion (sound side), 0–10.
Length of the in-frame calibration scale bar (§6.1), in metres. Optional — without it, body-aware distance metrics use body_scale_px(ctx) per the standard fallback.
Damian’s 12-question ergonomics survey payload — required for the workspace_wellness:Ergonomics Assessment exercise, silently ignored for every other activity/exercise. Gated on the (activity, exercise) pair, so a future workspace_wellness exercise does not inherit the mandatory survey. Lands on Session.meta.workstation_assessment after validation. See #303 for the full spec + biomech rationale.
object
Reusable workstation setup — posture + the six desk/seat/screen geometry measurements (Damian Q2–Q7). Snapshotted onto the session and persisted on the client for reuse.
object
Anton’s sit/stand toggle. Drives which measurement fields are required (seat-related fields are seated-only). When both apply to the subject’s workstation, the spec is to upload two separate sessions per CEO routing — one row per posture.
seated- Seatedstanding- Standing
Damian Q2–Q7. Six desk/seat/screen geometry measurements; three are seated-only.
object
Damian Q2. Desk-top height in cm (floor → desk surface).
Damian Q3. Seat-top height in cm (floor → top of seat pan). Required when posture is seated.
Damian Q4. Seat-pan depth in cm — measured from the point where the back rest meets the seat pan to the front edge of the seat pan. Required when posture is seated.
Damian Q5. Back-rest height in cm (seat pan → top of back rest). Required when posture is seated.
Damian Q6. Eye-level height in cm from the floor. Carries the seated OR standing variant depending on posture — the frontend UI prompt switches wording, the field value lands here either way.
Damian Q7. Top-of-screen height in cm from the floor. Same seated/standing variant pattern as eye_level_height.
Subject anthropometry (Damian Q1, physical height). Optional — only sent when the client profile lacks a height; the create path writes it back to Client.height_cm.
object
Damian Q1. Subject’s physical height in cm (floor → top of head). Asked once when the client profile lacks a height; written back to Client.height_cm on session create. Omit when the profile already carries the height — the analyzer reads it from there.
Damian Q8–Q9. Pain assessment + body-part dropdown.
object
Damian Q8. True if the subject reports pain/discomfort at the workstation.
head_neck- Head / Neckshoulder_upper_back- Shoulder / Upper Backtorso- Torsohip_lower_back- Hip / Lower Backlower_body- Lower Bodyarms_wrists- Arms / Wrists
Damian Q9. Required when has_pain is true. One of the six anatomical groups from the survey dropdown.
head_neck- Head / Neckshoulder_upper_back- Shoulder / Upper Backtorso- Torsohip_lower_back- Hip / Lower Backlower_body- Lower Bodyarms_wrists- Arms / Wrists
Damian Q10. How long the subject sits at the desk without a posture change, in minutes. Damian’s biomech grounding: stretches >30 min create MSK tension (per occupational-health research). The analyzer-side risk score weights this alongside the pose readings.
Damian Q11. Image or short video showing the workstation subject in their typical posture. The uploaded URL comes from the second presign returned by the upload-intent endpoint.
object
Canonical URL of the uploaded posture media (image or short video). Returned as posture_media.public_url by the upload-intent endpoint. Validated against the storage public-origin allowlist — the URL is later fetched by the ergonomist render path + the analyzer, so accepting an arbitrary host would be an SSRF gadget.
Whether the upload is a still image or a short (1–3 s) video. Both are accepted per Damian’s spec — coaches at the Pegel Padel pilot site default to image; short video is the fallback when the static frame is ambiguous (e.g. mid-stride).
image- Imagevideo- Video
Optional coach-assigned label for the session (#584), shown in the journal, the player header and shared views — handy for telling apart same-day, same-exercise sessions of one participant (e.g. Kevin – Baseline – Side View). Editable later via PATCH /api/sessions/{id}/. Omit for none.
Optional time range to trim the clip to, in seconds ({start_s, end_s}). Applied server-side during normalization, so the cut is accurate at any source frame rate. Omit to process the full clip.
object
Trim start in seconds from the clip origin.
Trim end in seconds; must be greater than start_s.
Opt-in face anonymisation. true → the subject’s face is blurred in the analysed (annotated) video — the artifact that is stored long-term, shown in the player and shared with the client. Metrics are unaffected (analysis runs on the clean frames first). The raw upload itself is NOT anonymised. Omit for the default (no blur).
Treadmill incline in degrees for running / walking treadmill sessions (0 = overground / level belt, the default). Positive = uphill, negative = decline treadmill. The analyzer re-references the foot-strike contact angle to the belt plane (legacy compute_fs_angles) and reports signed elevation change (gain uphill / loss on a decline), so an inclined run reads its true strike angle rather than one inflated by the slope. Stored on Session.meta.treadmill_incline_deg and forwarded to aikmodels. Silently ignored for non-gait activities. Omit (or 0) for overground.
Coach’s platform UI language (en / ru / es) at upload time. Snapshotted onto Session.meta.lang and forwarded to aikmodels so the joint-angle chips baked onto the annotated video render in that language (#1194); the angle values (°) are unchanged. Draw-stage only — no metric value depends on it. Omit (or en) for English, the default. Because the annotated mp4 is rendered once at processing, this reflects the language at that time; switching the UI language later does not retro-localize an already-rendered video.
en- Englishru- Русскийes- Español
Bowling approach step count — 4 or 5. The bowler knows their own pattern, so this is authoritative: the analyzer reports it as the step count and cross-checks it against auto-detection (a mismatch flags low phase-detection confidence). Stored on Session.meta.approach_step_count and forwarded to aikmodels. Silently ignored for non-bowling activities. Omit to auto-detect.
4- 4-step5- 5-step
Barbell mass in kg for weightlifting sessions — total load on the bar including the bar itself. The lifter declares it at upload (it is not derivable from video: the plate detector tracks bar position, not load). Feeds the GRF / power formulas (F = (m_athlete + m_barbell) × (a + g)). Stored on Session.meta.barbell_mass_kg and forwarded to aikmodels. Silently ignored for non-weightlifting activities. Omit when unknown — mass-dependent lift metrics are then suppressed rather than computed from an assumed load.
Measured course length in metres for a fixed-distance walk test — the 10 m walk, or the 6 m / 4 m gait-speed variants. The distance is a property of the protocol (marked on the floor, measured with a tape), so it cannot be recovered from the video: declare it here and the analyzer reports walking speed as distance ÷ traversal time, bypassing the height-derived pixel scale the standard speed estimate depends on. Stored on Session.meta.walk_test_distance_m and forwarded to aikmodels. Silently ignored for non-walking activities. Omit for a free walk with no marked course.
Responses
Section titled “Responses”object
Server-assigned UUID. Use the same value as session_id from the upload-intent step to keep the storage object key consistent.
Activity family — running, walking, jumps, weightlifting, agility, mobility, fencing, ergonomics. Drives which analyzer runs.
running- Runningwalking- Walkingjump- Vertical Jumpweightlifting- Weightliftingmobility- Mobility Assessmentworkspace_wellness- Workspace Wellnessagility- Agilityfencing- Fencingcycling- Cyclingtennis- Tennispadel- Padel Tennisbowling- Bowlingbaseball- Baseballfootball- Footballbadminton- Badmintoncricket- Cricketbasketball- Basketballscuba_diving- Scuba Divingice_hockey- Ice Hockeyamerican_football- American Footballfri- Falls Risk Indicator
Specific movement within the activity (e.g. Treadmill, Back Squat, Single-Leg Stand). Pick from GET /api/exercises/?activity=....
Stable per-team session number (#941), assigned at create time and immutable — the shared reference coaches use to point at a video (‘look at session 42’). Monotonic per team in creation order; survives filter / sort / pagination and reads the same for every teammate. Gaps appear where sessions were deleted (numbers are never reused). null on demo sessions (cross-team, deliberately unnumbered) and on the public /api/shared/{token}/ payload, where the team-internal ordinal is withheld from anonymous viewers.
Lifecycle state: pending (queued) → processing (analyzer running) → completed (metrics + annotated mp4 ready) | failed (see failure_reason).
pending- Pendingprocessing- Processingcompleted- Completedfailed- Failed
Public URL of the annotated playback mp4 — pose skeleton drawn over the original frames. Populated when status == 'completed'; empty before that and during reanalyze.
Public URL of a face-anonymised copy of the source video WITHOUT the pose overlay (#1233) — the privacy-safe ‘raw’ source for the player’s overlay toggle. Populated only on Face-Blur sessions (blur_face == true); empty otherwise, when the best-effort clean render failed, and during reanalyze. On non-blur sessions the player toggles to video_url directly. On the public /api/shared/{token}/ payload the un-anonymised video_url is withheld for blur sessions and this field is the only ‘raw’ source.
Public URL of the 3D pose sidecar JSON (#1356) — the per-frame 3D joint track uploaded next to the annotated mp4, consumed by the player’s interactive 3D viewer. Empty when the 3D lifter isn’t configured on the inference service, when its best-effort generation failed, and during reanalyze — clients then hide the 3D view. Served on the public /api/shared/{token}/ payload, EXCEPT on a Face-Blur session: it carries no imagery, but it reconstructs the athlete’s movement in full, so it is blanked there alongside video_url.
Public URL of a static JPG poster (~5-15 KB) extracted at ~0.5 s of the annotated mp4. The SPA renders this on the journal row instead of mounting a <video preload=metadata> per row, which avoids the main-thread freeze on long session lists. Empty when aikmodels couldn’t generate one — clients fall back to analyzed_url and decode the first frame themselves.
Frame rate the session was ANALYSED at — read off the normalized file, not the upload. Every metric and per-frame derivative in the report is a function of this number, which is why it is the one reported. It is min(AIKMODELS_NORMALIZE_FPS, source_fps) (#1951), so on a 60 or 120 fps upload it reads BELOW what the coach filmed; compare source_fps to tell the two apart.
Frame rate the camera actually captured at, probed before our normalize resample. Null on older sessions and when the probe could not run. Present so a client can say “filmed at 60, analysed at 30” rather than showing our rate as though it were the coach’s (#1951). May be fractional — 23.976 and 29.97 are real capture rates.
Source video duration in milliseconds.
ISO-8601 UTC timestamp of the session’s LAST successful analysis. Null until the first one completes; it is NOT cleared afterwards, so a session that delivered a report once and was then re-analyzed keeps its timestamp while sitting on pending/processing/failed.
ISO-8601 UTC timestamp of session creation.
UUID of the Client (athlete) the session is about. Optional but strongly recommended — without a client, body-aware metric adjustments fall back to defaults.
UUID of the Admin who uploaded the session. Team-scoped listings carry it so per-coach surfaces (e.g. the upload wizard’s recent-combos row) can filter to the current admin’s own history without leaking a teammate’s picks. null for legacy rows created before attribution was recorded.
Human-readable explanation when status == 'failed'. Empty otherwise.
List of all SessionMetric rows attached to this session — joint angles, timings, scores, the full per-key catalogue the analyzer emitted. Empty until status == 'completed'.
object
object
Body-aware-adjusted green-zone bounds keyed by metric: {key: {min_good, max_good, amber_low, amber_high, higher_is_better, beta}}. Already accounts for the athlete’s gender + height — front-end can render the zone directly without re-applying any adjustment. amber_low / amber_high are the ONROM (amber) outer bounds of the three-tier clinical band (#521); both null ⇒ classic binary green zone (the front-end derives the wnl/onrom/high_risk tier from them). beta flags a metric that has not cleared clinical validation (UI shows a precision caveat). Empty list when the activity has no zone-defining specs.
object
side / front / back. Read-only; derived from the value passed at session create time. Lets the player UI badge the view and hide metric cards that don’t apply.
Capture-quality findings for a COMPLETED session (#1868) — the typed defect taxonomy evaluated from the inference service’s capture signals: [{code, severity, data}], e.g. {"code": "pose_sparse", "severity": "advisory", "data": {"detection_ratio": 0.42}}. Empty list = evaluated clean; null = the session predates capture evaluation (never conflate the two). Codes: pose_sparse / implausible_geometry / subject_too_far / camera_off_plane / subject_left_frame. Severity is advisory-only today — fatal capture problems FAIL the session and speak through failure_reason.
object
right / left dominant hand or foot, or null. Read-only; from the value passed at create time. Seeds the reprocess dialog so a handedness-aware re-run keeps the athlete’s side instead of silently defaulting to right-handed (#775).
Worker’s most recent stage emit while this session is in flight. null for terminal states, queued sessions, and any read failure — the SPA renders a queued placeholder in those cases. Polled via the existing session-detail refetch (every 2 s while pending/processing); no separate endpoint.
object
Pipeline stage the worker is currently executing (e.g. download, pose_extract, draw). The full stage list is hardcoded on each side and kept in sync via a contract test (apps/api side) — adding or renaming a stage is a cross-track PR per ADR-0011.
0-based index of stage in the worker’s stage order.
Frames processed so far inside the current stage. Only moves during stages with sub-progress (today: pose_extract); 0 elsewhere.
Total frame count for the current stage; 0 when the stage has no frame counter or the worker hasn’t computed it yet.
Whether a live public share link exists for this session — so the player can offer Revoke on a session shared in an earlier visit. null means NOT REPORTED, which is every list response: the value is annotated by the session-detail view only, because a per-row lookup would be one query per session on a list that has no use for it. Treat null as ‘unknown’, not as ‘no share’.
Language (en / ru / es) frozen on the share when the coach created/refreshed it — only populated on the public GET /api/shared/{token}/ response, where the /r/<token> page reads it to render its whole UI in the same language as the AI coaching paragraph (issue #339). null on the authenticated session endpoints, which follow the coach’s own UI locale.
True when this session was created with the Blur Face option — the analysed video has the subject’s face anonymised. Read-only; false for every session created without the flag.
The lifter’s declared total bar load in kg, or null when never declared. Read-only; from the value passed at create time (#1325). Seeds the reprocess dialog so a weightlifting re-run keeps the declared load instead of silently losing it (the repoint clears stale meta by default).
The declared walk-test course in metres, or null when the session has none (#1493). Read-only. Seeds the reprocess dialog so a Timed Walk re-run keeps the measured course — and so a course typed wrong at upload can be corrected there instead of re-filming a perfectly good walk.
True when this session already has a scuba session-meta sidecar on Session.meta.scuba. A boolean only — the clinical payload (side / condition / arms-engaged) is never exposed. The reprocess dialog gates on it: a scuba re-run that already has a sidecar carries the stored one over (no blind overwrite); one without it (legacy / re-routing in from another activity) must supply it (#775).
How many active Solution reports bind this session — the reverse of the report→sessions link, shown as a ‘used in N reports’ badge on the journal row (#1192). Only populated on the session-list endpoint (the view seeds a per-page map to avoid N+1); null everywhere else, where the FE simply omits the badge.
The active Solution reports that bind this session, each with a ready name + slug — powers the ‘used in N reports’ links on the single-session player (#1192). Only populated on the session-detail endpoint (the view seeds the reverse-scan for the one session); an empty list elsewhere, where the FE shows no links.
object
Movement Norms Library entry for this session’s movement (reference table + explanation), localised to the session’s locale. Populated ONLY on the public shared payload (#1118) so the /r/
object
Plain-English description of the movement and what its variables capture.
One evidence-backed fact, with citation where available.
How the bands shift by sex/age.
What improves from beginner to advanced.
Per-metric reference row for the Movement Guide table. min_good/max_good are the team-default reference band (same as the Threshold configurator) in the metric’s canonical unit; evidence_basis is validated | experimental | assumed; reference_only metrics are library variables we don’t yet compute for a session.
object
Examples
Created, queued for analysis
{ "id": "5c836d7d-3301-49df-bfa0-9cff0550fd0e", "activity": "running", "exercise": "Treadmill", "status": "pending", "video_url": "https://s3.ai.aikynetix.app/aikynetix-media/teams/2648a781-8751-47c0-836d-2c0189792d71/sessions/2026/04/5c836d7d-3301-49df-bfa0-9cff0550fd0e.source.mp4", "analyzed_url": "", "thumbnail_url": "", "fps": null, "duration_ms": null, "processed_at": null, "created_at": "2026-04-29T11:15:55Z", "client": "f6d52e64-3aa3-4a26-bac2-0fc9d718448d", "uploaded_by": "9b3f1c2a-7e44-4d1b-8c0a-12ab34cd56ef", "failure_reason": "", "metrics": [], "metric_targets": {}, "camera_view": "side"}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).
Examplegenerated
{ "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).
Examplegenerated
{ "detail": "example"}object
One of five 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.
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 surface
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 uploaded 5 sessions this period, hit the 6th
{ "detail": "quota_exhausted", "reset_at": "2026-06-01T00:00:00Z", "current_plan": "free", "current_role": "free"}Starter team uploaded 20 sessions this period
{ "detail": "quota_exhausted", "reset_at": "2026-06-13T00:00:00Z", "current_plan": "starter", "current_role": "starter"}Pro team in past_due; Stripe Smart Retries in flight
{ "detail": "subscription_suspended", "reset_at": "2026-05-15T00:00:00Z", "current_plan": "professional", "current_role": "professional"}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).
Examplegenerated
{ "detail": "example"}object
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.
Examples
Quota debited then the task broker was unreachable; retry
{ "detail": "enqueue_failed"}