Create a client on the current team
curl -X POST https://api.ai.aikynetix.app/api/clients/ \ -H 'Authorization: Bearer aik_yourPartnerTokenHere' \ -H 'Content-Type: application/json' \ -d '{ "display_name": "Anna Petrova", "gender": "female", "height_cm": 168, "weight_kg": 58, "external_uid": "club-roster-id-2419" }'import requestsr = requests.post( "https://api.ai.aikynetix.app/api/clients/", headers={"Authorization": "Bearer aik_yourPartnerTokenHere"}, json={ "display_name": "Anna Petrova", "gender": "female", "height_cm": 168, "weight_kg": 58, "external_uid": "club-roster-id-2419", }, timeout=30,)if r.status_code == 402: raise RuntimeError(r.json()["detail"]) # client_limit_reachedr.raise_for_status()client = r.json()const r = await fetch("https://api.ai.aikynetix.app/api/clients/", { method: "POST", headers: { Authorization: "Bearer aik_yourPartnerTokenHere", "Content-Type": "application/json" }, body: JSON.stringify({ display_name: "Anna Petrova", gender: "female", height_cm: 168, weight_kg: 58, external_uid: "club-roster-id-2419", }),});if (r.status === 402) throw new Error((await r.json()).detail);if (!r.ok) throw new Error(`HTTP ${r.status}`);const client = await r.json();Creates a new Client (athlete) under the signed-in coach’s active team. display_name, date_of_birth, height_cm and weight_kg are required — they drive the age- and body-aware green zones; everything else (email, gender, leg_length_cm, shoe_size, skill_level, external_uid) is optional. external_uid is your own stable id for cross-system joins; we never interpret it. Returns the created Client.
Athletes under 18. is_minor is true by date_of_birth, or when you send minor_declared: true. Until a parent’s or guardian’s consent is recorded (guardian_consent, on create or a later PATCH; null withdraws it) every analysis of the athlete is face-blurred whatever the session asks for, and public share links are refused with 403 guardian_consent_required.
402 — client_limit_reached when the team has already filled plan.max_client_profiles. Free’s (1) cap makes this fire on the second create; the payload shape is identical to the other billing gates (quota / suspended / seat) — see PaywallErrorResponse.
403 when the caller has no active team selected (a transient state, e.g. mid team-switch or just after the active team was deleted).
Authorizations
Section titled “Authorizations”Request Bodyrequired
Section titled “Request Bodyrequired”object
Athlete’s display name as the coach refers to them. Required.
male / female / nonbinary / empty string — case and surrounding whitespace are normalised, anything else is a 400. Drives gender-specific green-zone overrides on running cadence, stride length, jump power, and other normative metrics. Leave blank if unknown — you’ll get the all-genders defaults.
ISO date (YYYY-MM-DD). Required on write; reads back null on athletes migrated before the requirement. Drives age-band-aware green zones — a masters runner and a junior are judged against age-appropriate bands. Stored as the date, not the age, so it never goes stale.
Declare the athlete under 18 when the date of birth doesn’t show it, or there is none (#2751). Default false. Does not change age_band, which stays from the date of birth.
The parent’s or guardian’s consent in force, or null. Write an object to record one (on create or update), null to withdraw it. Without one, a minor’s analyses are always face-blurred and no public link to their data is minted or served (#2751).
object
The parent or guardian who gave consent.
How they are related to the athlete, e.g. mother, legal guardian.
How consent was given: signed_form, email, in_person or other.
signed_form- Signed formemail- Emailin_person- In personother- Other
The day the guardian consented (YYYY-MM-DD).
Anything else worth keeping, e.g. where the signed form is filed.
beginner- Beginnerintermediate- Intermediateadvanced- Advanced
Training experience: beginner / intermediate / advanced / empty string. Drives skill-level-specific green-zone overrides — a beginner and an advanced athlete are judged against different bands. Coach-set; leave blank for all-levels defaults.
beginner- Beginnerintermediate- Intermediateadvanced- Advanced
Whose words this client’s results are written in (#2867): coach (default) or amateur — Bowler Mode on bowling. Every report, PDF and new share link about the client starts from it; an activity with no amateur copy reads as coach either way.
coach- Coachamateur- Amateur
Standing height in centimetres (55-260). Required on write; reads back null on athletes migrated before the requirement. Drives proportional scaling of stride length, vertical oscillation, step width, and trunk sway green zones (legacy rows without it fall back to a 170 cm reference).
Body weight in kilograms (20-400). Required on write; reads back null on athletes migrated before the requirement — drives force / power / impulse metrics on jump and agility activities (legacy rows without it fall back to 75 kg, which can introduce 20-30% error on those readings).
Optional. Leg length in centimetres (35-150). Used only by specific running analyzers when present; safe to leave null.
Free-text shoe size (e.g. 42 EU, 9 US). Coach reference only.
right- Rightleft- Leftambidextrous- Ambidextrous
right / left / ambidextrous / empty. Durable client-profile attribute; protocol reports (Tennis Serve, Running Gait) key off the dominant side. Distinct from the per-session handedness mapping.
right- Rightleft- Leftambidextrous- Ambidextrous
quarterback- Quarterbackrunning_back- Running Backwide_receiver- Wide Receivertight_end- Tight Endoffensive_line- Offensive Linedefensive_line- Defensive Linelinebacker- Linebackerdefensive_back- Defensive Back
Playing-position group for team-sport athletes: quarterback / running_back / wide_receiver / tight_end / offensive_line / defensive_line / linebacker / defensive_back / empty string. The DB Injury-Risk Battery uses it for its Defensive-Back eligibility check (warns on a non-DB athlete, never blocks). Leave blank if unknown / not a team-sport athlete.
quarterback- Quarterbackrunning_back- Running Backwide_receiver- Wide Receivertight_end- Tight Endoffensive_line- Offensive Linedefensive_line- Defensive Linelinebacker- Linebackerdefensive_back- Defensive Back
List of prior / ongoing injury entries. Each item: { id, bodyPartId, label, side?, date?, severity?, status?, resolvedDate?, note? } (mirrors the InjuryHistoryEditor item shape; date is yyyy-mm-dd or the sentinel ongoing; status is active / monitoring / resolved; resolvedDate is yyyy-mm-dd, not before date). Stored as written so a Solutions InjuryHistoryBlock renders it with no transform. Send each entry’s id back. An entry without one is matched to the stored entry with the same bodyPartId, side and date and keeps its id, so its follow-up notes and documents stay with it; when that is ambiguous and one of the candidates has notes or documents, the write is a 400 ambiguous_injury_entry. A duplicate id is a 400. Dropping an entry never deletes its notes or documents.
Reusable Workspace Wellness workstation setup — { posture, measurements_cm: { desktop_height, seat_top_height?, seat_pan_depth?, back_rest_height?, eye_level_height, screen_top_height } } (mirrors WorkstationSetupSerializer; seat fields required only when posture is seated). The ergonomics survey step pre-fills from this so a returning subject doesn’t re-measure their desk; each session snapshots its own point-in-time copy. Empty object {} means unset. See #303 / #530.
Coach’s free-text notes on the athlete — the general-purpose field that replaced the narrow sport_type / nature_of_work questions (#926). Bounded to 2000 chars.
Optional precondition on an update (#2693): the updated_at the caller read. When the athlete has changed since, the write is refused with 409 client_changed and nothing is stored, so a stale edit cannot drop an injury entry a colleague has just added. Left out, the write behaves as it always has. Ignored on create.
Examples
All optional fields populated (best metric accuracy)
{ "display_name": "Anna Petrova", "email": "anna@example.com", "date_of_birth": "1996-03-02", "gender": "female", "height_cm": 168, "weight_kg": 58, "shoe_size": "38 EU", "external_uid": "club-roster-id-2419"}Name, date of birth, height and weight
{ "display_name": "Pavel Sidorov", "date_of_birth": "1991-11-23", "height_cm": 182, "weight_kg": 78}Recording the consent unlocks unblurred analysis and share links
{ "display_name": "Mia Novak", "date_of_birth": "2012-06-14", "height_cm": 152, "weight_kg": 41, "guardian_consent": { "guardian_name": "Eva Novak", "relationship": "mother", "method": "signed_form", "given_on": "2026-10-18", "notes": "Paper form kept at the club office." }}object
Athlete’s display name as the coach refers to them. Required.
male / female / nonbinary / empty string — case and surrounding whitespace are normalised, anything else is a 400. Drives gender-specific green-zone overrides on running cadence, stride length, jump power, and other normative metrics. Leave blank if unknown — you’ll get the all-genders defaults.
ISO date (YYYY-MM-DD). Required on write; reads back null on athletes migrated before the requirement. Drives age-band-aware green zones — a masters runner and a junior are judged against age-appropriate bands. Stored as the date, not the age, so it never goes stale.
Declare the athlete under 18 when the date of birth doesn’t show it, or there is none (#2751). Default false. Does not change age_band, which stays from the date of birth.
The parent’s or guardian’s consent in force, or null. Write an object to record one (on create or update), null to withdraw it. Without one, a minor’s analyses are always face-blurred and no public link to their data is minted or served (#2751).
object
The parent or guardian who gave consent.
How they are related to the athlete, e.g. mother, legal guardian.
How consent was given: signed_form, email, in_person or other.
signed_form- Signed formemail- Emailin_person- In personother- Other
The day the guardian consented (YYYY-MM-DD).
Anything else worth keeping, e.g. where the signed form is filed.
beginner- Beginnerintermediate- Intermediateadvanced- Advanced
Training experience: beginner / intermediate / advanced / empty string. Drives skill-level-specific green-zone overrides — a beginner and an advanced athlete are judged against different bands. Coach-set; leave blank for all-levels defaults.
beginner- Beginnerintermediate- Intermediateadvanced- Advanced
Whose words this client’s results are written in (#2867): coach (default) or amateur — Bowler Mode on bowling. Every report, PDF and new share link about the client starts from it; an activity with no amateur copy reads as coach either way.
coach- Coachamateur- Amateur
Standing height in centimetres (55-260). Required on write; reads back null on athletes migrated before the requirement. Drives proportional scaling of stride length, vertical oscillation, step width, and trunk sway green zones (legacy rows without it fall back to a 170 cm reference).
Body weight in kilograms (20-400). Required on write; reads back null on athletes migrated before the requirement — drives force / power / impulse metrics on jump and agility activities (legacy rows without it fall back to 75 kg, which can introduce 20-30% error on those readings).
Optional. Leg length in centimetres (35-150). Used only by specific running analyzers when present; safe to leave null.
Free-text shoe size (e.g. 42 EU, 9 US). Coach reference only.
right- Rightleft- Leftambidextrous- Ambidextrous
right / left / ambidextrous / empty. Durable client-profile attribute; protocol reports (Tennis Serve, Running Gait) key off the dominant side. Distinct from the per-session handedness mapping.
right- Rightleft- Leftambidextrous- Ambidextrous
quarterback- Quarterbackrunning_back- Running Backwide_receiver- Wide Receivertight_end- Tight Endoffensive_line- Offensive Linedefensive_line- Defensive Linelinebacker- Linebackerdefensive_back- Defensive Back
Playing-position group for team-sport athletes: quarterback / running_back / wide_receiver / tight_end / offensive_line / defensive_line / linebacker / defensive_back / empty string. The DB Injury-Risk Battery uses it for its Defensive-Back eligibility check (warns on a non-DB athlete, never blocks). Leave blank if unknown / not a team-sport athlete.
quarterback- Quarterbackrunning_back- Running Backwide_receiver- Wide Receivertight_end- Tight Endoffensive_line- Offensive Linedefensive_line- Defensive Linelinebacker- Linebackerdefensive_back- Defensive Back
List of prior / ongoing injury entries. Each item: { id, bodyPartId, label, side?, date?, severity?, status?, resolvedDate?, note? } (mirrors the InjuryHistoryEditor item shape; date is yyyy-mm-dd or the sentinel ongoing; status is active / monitoring / resolved; resolvedDate is yyyy-mm-dd, not before date). Stored as written so a Solutions InjuryHistoryBlock renders it with no transform. Send each entry’s id back. An entry without one is matched to the stored entry with the same bodyPartId, side and date and keeps its id, so its follow-up notes and documents stay with it; when that is ambiguous and one of the candidates has notes or documents, the write is a 400 ambiguous_injury_entry. A duplicate id is a 400. Dropping an entry never deletes its notes or documents.
Reusable Workspace Wellness workstation setup — { posture, measurements_cm: { desktop_height, seat_top_height?, seat_pan_depth?, back_rest_height?, eye_level_height, screen_top_height } } (mirrors WorkstationSetupSerializer; seat fields required only when posture is seated). The ergonomics survey step pre-fills from this so a returning subject doesn’t re-measure their desk; each session snapshots its own point-in-time copy. Empty object {} means unset. See #303 / #530.
Coach’s free-text notes on the athlete — the general-purpose field that replaced the narrow sport_type / nature_of_work questions (#926). Bounded to 2000 chars.
Optional precondition on an update (#2693): the updated_at the caller read. When the athlete has changed since, the write is refused with 409 client_changed and nothing is stored, so a stale edit cannot drop an injury entry a colleague has just added. Left out, the write behaves as it always has. Ignored on create.
object
Athlete’s display name as the coach refers to them. Required.
male / female / nonbinary / empty string — case and surrounding whitespace are normalised, anything else is a 400. Drives gender-specific green-zone overrides on running cadence, stride length, jump power, and other normative metrics. Leave blank if unknown — you’ll get the all-genders defaults.
ISO date (YYYY-MM-DD). Required on write; reads back null on athletes migrated before the requirement. Drives age-band-aware green zones — a masters runner and a junior are judged against age-appropriate bands. Stored as the date, not the age, so it never goes stale.
Declare the athlete under 18 when the date of birth doesn’t show it, or there is none (#2751). Default false. Does not change age_band, which stays from the date of birth.
The parent’s or guardian’s consent in force, or null. Write an object to record one (on create or update), null to withdraw it. Without one, a minor’s analyses are always face-blurred and no public link to their data is minted or served (#2751).
object
The parent or guardian who gave consent.
How they are related to the athlete, e.g. mother, legal guardian.
How consent was given: signed_form, email, in_person or other.
signed_form- Signed formemail- Emailin_person- In personother- Other
The day the guardian consented (YYYY-MM-DD).
Anything else worth keeping, e.g. where the signed form is filed.
beginner- Beginnerintermediate- Intermediateadvanced- Advanced
Training experience: beginner / intermediate / advanced / empty string. Drives skill-level-specific green-zone overrides — a beginner and an advanced athlete are judged against different bands. Coach-set; leave blank for all-levels defaults.
beginner- Beginnerintermediate- Intermediateadvanced- Advanced
Whose words this client’s results are written in (#2867): coach (default) or amateur — Bowler Mode on bowling. Every report, PDF and new share link about the client starts from it; an activity with no amateur copy reads as coach either way.
coach- Coachamateur- Amateur
Standing height in centimetres (55-260). Required on write; reads back null on athletes migrated before the requirement. Drives proportional scaling of stride length, vertical oscillation, step width, and trunk sway green zones (legacy rows without it fall back to a 170 cm reference).
Body weight in kilograms (20-400). Required on write; reads back null on athletes migrated before the requirement — drives force / power / impulse metrics on jump and agility activities (legacy rows without it fall back to 75 kg, which can introduce 20-30% error on those readings).
Optional. Leg length in centimetres (35-150). Used only by specific running analyzers when present; safe to leave null.
Free-text shoe size (e.g. 42 EU, 9 US). Coach reference only.
right- Rightleft- Leftambidextrous- Ambidextrous
right / left / ambidextrous / empty. Durable client-profile attribute; protocol reports (Tennis Serve, Running Gait) key off the dominant side. Distinct from the per-session handedness mapping.
right- Rightleft- Leftambidextrous- Ambidextrous
quarterback- Quarterbackrunning_back- Running Backwide_receiver- Wide Receivertight_end- Tight Endoffensive_line- Offensive Linedefensive_line- Defensive Linelinebacker- Linebackerdefensive_back- Defensive Back
Playing-position group for team-sport athletes: quarterback / running_back / wide_receiver / tight_end / offensive_line / defensive_line / linebacker / defensive_back / empty string. The DB Injury-Risk Battery uses it for its Defensive-Back eligibility check (warns on a non-DB athlete, never blocks). Leave blank if unknown / not a team-sport athlete.
quarterback- Quarterbackrunning_back- Running Backwide_receiver- Wide Receivertight_end- Tight Endoffensive_line- Offensive Linedefensive_line- Defensive Linelinebacker- Linebackerdefensive_back- Defensive Back
List of prior / ongoing injury entries. Each item: { id, bodyPartId, label, side?, date?, severity?, status?, resolvedDate?, note? } (mirrors the InjuryHistoryEditor item shape; date is yyyy-mm-dd or the sentinel ongoing; status is active / monitoring / resolved; resolvedDate is yyyy-mm-dd, not before date). Stored as written so a Solutions InjuryHistoryBlock renders it with no transform. Send each entry’s id back. An entry without one is matched to the stored entry with the same bodyPartId, side and date and keeps its id, so its follow-up notes and documents stay with it; when that is ambiguous and one of the candidates has notes or documents, the write is a 400 ambiguous_injury_entry. A duplicate id is a 400. Dropping an entry never deletes its notes or documents.
Reusable Workspace Wellness workstation setup — { posture, measurements_cm: { desktop_height, seat_top_height?, seat_pan_depth?, back_rest_height?, eye_level_height, screen_top_height } } (mirrors WorkstationSetupSerializer; seat fields required only when posture is seated). The ergonomics survey step pre-fills from this so a returning subject doesn’t re-measure their desk; each session snapshots its own point-in-time copy. Empty object {} means unset. See #303 / #530.
Coach’s free-text notes on the athlete — the general-purpose field that replaced the narrow sport_type / nature_of_work questions (#926). Bounded to 2000 chars.
Optional precondition on an update (#2693): the updated_at the caller read. When the athlete has changed since, the write is refused with 409 client_changed and nothing is stored, so a stale edit cannot drop an injury entry a colleague has just added. Left out, the write behaves as it always has. Ignored on create.
Responses
Section titled “Responses”object
Server-assigned UUID for the athlete.
Athlete’s display name as the coach refers to them. Required.
male / female / nonbinary / empty string — case and surrounding whitespace are normalised, anything else is a 400. Drives gender-specific green-zone overrides on running cadence, stride length, jump power, and other normative metrics. Leave blank if unknown — you’ll get the all-genders defaults.
ISO date (YYYY-MM-DD). Required on write; reads back null on athletes migrated before the requirement. Drives age-band-aware green zones — a masters runner and a junior are judged against age-appropriate bands. Stored as the date, not the age, so it never goes stale.
Whole-years chronological age, computed live from date_of_birth (never stored). null when no DOB is set. Drives age-band-aware metric green zones.
Demographic age band derived live from age — under_18 / 18_34 / 35_49 / 50_64, then five-year bands above 65 (65_69 / 70_74 / 75_79 / 80_84 / 85_plus), or "" when no DOB is set. The same slice the green-zone resolver and the threshold configurator use; band cutoffs are server-owned so clients never re-derive them (#1406).
Declare the athlete under 18 when the date of birth doesn’t show it, or there is none (#2751). Default false. Does not change age_band, which stays from the date of birth.
Under 18 by date_of_birth, or declared so with minor_declared (#2751). India’s DPDP Act counts anyone under 18 as a child.
The parent’s or guardian’s consent in force, or null. Write an object to record one (on create or update), null to withdraw it. Without one, a minor’s analyses are always face-blurred and no public link to their data is minted or served (#2751).
object
The parent or guardian who gave consent.
How they are related to the athlete, e.g. mother, legal guardian.
How consent was given: signed_form, email, in_person or other.
signed_form- Signed formemail- Emailin_person- In personother- Other
The day the guardian consented (YYYY-MM-DD).
Anything else worth keeping, e.g. where the signed form is filed.
When it was recorded in the app.
Who recorded it (a coach’s name, or API key).
True for a minor with no guardian’s consent in force: blur is forced on every analysis and sharing answers 403 guardian_consent_required.
beginner- Beginnerintermediate- Intermediateadvanced- Advanced
Training experience: beginner / intermediate / advanced / empty string. Drives skill-level-specific green-zone overrides — a beginner and an advanced athlete are judged against different bands. Coach-set; leave blank for all-levels defaults.
beginner- Beginnerintermediate- Intermediateadvanced- Advanced
Whose words this client’s results are written in (#2867): coach (default) or amateur — Bowler Mode on bowling. Every report, PDF and new share link about the client starts from it; an activity with no amateur copy reads as coach either way.
coach- Coachamateur- Amateur
Standing height in centimetres (55-260). Required on write; reads back null on athletes migrated before the requirement. Drives proportional scaling of stride length, vertical oscillation, step width, and trunk sway green zones (legacy rows without it fall back to a 170 cm reference).
Body weight in kilograms (20-400). Required on write; reads back null on athletes migrated before the requirement — drives force / power / impulse metrics on jump and agility activities (legacy rows without it fall back to 75 kg, which can introduce 20-30% error on those readings).
Optional. Leg length in centimetres (35-150). Used only by specific running analyzers when present; safe to leave null.
Free-text shoe size (e.g. 42 EU, 9 US). Coach reference only.
right- Rightleft- Leftambidextrous- Ambidextrous
right / left / ambidextrous / empty. Durable client-profile attribute; protocol reports (Tennis Serve, Running Gait) key off the dominant side. Distinct from the per-session handedness mapping.
right- Rightleft- Leftambidextrous- Ambidextrous
quarterback- Quarterbackrunning_back- Running Backwide_receiver- Wide Receivertight_end- Tight Endoffensive_line- Offensive Linedefensive_line- Defensive Linelinebacker- Linebackerdefensive_back- Defensive Back
Playing-position group for team-sport athletes: quarterback / running_back / wide_receiver / tight_end / offensive_line / defensive_line / linebacker / defensive_back / empty string. The DB Injury-Risk Battery uses it for its Defensive-Back eligibility check (warns on a non-DB athlete, never blocks). Leave blank if unknown / not a team-sport athlete.
quarterback- Quarterbackrunning_back- Running Backwide_receiver- Wide Receivertight_end- Tight Endoffensive_line- Offensive Linedefensive_line- Defensive Linelinebacker- Linebackerdefensive_back- Defensive Back
List of prior / ongoing injury entries. Each item: { id, bodyPartId, label, side?, date?, severity?, status?, resolvedDate?, note? } (mirrors the InjuryHistoryEditor item shape; date is yyyy-mm-dd or the sentinel ongoing; status is active / monitoring / resolved; resolvedDate is yyyy-mm-dd, not before date). Stored as written so a Solutions InjuryHistoryBlock renders it with no transform. Send each entry’s id back. An entry without one is matched to the stored entry with the same bodyPartId, side and date and keeps its id, so its follow-up notes and documents stay with it; when that is ambiguous and one of the candidates has notes or documents, the write is a 400 ambiguous_injury_entry. A duplicate id is a 400. Dropping an entry never deletes its notes or documents.
Reusable Workspace Wellness workstation setup — { posture, measurements_cm: { desktop_height, seat_top_height?, seat_pan_depth?, back_rest_height?, eye_level_height, screen_top_height } } (mirrors WorkstationSetupSerializer; seat fields required only when posture is seated). The ergonomics survey step pre-fills from this so a returning subject doesn’t re-measure their desk; each session snapshots its own point-in-time copy. Empty object {} means unset. See #303 / #530.
Coach’s free-text notes on the athlete — the general-purpose field that replaced the narrow sport_type / nature_of_work questions (#926). Bounded to 2000 chars.
Your own stable id for this athlete in your external system (athlete-management software, club ID, etc.). We never interpret this; useful for cross-system joins. Read-only here — set at create time only.
Number of sessions tied to this client. Read-only; computed live. The web’s delete-confirm dialog uses this to spell out how much history a delete will wipe.
Number of this client’s COMPLETED sessions — the ones an analysis exists for. Read-only; computed live. The athlete pickers show this count; session_count counts every status.
UUIDs of the ClientGroups this athlete belongs to (#545) — many-to-many, so an athlete can be in several. Drives the group chips + filter on the Clients page; mutate via the /api/client-groups/<id>/members/ endpoints, never here.
ISO-8601 UTC timestamp of client creation.
ISO-8601 UTC timestamp of the last mutation.
Examples
{ "id": "f6d52e64-3aa3-4a26-bac2-0fc9d718448d", "display_name": "Anna Petrova", "email": "anna@example.com", "gender": "female", "height_cm": 168, "weight_kg": 58, "leg_length_cm": null, "shoe_size": "38 EU", "avatar_url": "https://s3.ai.aikynetix.app/aikynetix-media/teams/…/clients/…/avatar-a1b2c3d4.jpg?X-Amz-Expires=604800&X-Amz-Signature=…", "external_uid": "club-roster-id-2419", "session_count": 0, "minor_declared": false, "is_minor": false, "guardian_consent": null, "guardian_consent_required": false, "created_at": "2026-04-29T10:00:00Z", "updated_at": "2026-04-29T10:00:00Z"}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).
Examples
400 — Validation error
{ "display_name": [ "This field is required." ]}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
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's `max_client_profiles=1` is already filled
{ "detail": "client_limit_reached", "reset_at": "2026-06-01T00:00:00Z", "current_plan": "free", "current_role": "free"}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"}