Team and invitations API
Workspace membership is how sharing actually works in OpenDocs — if a
post is workspace visible, every active member of that workspace can
read it. These endpoints cover everything about team members and
invitations.
Every endpoint on this page requires a browser session cookie. API
keys return 401 unauthorized — teams are a dashboard surface. See
Authentication.
All routes fall under the general 120 req/min rate bucket.
Roles
Every member has one of three roles:
| Role | What it grants |
|---|---|
owner | Every permission, plus the right to transfer ownership. One per workspace. |
admin | Full admin: rename workspace, change slug, manage branding, invite / remove / re-role members. Can't remove the owner. |
member | Read/write posts. Cannot change workspace settings or manage members. |
The user who creates the workspace becomes its owner. Admins and members are added through the invitation flow below.
Member caps
Member counts are capped per plan:
| Plan | Members per workspace |
|---|---|
| Free | 3 |
| Pro | 5 |
| Team | unlimited |
Hitting the cap returns 403 member_limit from POST /workspace/team/invite. See
Plans and limits for the full
breakdown.
GET /api/v1/workspace/team
List the caller's workspace members.
Response
{
"members": [
{
"id": "mem_1",
"userId": "usr_1",
"email": "ada@acme.com",
"fullName": "Ada Lovelace",
"role": "owner",
"joinedAt": "2026-01-10T12:00:00Z"
},
{
"id": "mem_2",
"userId": "usr_2",
"email": "grace@acme.com",
"fullName": "Grace Hopper",
"role": "admin",
"joinedAt": "2026-02-14T09:30:00Z"
}
]
}
Returns 400 no_workspace if the caller hasn't completed onboarding.
GET /api/v1/workspace/team/stats
Compact numeric summary used by the dashboard badge.
{
"total": 3,
"pendingInvitations": 1,
"limit": 5,
"remaining": 2
}
limit and remaining are null on the Team plan (unlimited).
GET /api/v1/workspace/team/invitations
List pending invitations. Owner/admin only — members get 403 forbidden.
{
"invitations": [
{
"id": "inv_1",
"email": "mike@acme.com",
"role": "member",
"status": "pending",
"sentAt": "2026-04-01T10:15:00Z",
"expiresAt": "2026-04-08T10:15:00Z"
}
]
}
POST /api/v1/workspace/team/invite
Send an invitation. Owner/admin only.
Request body
{
"email": "mike@acme.com",
"role": "member",
"workspaceId": "ws_123"
}
| Field | Required | Notes |
|---|---|---|
email | yes | Valid email, ≤ 320 chars. |
role | yes | "admin" or "member". You can't invite another owner. |
workspaceId | no | Only needed if the caller belongs to multiple workspaces. Defaults to the current workspace. |
Success response
Status 201.
{
"invitation": {
"id": "inv_1",
"email": "mike@acme.com",
"role": "member",
"status": "pending",
"expiresAt": "2026-04-24T10:15:00Z"
}
}
Errors
| Status | error | When |
|---|---|---|
403 | forbidden | Caller isn't owner/admin |
403 | member_limit | Workspace is at its per-plan member cap |
409 | already_member | Email already belongs to an active member |
409 | already_invited | A pending invitation for this email already exists |
400 | invite_limit | Pending-invitation backlog is too large |
POST /api/v1/workspace/team/invitations/:id/resend
Re-send the invitation email and refresh its expiry.
{ "resent": true, "expiresAt": "2026-04-24T10:15:00Z" }
Errors: 403 forbidden, 404 not_found.
DELETE /api/v1/workspace/team/invitations/:id
Cancel a pending invitation.
{ "cancelled": true }
Errors: 403 forbidden, 404 not_found.
POST /api/v1/workspace/team/accept-invite
Accept an invitation token. Called from the ?token=… landing page — not
typically used from automation.
Request body
{ "token": "inv_signed_token_abc123" }
Success response
{
"workspaceId": "ws_123",
"workspaceSlug": "acme",
"role": "member"
}
Errors
| Status | error | When |
|---|---|---|
404 | not_found | Token doesn't match any invitation |
410 | expired | Invitation has expired — ask the inviter to re-send |
409 | already_member | Caller is already a member of this workspace |
PATCH /api/v1/workspace/team/members/:id/role
Change a member's role. Owner/admin only.
Request body
{ "role": "admin" }
Role must be "admin" or "member". The owner's role can't be changed
through this endpoint — use the ownership-transfer flow in the dashboard.
Success response
{ "id": "mem_2", "role": "admin" }
Errors: 403 forbidden, 404 not_found.
DELETE /api/v1/workspace/team/members/:id
Remove a member. Owner/admin only. The owner cannot be removed.
{ "removed": true }
Errors: 403 forbidden, 404 not_found.
Invitation lifecycle
- send —
POST /workspace/team/invite— statuspending, email sent, token generated, 7-day expiry. - resend —
POST /workspace/team/invitations/:id/resend— re-send email, reset expiry. - cancel —
DELETE /workspace/team/invitations/:id— invitation is deleted; the token stops working. - accept —
POST /workspace/team/accept-invite— invitee becomes an active member, invitation row is marked consumed. - expire — if no accept within 7 days, the token starts returning
410 expired. Resending refreshes the expiry without issuing a new token URL.
Related pages
- Workspaces API — settings, branding, slug, domains
- Workspaces and team access — conceptual overview of how membership affects visibility
- Plans and limits — member caps per tier
- Errors and pagination — full error catalogue