Collectives & Authorization API
Last updated: 2026-05-26
Overview
The Collectives system enables group management for families, classes, and other trusted groups. Parents (or other adult guardians) create a Collective, invite teenagers via shareable links, manage members, and grant/revoke parental consent — all through a REST API.
This system is tightly integrated with the consent pipeline (see Onboarding) and the authorization flow (see Client Development).
Data Model
┌──────────────────────┐ ┌──────────────────────────────┐
│ Collective │ │ CollectiveMembership │
│──────────────────────│ │──────────────────────────────│
│ id: UUID (PK) │◄────│ collective_id: UUID (FK) │
│ name: str │ │ user_id: UUID (FK) │
│ type: str │ │ role: CollectiveRoles │
│ parental_consent_ │ │ joined_at: datetime │
│ granted: bool │ └──────────────┬───────────────┘
│ created_by: UUID (FK) │ │
└──────────┬────────────┘ │
│ │
│ ┌──────────────────────────────┐│
│ │ User ││
│ │ (teenager or parent) │◄┘
│ └──────────────────────────────┘
│
│ ┌──────────────────────────────┐
│ │ Invitation │
│ │──────────────────────────────│
│ │ id: UUID (PK) │
│ │ collective_id: UUID (FK) │
│ │ inviter_id: UUID (FK) │
├──│ age_class: AgeClass │
│ │ language: str │
│ │ label: str (optional) │
│ │ usage_limit: int (nullable) │
│ │ current_uses: int │
│ │ status: InvitationStatus │
│ │ created_at / expires_at │
│ │ accepted_at / accepted_by │
│ └──────────────────────────────┘
│
│ ┌───────────────────────────────┐
│ │ ParentalAuthorizationRequest │
│ │───────────────────────────────│
│ │ id: UUID (PK) │
│ │ teenager_id: UUID (FK) │
│ │ status: AuthorizationReqStatus │
│ │ created_at / expires_at │
│ └───────────────────────────────┘
│
│ ┌───────────────────────────────┐
│ │ ParentalConsent │
│ │───────────────────────────────│
│ │ parent_id: UUID (FK, PK) │
│ │ teenager_id: UUID (FK, PK) │
│ │ created_at │
│ └───────────────────────────────┘
Enums
| Enum | Values |
|---|---|
CollectiveRoles |
owner, admin, member |
InvitationStatus |
pending, active, accepted, expired, revoked |
AuthorizationRequestStatus |
pending, fulfilled, expired |
AgeClass |
child <11, preteen 11-14, teenager 15-17, adult |
Role Hierarchy
- Owner: Full control — create invitations, manage members, grant parental consent, view all data
- Admin: Same as owner (roles are currently treated equivalently in access checks)
- Member: Read-only — can accept invitations and be managed by owners/admins
Endpoints
All endpoints are prefixed with /api/v1/collectives.
List Collectives
GET /collectives
Authorization: Bearer <token>
Returns all collectives where the authenticated user is an owner or admin.
Response:
{
"collectives": [
{
"id": "uuid",
"name": "Smith Family",
"type": "family",
"parental_consent_granted": false
}
]
}List Members
GET /collectives/{collective_id}/members
Authorization: Bearer <token>
Returns all members of a collective with their consent status. Requires owner/admin access.
Response:
{
"members": [
{
"id": "uuid",
"name": "User a1b2c3d4",
"role": "member",
"status": "pending_consent",
"needs_parental_consent": true
}
]
}Member status values:
| Status | Meaning |
|---|---|
active |
User has consented to TOS and has parental consent (if needed) |
pending_tos |
User has not yet accepted the Terms of Service |
pending_consent |
User needs parental consent but it has not been granted |
Create Invitation
POST /collectives/{collective_id}/invitations
Authorization: Bearer <token>
Content-Type: application/json
{
"age_class": "teenager 15-17",
"language": "fr",
"label": "For Emma"
}
Creates an invitation link for a teenager. The age_class determines the consent requirements. Setting age_class to an age class that requires parental consent automatically sets usage_limit = 1.
Response:
{
"invitation_id": "uuid",
"invite_url": "https://<frontend>/join?invitation_id=<uuid>"
}The invite_url is a shareable link that the teenager uses to join the collective. The URL points to the teenager frontend (FRONTEND_TEENAGER_URL) with the invitation ID as a query parameter.
List Invitations
GET /collectives/{collective_id}/invitations
Authorization: Bearer <token>
Returns all active/pending invitations for a collective. Requires owner/admin access.
Response:
{
"invitations": [
{
"id": "uuid",
"age_class": "teenager 15-17",
"language": "fr",
"label": "For Emma",
"status": "pending",
"usage_limit": 1,
"current_uses": 0,
"created_at": "2026-05-26T12:00:00Z",
"invite_url": "https://<frontend>/join?invitation_id=<uuid>"
}
]
}Only invitations with status pending or active are returned. Expired and revoked invitations are filtered out.
Revoke Invitation
DELETE /collectives/{collective_id}/invitations/{invitation_id}
Authorization: Bearer <token>
Revokes an invitation, preventing further use. Requires owner/admin access.
Update Member Consent
PATCH /collectives/{collective_id}/members/{member_id}/consent
Authorization: Bearer <token>
Content-Type: application/json
{
"has_parental_consent": true
}
Grants or revokes parental consent for a specific member. Requires owner/admin access. The member must be in the collective and must have needs_parental_consent == true.
Set Parental Consent Flag
PATCH /collectives/{collective_id}/parental-consent
Authorization: Bearer <token>
Content-Type: application/json
{
"granted": true
}
Sets the parental_consent_granted flag on the collective itself. This is a collective-level toggle separate from per-member consent. Requires owner/admin access.
Invitation Lifecycle
Create Accept Join Collective
──────► PENDING ──────► ACCEPTED ──────► membership record
│
├──► ACTIVE (in use)
├──► EXPIRED (timeout)
└──► REVOKED (manual delete)
- Parent creates invitation —
POST /collectives/{id}/invitations. Status:pending. - Teenager opens link — Frontend reads
invitation_idfrom URL query param. Passed toPOST /auth/loginasinvitation_id. - Backend validates — Checks invitation status, usage limit, expiration. If valid, auto-grants consent (the invitation serves as implicit parental consent).
- Invitation consumed —
current_usesincremented. Ifcurrent_uses >= usage_limit, status changes toaccepted. - Parent can revoke —
DELETE /collectives/{id}/invitations/{inv_id}changes status torevoked.
Usage Limits
usage_limit = null: Unlimited uses (invitation can be reused)usage_limit = 1: One-time use (auto-set whenage_classrequires parental consent)current_uses: Tracks how many times the invitation has been accepted
Integration with Consent Pipeline
The collectives system integrates with the consent pipeline at multiple points:
| Integration Point | Behavior |
|---|---|
| Invitation acceptance | Auto-grants parental consent (the invitation = implicit consent from the parent who created it) |
| Member consent status | list_collective_members returns per-member consent status (pending_tos, pending_consent, active) |
| Parental consent grant | PATCH /members/{id}/consent creates/removes ParentalConsent records |
| Authorization requests | Creates a ParentalAuthorizationRequest voucher, separate from the invitation system |
Session Scope Interaction
When a user joins via invitation: - has_consented = true and invitation_id are sent in POST /auth/login - The backend validates the invitation, auto-grants consent, and sets session scope to full - The needs_parental_consent flag on the User model determines whether the user requires explicit parental consent in the future
Schemas
Request Schemas
| Schema | Fields |
|---|---|
InvitationCreateRequest |
age_class: AgeClass?, language: str, label: str? |
MemberConsentUpdateRequest |
has_parental_consent: bool |
AuthorizeTeenagerRequest |
teenager_id: UUID |
ParentalConsentGrantedRequest |
granted: bool |
Response Schemas
| Schema | Fields |
|---|---|
CollectiveListResponse |
collectives: List[CollectiveRecord] |
CollectiveRecord |
id: UUID, name: str, type: str, parental_consent_granted: bool |
CollectiveMemberResponse |
members: List[MemberRecord] |
MemberRecord |
id: UUID, name: str, role: CollectiveRoles, status: str, needs_parental_consent: bool |
InvitationResponse |
invitation_id: UUID, invite_url: str |
CollectiveInvitationListResponse |
invitations: List[CollectiveInvitationRecord] |
CollectiveInvitationRecord |
id: UUID, age_class: AgeClass?, language: str, label: str?, status: str, usage_limit: int?, current_uses: int, created_at: datetime, invite_url: str |