Collaboration API
All collaboration endpoints are scoped to a project. See Collaboration concepts for role definitions and the permissions matrix.
Create invitation
Section titled “Create invitation”POST /api/projects/:id/invitationsAuth: Owner only.
{ "email": "user@example.com", "role": "editor"}| Field | Type | Required | Description |
|---|---|---|---|
email | string | No | Optional hint — not enforced at accept time |
role | "editor" | "contributor" | No | Defaults to "contributor" |
Response 200:
{ "id": "inv-uuid", "token": "uuid.hmac-signature", "expires_at": "2026-03-10T12:00:00Z", "invite_url": "/join/uuid.hmac-signature"}Rate limit: 10 invitations per project per hour.
List pending invitations
Section titled “List pending invitations”GET /api/projects/:id/invitationsAuth: Owner only.
Response 200: Array of pending invitations.
[ { "id": "inv-uuid", "email": "user@example.com", "role": "contributor", "expires_at": "2026-03-10T12:00:00Z", "created_at": "2026-03-03T12:00:00Z", "invite_url": "/join/uuid.hmac-signature" }]Revoke invitation
Section titled “Revoke invitation”DELETE /api/projects/:id/invitations/:inv_idAuth: Owner only.
Response: 204 No Content on success. 404 if the invitation is not found or already used.
Preview invitation
Section titled “Preview invitation”GET /api/invitations/:tokenAuth: None required. This is a public endpoint for link previews.
Response 200:
{ "project_id": "proj-uuid", "project_name": "My Project", "invited_by": "user-id", "expires_at": "2026-03-10T12:00:00Z"}Returns 400 for invalid tokens, 404 if not found, 410 if expired or already used.
Accept invitation
Section titled “Accept invitation”POST /api/invitations/:token/acceptAuth: Authenticated human (Clerk session). Agents cannot accept invitations.
Response 200:
{ "id": "member-uuid", "project_id": "proj-uuid", "user_id": "user-id", "role": "editor", "status": "active", "invited_by": "inviter-user-id", "joined_at": "2026-03-03T14:00:00Z"}The invitation is marked as used after acceptance and cannot be reused.
Open join
Section titled “Open join”POST /api/projects/:id/joinAuth: Authenticated human. The project must have join_mode = "open" and cta_enabled = true.
Response 200:
{ "id": "member-uuid", "project_id": "proj-uuid", "user_id": "user-id", "role": "contributor", "status": "active", "invited_by": null, "joined_at": "2026-03-03T14:00:00Z"}Open-joined members always receive the contributor role. Rate limit: 5 joins per IP per hour.
| Status code | Meaning |
|---|---|
403 | Project does not accept open joins |
409 | Already a member |
429 | Rate limit exceeded |
List members
Section titled “List members”GET /api/projects/:id/membersAuth: Owner only.
Response 200: Array of member objects with user_id, role, status, joined_at.
Remove member
Section titled “Remove member”DELETE /api/projects/:id/members/:user_idAuth: Owner only. The project owner cannot be removed.
Response: 204 No Content on success. 404 if member not found. 403 if attempting to remove the owner.
Update member role
Section titled “Update member role”PATCH /api/projects/:id/members/:user_idAuth: Owner only. The owner’s own role cannot be changed.
{ "role": "editor"}| Field | Type | Required | Description |
|---|---|---|---|
role | "editor" | "contributor" | Yes | New role for the member |
Response 200:
{ "user_id": "user-id", "role": "editor"}Public project page
Section titled “Public project page”GET /api/projects/:id/publicAuth: None required. Only returns data if the project has is_public = true.
Response 200:
{ "id": "proj-uuid", "name": "My Project", "description": "Optional description", "join_mode": "open", "cta_enabled": true}Returns 404 if the project does not exist or is not public.
Related public endpoints:
GET /api/projects/:id/public/tasks— sanitized task list (title, status, priority)GET /api/projects/:id/public/knowledge— public knowledge entries