Invites¶
Endpoints for resolving invite tokens and accepting or declining organization invitations. These endpoints support the invite-first onboarding flow.
Base path: /api/invites
Endpoints¶
| Method | Path | Auth | Description |
|---|---|---|---|
GET |
/api/invites/:token |
Public | Resolve an invite token |
POST |
/api/invites/accept |
Public | Create account and accept invite (legacy flow) |
GET |
/api/invites/my-pending |
Required | List caller's pending invites |
POST |
/api/invites/:id/accept |
Required | Accept a pending invite |
POST |
/api/invites/:id/decline |
Required | Decline a pending invite |
Onboarding Flows¶
New user (no existing account)¶
- User clicks invite link in email — token is embedded in the URL.
- Frontend calls
GET /api/invites/:tokento resolve invite metadata. - User fills in name and password.
- Frontend calls
POST /api/invites/acceptwith the token, credentials, and profile fields. - Account is created, org membership is assigned, and invite is marked
accepted.
Existing user¶
- User clicks invite link —
hasAccount: truein theGET /api/invites/:tokenresponse. - Frontend shows sign-in CTA.
- User signs in normally.
- After sign-in, if
GET /api/invites/my-pendingreturns pending invites, the app shows the pending invites screen. - User accepts or declines each invite manually.
Invite gate
When an authenticated user has pending invites, most protected endpoints return 403 until all invites are accepted or declined. Invite endpoints remain accessible so the user can complete onboarding.
GET /api/invites/:token¶
Resolve an invite token and return its metadata. Public — no authentication required.
Path parameters:
| Parameter | Type | Description |
|---|---|---|
token |
string | The raw invite token from the URL |
Response 200:
{
"data": {
"email": "newcoach@example.com",
"roleInOrganization": "Coach",
"organizationName": "Praxia Academy",
"status": "pending",
"expiresAt": "2025-04-15T12:00:00.000Z",
"isAvailable": true,
"hasAccount": false
}
}
Response fields:
| Field | Type | Description |
|---|---|---|
email |
string | Email address the invite was sent to |
roleInOrganization |
string | Role to be assigned on acceptance |
organizationName |
string | Name of the inviting organization |
status |
string | pending, accepted, revoked, or expired |
expiresAt |
string | ISO 8601 expiry timestamp |
isAvailable |
boolean | true only when status = 'pending' and not expired |
hasAccount |
boolean | Whether the invited email already has a user account |
Error responses:
| Code | Condition |
|---|---|
404 |
Token not found |
POST /api/invites/accept¶
Create a new user account and accept the invite in a single request. Use this for the new-user flow only — existing users should sign in first and then call POST /api/invites/:id/accept.
Authentication: Not required.
Request body:
{
"token": "<raw-invite-token>",
"firstName": "Jane",
"lastName": "Smith",
"password": "Secret1234!",
"email": "newcoach@example.com",
"preferredLanguage": "es"
}
| Field | Type | Required | Constraints |
|---|---|---|---|
token |
string | Yes | The raw token from the invite URL |
firstName |
string | Yes | 1–100 characters |
lastName |
string | Yes | 1–100 characters |
password |
string | Yes | Minimum 8 characters |
email |
string | No | If provided, must match the invite's email exactly |
preferredLanguage |
string | No | en or es (fallback: Accept-Language, then en) |
Response 200:
{
"data": {
"userId": "user-cuid",
"email": "newcoach@example.com",
"organizationId": "org-uuid",
"roleInOrganization": "Coach"
}
}
Error responses:
| Code | Condition |
|---|---|
400 |
Invalid token, expired invite, or email mismatch |
409 |
A user with this email already exists |
500 |
Account creation or org assignment failed |
GET /api/invites/my-pending¶
Returns all pending, non-expired invitations addressed to the current user's email.
Authentication: Required.
Response 200:
{
"data": [
{
"id": "invite-uuid",
"email": "jane@example.com",
"organizationId": "org-uuid",
"organizationName": "Praxia Academy",
"roleInOrganization": "Coach",
"status": "pending",
"expiresAt": "2025-04-15T12:00:00.000Z",
"createdAt": "2025-04-08T12:00:00.000Z"
}
]
}
POST /api/invites/:id/accept¶
Accept a specific pending invitation. The caller's email must match the invite's email.
Authentication: Required.
Path parameters:
| Parameter | Type | Description |
|---|---|---|
id |
string | Invite UUID |
Request body: None.
Response 200:
{
"data": {
"id": "invite-uuid",
"organizationId": "org-uuid",
"roleInOrganization": "Coach",
"status": "accepted"
}
}
Error responses:
| Code | Condition |
|---|---|
400 |
Invite is no longer in pending status, or has expired |
403 |
Caller's email does not match the invite's email |
404 |
Invite not found |
POST /api/invites/:id/decline¶
Decline a specific pending invitation. Sets the invite status to revoked.
Authentication: Required.
Path parameters:
| Parameter | Type | Description |
|---|---|---|
id |
string | Invite UUID |
Request body: None.
Response 200:
Error responses:
| Code | Condition |
|---|---|
400 |
Invite is no longer in pending status |
403 |
Caller's email does not match the invite's email |
404 |
Invite not found |