Skip to content

Accept a client invite

POST
/api/client-invites/accept/
curl --request POST \
--url http://localhost:8000/api/client-invites/accept/ \
--header 'Authorization: Bearer <token>' \
--header 'Content-Type: application/json' \
--data '{ "token": "Gu7Qk2n0bVw1sXyZ-4hRt9pLmA3cE6dF8jK0oPqS5uW", "consent_version": "2026-10-29-draft", "confirm_adult": true }'

Turns the invite into access to the athlete’s card for the signed-in account, and records the consent it gave (consent_version must be the one the preview names). The account’s e-mail must be the invited address (409 invite_email_mismatch) and verified by something other than the link — the e-mail code flow (/api/me/send-verification/, /api/me/verify-email/) or the sign-in’s own verified address (403 email_unverified): a forwarded link must not open the card for whoever signs up with the address. 409 consent_outdated for an old consent version, minor_athlete_invite / date_of_birth_required when the athlete can no longer be invited themselves, already_on_team for a coach of the card’s team, access_exists when the account already has the card. 400 unless confirm_adult is true (minors never hold an account; a guardian is an adult too). Answers the card, as the portal lists it. Creates no team. Rate-limited (429). Answers 404 while client access is switched off on the environment.

POST /api/client-invites/accept/ body (ClientInviteAcceptInput).

object
confirm_adult
required

The person confirms they are 18 or over (or the age of majority where they live). Must be true.

boolean
token
required
string
>= 1 characters <= 128 characters
consent_version
required

The version of the consent texts the person agreed to.

string
>= 1 characters <= 32 characters

Examples

ExampleAcceptWithTheCurrentConsent

Accept with the current consent

{
"token": "Gu7Qk2n0bVw1sXyZ-4hRt9pLmA3cE6dF8jK0oPqS5uW",
"consent_version": "2026-10-29-draft",
"confirm_adult": true
}
Media typeapplication/json

One card the person can open in the portal (PortalCard).

object
client_id
required
string format: uuid
client_name
required
string
avatar_url
required

The athlete’s photo, signed for two hours; empty when there is none.

string
team_name
required
string
team_logo_url
required

The team’s own logo for the portal header (S5), signed for two hours; null when the team set none — the deployment’s brand shows instead.

string
nullable
relation
required
  • athlete - Athlete
  • guardian - Parent or guardian
string
Allowed values: athlete guardian
level
required
  • view - View
  • contribute - Contribute
string
Allowed values: view contribute
unread_count
required

The person’s unread messages on the card’s conversation.

integer
active
required

Whether the card opens now. False: opening it answers the inactive_reason page. /api/portal/cards/ lists the active cards first. A revoked grant is not listed at all.

boolean
inactive_reason
required
One of:
string
Allowed values: team_plan minor consent_required

Examples

ExampleTheCardTheAcceptOpened

The card the accept opened

{
"client_id": "a1f1c0a2-0000-4000-8000-0000000000aa",
"client_name": "Sofia Lebedeva",
"avatar_url": "",
"team_name": "North Track Academy",
"team_logo_url": null,
"relation": "guardian",
"level": "view",
"unread_count": 0,
"active": true,
"inactive_reason": null
}
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

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

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

Examples

Example403—AddressNotVerified

403 — address not verified

Verify the address with the e-mail code first, then accept again.

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

Examples

Example404—NoSuchInvite

404 — no such invite

{
"detail": "invite_not_found"
}
Media typeapplication/json
object
detail
required

409 — the request conflicts with current state. detail is a stable code (e.g. reanalyze_in_flight, already_on_plan, email_in_use). Some 409s carry extra context fields alongside detail; those are documented on the specific endpoint that emits them.

string

Examples

409 — another address

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

Examples

410 — expired

The 14-day link lapsed: ask the coach to resend it.

{
"detail": "invite_expired"
}
Media typeapplication/json
object
detail
required

429 — throttled: this account’s (or address’s) rate for the route is used up; try again after wait seconds, which Retry-After repeats. A route with a limit of its own answers its own code instead, documented there.

string
wait

With throttled: whole seconds until the next request can pass.

integer
nullable

Example generated

{
"detail": "example",
"wait": 1
}