Skip to content

Resend an invite

POST
/api/clients/{id}/access/{access_id}/resend/
curl --request POST \
--url http://localhost:8000/api/clients/2489E9AD-2EE2-8E00-8EC9-32D5F69181C0/access/2489E9AD-2EE2-8E00-8EC9-32D5F69181C0/resend/ \
--header 'Authorization: Bearer <token>'

A fresh link for an invite that is pending or expired, valid 14 days; the previous link stops working. A second resend within a minute of the last e-mail sends nothing and answers the grant as it is. The invite rules are checked again (400 minor_athlete_invite / date_of_birth_required, 409 already_on_team). 409 access_active for an accepted grant (under local auth it mails the account a sign-in link instead — 409 email_changed when the account’s address is no longer the invited one), access_revoked for a revoked one. Rate-limited per coach, and to three e-mails per address a day on a card across coaches (429, invite_rate_limited).

A coach of the team, on a card of the active team. Every route answers 404 while client access is switched off on the environment. A partner API key and a client’s own account get 403. 402 trial_expired / access_ended on a read-only team (its trial or plan ended), else feature_not_in_plan (feature: client_access) on a plan without client access — every paid tier and Strategic include it; a trial does not.

access_id
required
string format: uuid
id
required
string format: uuid
Media typeapplication/json

One person with access to a card, as the coach sees it (ClientAccessRecord).

object
id
required
string format: uuid
email
required

The address the invite went to.

string format: email
display_name
required

The person’s name once they accepted; empty until then.

string
relation
required
string
Allowed values: athlete guardian
level
required
string
Allowed values: view contribute
status
required

invited · active · expired · revoked. expired is an invite whose 14-day link lapsed; the coach can resend it.

  • invited - Invited
  • active - Active
  • expired - Expired
  • revoked - Revoked
string
Allowed values: invited active expired revoked
language
required

The invite’s language. Later e-mails to this person use it too.

  • en - English
  • ru - Русский
  • es - Español
string
Allowed values: en ru es
invited_by_name
required

Who sent the invite, for the team’s coaches: the inviting coach’s name, else their e-mail address; once their account is gone, the name it left, else “A coach” in ?lang (English by default).

string
invited_at
required
string format: date-time
expires_at
required

When the invite link lapses — set only while the status is invited.

string format: date-time
nullable
accepted_at
required
string format: date-time
nullable
revoked_at
required
string format: date-time
nullable
last_sent_at
required

When the last invite or sign-in e-mail left; null when none ever did.

string format: date-time
nullable
email_sent
required

Whether the e-mail reached the person (false: the transport refused it — the grant stands, resend it). On an invite or resend answer: this call’s e-mail, null when the call sent nothing on purpose (a second resend within a minute). On every other answer: the latest attempt’s, null when none was ever made.

boolean
nullable

Examples

ExampleInvited
{
"id": "6f1c2a7e-0d4b-4e8a-9c3f-2b5d7e9a1c40",
"email": "anna@example.com",
"display_name": "",
"relation": "athlete",
"level": "view",
"status": "invited",
"language": "en",
"invited_by_name": "Kim Larsen",
"invited_at": "2026-10-06T09:00:00Z",
"expires_at": "2026-10-20T09:00:00Z",
"accepted_at": null,
"revoked_at": null,
"last_sent_at": "2026-10-06T09:00:00Z",
"email_sent": true
}
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

Example400—AthleteInviteOnAMinor'sCard

400 — athlete invite on a minor's card

Minors never hold an account: invite a parent or guardian instead.

{
"detail": "minor_athlete_invite"
}
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 team is read-only, or its plan does not include client access.

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—ClientAccessNotInPlan

A Free team opens the App access panel

{
"detail": "feature_not_in_plan",
"reset_at": null,
"current_plan": "free",
"current_role": "free",
"feature": "client_access"
}
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—AClient'sOwnAccount

403 — a client's own account

The coach side is for a coach of the team; a client uses the portal.

{
"detail": "client_account"
}
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—NoSuchGrantOnTheCard

404 — no such grant on the card

{
"detail": "access_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 — the invite was already accepted

An accepted grant has no invite left to resend.

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