Workspaces and people
Workspaces, their members, groups and invitations, the capabilities of the calling account, and what the workspace has used of its plan.
29 operations. Every path is relative to the instance origin; every response body is JSON unless stated. The endpoint descriptions below come straight from the server’s own route table. See API for authentication, errors and pagination.
GET /api/v1/workspaces
List workspaces the caller is a member of.
listWorkspaces · token scope: session only
Query parameters
cursor· string — length 1–∞limit· integer — 1–100
Response 200 — application/json
items· object[] — requiredid· string (uuid) — requiredslug· string — required, length 1–100name· string — requiredicon· string | null — requiredrole·"owner"|"admin"|"member"|"guest"— requiredcreatedAt· string (date-time) — required
nextCursor· string | null — required
Response default — application/json
error· object — requiredcode·"bad_request"|"validation_failed"|"unauthorized"|"forbidden"|"not_found"|"conflict"|"rate_limited"|"internal"— requiredmessage· string — requiredreason· string — length 1–64
POST /api/v1/workspaces
Create a workspace; the caller becomes its owner, and it starts with one open collection.
createWorkspace · token scope: session only
Request body — application/json, required
name· string — required, length 1–200slug· string — length 1–100
Response 200 — application/json
id· string (uuid) — requiredslug· string — required, length 1–100name· string — requiredicon· string | null — requiredrole·"owner"|"admin"|"member"|"guest"— requiredcreatedAt· string (date-time) — required
Response default — application/json
error· object — requiredcode·"bad_request"|"validation_failed"|"unauthorized"|"forbidden"|"not_found"|"conflict"|"rate_limited"|"internal"— requiredmessage· string — requiredreason· string — length 1–64
GET /api/v1/workspaces/activatable
List workspaces the caller is a member of that this product is not activated on.
listActivatableWorkspaces · token scope: session only
Query parameters
cursor· string — length 1–∞limit· integer — 1–100
Response 200 — application/json
items· object[] — requiredid· string (uuid) — requiredslug· string — required, length 1–100name· string — requiredicon· string | null — requiredrole·"owner"|"admin"|"member"|"guest"— requiredcreatedAt· string (date-time) — required
nextCursor· string | null — required
Response default — application/json
error· object — requiredcode·"bad_request"|"validation_failed"|"unauthorized"|"forbidden"|"not_found"|"conflict"|"rate_limited"|"internal"— requiredmessage· string — requiredreason· string — length 1–64
GET /api/v1/workspaces/{workspaceId}
Get a single workspace.
getWorkspace · token scope: content:read
Path parameters
workspaceId· string (uuid) — required
Response 200 — application/json
id· string (uuid) — requiredslug· string — required, length 1–100name· string — requiredicon· string | null — requiredrole·"owner"|"admin"|"member"|"guest"— requiredcreatedAt· string (date-time) — requiredactivated· boolean — required
Response default — application/json
error· object — requiredcode·"bad_request"|"validation_failed"|"unauthorized"|"forbidden"|"not_found"|"conflict"|"rate_limited"|"internal"— requiredmessage· string — requiredreason· string — length 1–64
PATCH /api/v1/workspaces/{workspaceId}
Update a workspace name and/or icon.
updateWorkspace · token scope: admin:write
Path parameters
workspaceId· string (uuid) — required
Request body — application/json, required
name· string — length 1–200icon· string | null
Response 200 — application/json
id· string (uuid) — requiredslug· string — required, length 1–100name· string — requiredicon· string | null — requiredrole·"owner"|"admin"|"member"|"guest"— requiredcreatedAt· string (date-time) — required
Response default — application/json
error· object — requiredcode·"bad_request"|"validation_failed"|"unauthorized"|"forbidden"|"not_found"|"conflict"|"rate_limited"|"internal"— requiredmessage· string — requiredreason· string — length 1–64
DELETE /api/v1/workspaces/{workspaceId}
Delete a workspace and everything in it — irreversible. Requires the workspace.delete capability, i.e. owner (ADR-0016 D1); an admin gets 403. Removes memberships, groups, invitations, collections, the field registry, every block with its doc and projections, and every attachment.
deleteWorkspace · token scope: admin:write
Path parameters
workspaceId· string (uuid) — required
Response 200 — application/json
id· string (uuid) — requiredslug· string — required, length 1–100name· string — requiredicon· string | null — requiredrole·"owner"|"admin"|"member"|"guest"— requiredcreatedAt· string (date-time) — required
Response default — application/json
error· object — requiredcode·"bad_request"|"validation_failed"|"unauthorized"|"forbidden"|"not_found"|"conflict"|"rate_limited"|"internal"— requiredmessage· string — requiredreason· string — length 1–64
POST /api/v1/workspaces/{workspaceId}/activation
Activate the asking product on this workspace (ADR-0033). The product comes from the request origin, never from a body. Idempotent.
activateWorkspaceProduct · token scope: session only
Path parameters
workspaceId· string (uuid) — required
Response 200 — application/json
id· string (uuid) — requiredslug· string — required, length 1–100name· string — requiredicon· string | null — requiredrole·"owner"|"admin"|"member"|"guest"— requiredcreatedAt· string (date-time) — requiredactivated· boolean — required
Response default — application/json
error· object — requiredcode·"bad_request"|"validation_failed"|"unauthorized"|"forbidden"|"not_found"|"conflict"|"rate_limited"|"internal"— requiredmessage· string — requiredreason· string — length 1–64
GET /api/v1/workspaces/{workspaceId}/members
List the members of a workspace. Email addresses are disclosed only to callers holding members.manage (admin/owner) — user.email is null for everyone else (ADR-0016 D9d). A guest sees only the members co-present on a page they can read, names only.
listWorkspaceMembers · token scope: admin:read
Path parameters
workspaceId· string (uuid) — required
Query parameters
cursor· string — length 1–∞limit· integer — 1–100
Response 200 — application/json
items· object[] — requiredworkspaceId· string (uuid) — requireduserId· string (uuid) — requiredrole·"owner"|"admin"|"member"|"guest"— requireduser· object — requiredid· string (uuid) — requiredemail· string (email) | null — requiredname· string | null — requiredimage· string | null — required
joinedAt· string (date-time) — required
nextCursor· string | null — required
Response default — application/json
error· object — requiredcode·"bad_request"|"validation_failed"|"unauthorized"|"forbidden"|"not_found"|"conflict"|"rate_limited"|"internal"— requiredmessage· string — requiredreason· string — length 1–64
GET /api/v1/workspaces/{workspaceId}/collections/{collectionId}/members
List a collection's explicit grants (collection_member, ADR-0016 D3) with each subject resolved for display. These are step 2 of resolution — the implicit levels a visibility gives a workspace role (D2) are NOT rows and do not appear here. Requires full on the collection.
listCollectionMembers · token scope: admin:read
Path parameters
workspaceId· string (uuid) — requiredcollectionId· string (uuid) — required
Query parameters
cursor· string — length 1–∞limit· integer — 1–100
Response 200 — application/json
items· object[] — requiredcollectionId· string (uuid) — requiredsubjectType·"user"|"group"— requiredsubjectId· string (uuid) — requiredrole·"read"|"comment"|"edit"|"full"— requiredsubject· object — requiredsubjectType·"user"|"group"— requiredsubjectId· string (uuid) — requiredname· string — requiredimage· string | null — required
nextCursor· string | null — required
Response default — application/json
error· object — requiredcode·"bad_request"|"validation_failed"|"unauthorized"|"forbidden"|"not_found"|"conflict"|"rate_limited"|"internal"— requiredmessage· string — requiredreason· string — length 1–64
PUT /api/v1/workspaces/{workspaceId}/collections/{collectionId}/members
Grant a user or group a level in a collection — upsert on (collection, subjectType, subjectId), so re-sending changes the level. Requires full on the collection; an owner may always write themselves in (D1: owners can take access, never quietly have it). Bumps the access generation.
setCollectionMember · token scope: admin:write
Path parameters
workspaceId· string (uuid) — requiredcollectionId· string (uuid) — required
Request body — application/json, required
subjectType·"user"|"group"— requiredsubjectId· string (uuid) — requiredrole·"read"|"comment"|"edit"|"full"— required
Response 200 — application/json
collectionId· string (uuid) — requiredsubjectType·"user"|"group"— requiredsubjectId· string (uuid) — requiredrole·"read"|"comment"|"edit"|"full"— requiredsubject· object — requiredsubjectType·"user"|"group"— requiredsubjectId· string (uuid) — requiredname· string — requiredimage· string | null — required
Response default — application/json
error· object — requiredcode·"bad_request"|"validation_failed"|"unauthorized"|"forbidden"|"not_found"|"conflict"|"rate_limited"|"internal"— requiredmessage· string — requiredreason· string — length 1–64
DELETE /api/v1/workspaces/{workspaceId}/collections/{collectionId}/members/{subjectType}/{subjectId}
Revoke a subject's explicit grant in a collection. Requires full on the collection. Refused (409) when it would remove the LAST full member (ADR-0016 D6 — the same last-subject invariant as ACLs). Bumps the access generation. Returns the membership that was removed.
removeCollectionMember · token scope: admin:write
Path parameters
workspaceId· string (uuid) — requiredcollectionId· string (uuid) — requiredsubjectType·"user"|"group"— requiredsubjectId· string (uuid) — required
Response 200 — application/json
collectionId· string (uuid) — requiredsubjectType·"user"|"group"— requiredsubjectId· string (uuid) — requiredrole·"read"|"comment"|"edit"|"full"— requiredsubject· object — requiredsubjectType·"user"|"group"— requiredsubjectId· string (uuid) — requiredname· string — requiredimage· string | null — required
Response default — application/json
error· object — requiredcode·"bad_request"|"validation_failed"|"unauthorized"|"forbidden"|"not_found"|"conflict"|"rate_limited"|"internal"— requiredmessage· string — requiredreason· string — length 1–64
POST /api/v1/me/avatar
Set the calling account's profile picture (multipart/form-data, one file field). PNG/JPEG/GIF/WebP by BYTES, not by declared type; max 1024 KiB. Account-wide, so no :workspaceId and no plan quota. Replaces any previous one.
uploadMyAvatar · token scope: session only
Response 200 — application/json
image· string | null — required
Response default — application/json
error· object — requiredcode·"bad_request"|"validation_failed"|"unauthorized"|"forbidden"|"not_found"|"conflict"|"rate_limited"|"internal"— requiredmessage· string — requiredreason· string — length 1–64
DELETE /api/v1/me/avatar
Remove the calling account's picture — row, bytes and image. Displays fall back to initials. Idempotent.
deleteMyAvatar · token scope: session only
Response 200 — application/json
image· string | null — required
Response default — application/json
error· object — requiredcode·"bad_request"|"validation_failed"|"unauthorized"|"forbidden"|"not_found"|"conflict"|"rate_limited"|"internal"— requiredmessage· string — requiredreason· string — length 1–64
GET /api/v1/users/{userId}/avatar
Stream a person's picture, as an <img src> rather than JSON. Readable by that person and by anyone sharing a workspace with them; everything else — unknown id, no picture, no shared workspace — is the same 404. v is a cache-buster and is ignored.
getUserAvatar · token scope: session only
Path parameters
userId· string (uuid) — required
Response 200 — application/json
- string
Response default — application/json
error· object — requiredcode·"bad_request"|"validation_failed"|"unauthorized"|"forbidden"|"not_found"|"conflict"|"rate_limited"|"internal"— requiredmessage· string — requiredreason· string — length 1–64
GET /api/v1/workspaces/{workspaceId}/usage
This workspace's storage and seat usage against its plan's limits. Readable by every member (guests included): it is the answer to “why was my upload refused”. Being over a limit stops new writes of that kind and never removes, hides or locks anything that already exists.
getWorkspaceUsage · token scope: content:read
Path parameters
workspaceId· string (uuid) — required
Response 200 — application/json
workspaceId· string (uuid) — requiredplan·"free"|"pro"|"business"|"enterprise"— requiredplanSource·"operator"|"subscription"| null — required Who decided this plan:operator(assigned by a human in the back office),subscription(a payment), or null (this deployment’s default). An operator-assigned plan cannot be bought over — a payment would be taken and the plan left unchanged — so a client offering a purchase has to read this.limits· object — requiredstorageBytes· integer | null — requiredfileSizeBytes· integer | null — requiredmembers· integer | null — requiredguests· integer | null — requiredversionHistoryDays· integer | null — required
storageBytes· integer — required, 0–9007199254740991attachmentCount· integer — required, 0–9007199254740991members· integer — required, 0–9007199254740991guests· integer — required, 0–9007199254740991over·"storageBytes"|"fileSizeBytes"|"members"|"guests"|"versionHistoryDays"[] — required
Response default — application/json
error· object — requiredcode·"bad_request"|"validation_failed"|"unauthorized"|"forbidden"|"not_found"|"conflict"|"rate_limited"|"internal"— requiredmessage· string — requiredreason· string — length 1–64
GET /api/v1/workspaces/{workspaceId}/me/capabilities
The caller's own workspace role and the administrative capabilities it carries (ADR-0016 D1), derived server-side from workspaceCapabilities(). The single source for enabling admin UI — content permissions are NOT here (no role grants implicit read); those come from getPageAccess.
getMyCapabilities · token scope: admin:read
Path parameters
workspaceId· string (uuid) — required
Response 200 — application/json
workspaceId· string (uuid) — requireduserId· string (uuid) — requiredrole·"owner"|"admin"|"member"|"guest"— requiredcapabilities·"workspace.delete"|"workspace.transferOwnership"|"workspace.manageOwnerRole"|"workspace.manage"|"members.manage"|"groups.manage"|"invitations.manage"|"fields.manage"|"collections.create"|"collections.delete"|"purge"[] — required
Response default — application/json
error· object — requiredcode·"bad_request"|"validation_failed"|"unauthorized"|"forbidden"|"not_found"|"conflict"|"rate_limited"|"internal"— requiredmessage· string — requiredreason· string — length 1–64
PATCH /api/v1/workspaces/{workspaceId}/members/{userId}
Change a member's workspace role (ADR-0016 D11a). Granting or revoking owner requires workspace.manageOwnerRole (owner only); every other change requires members.manage. Nobody may raise their OWN role, and the last owner cannot be demoted (409). Bumps the workspace access generation.
updateMemberRole · token scope: admin:write
Path parameters
workspaceId· string (uuid) — requireduserId· string (uuid) — required
Request body — application/json, required
role·"owner"|"admin"|"member"|"guest"— required
Response 200 — application/json
workspaceId· string (uuid) — requireduserId· string (uuid) — requiredrole·"owner"|"admin"|"member"|"guest"— requireduser· object — requiredid· string (uuid) — requiredemail· string (email) | null — requiredname· string | null — requiredimage· string | null — required
joinedAt· string (date-time) — required
Response default — application/json
error· object — requiredcode·"bad_request"|"validation_failed"|"unauthorized"|"forbidden"|"not_found"|"conflict"|"rate_limited"|"internal"— requiredmessage· string — requiredreason· string — length 1–64
DELETE /api/v1/workspaces/{workspaceId}/members/{userId}
Remove a member from the workspace. Requires members.manage; removing an owner additionally requires workspace.manageOwnerRole, and the last owner cannot be removed (409). Drops their group memberships; their explicit ACL and collection-member rows are removed with them, so a restricted page may be left without a full subject — the D6 invariant is re-checked and the removal is refused (409) when it would strand one. Returns the membership that was removed.
removeMember · token scope: admin:write
Path parameters
workspaceId· string (uuid) — requireduserId· string (uuid) — required
Response 200 — application/json
workspaceId· string (uuid) — requireduserId· string (uuid) — requiredrole·"owner"|"admin"|"member"|"guest"— requireduser· object — requiredid· string (uuid) — requiredemail· string (email) | null — requiredname· string | null — requiredimage· string | null — required
joinedAt· string (date-time) — required
Response default — application/json
error· object — requiredcode·"bad_request"|"validation_failed"|"unauthorized"|"forbidden"|"not_found"|"conflict"|"rate_limited"|"internal"— requiredmessage· string — requiredreason· string — length 1–64
GET /api/v1/workspaces/{workspaceId}/groups
List the workspace groups with their member counts. Readable by any member: a group name has to be legible in a share dialog for the permission model to be understandable. Writes require groups.manage.
listGroups · token scope: admin:read
Path parameters
workspaceId· string (uuid) — required
Query parameters
cursor· string — length 1–∞limit· integer — 1–100
Response 200 — application/json
items· object[] — requiredid· string (uuid) — requiredworkspaceId· string (uuid) — requiredname· string — requiredmemberCount· integer — required, 0–9007199254740991createdAt· string (date-time) — required
nextCursor· string | null — required
Response default — application/json
error· object — requiredcode·"bad_request"|"validation_failed"|"unauthorized"|"forbidden"|"not_found"|"conflict"|"rate_limited"|"internal"— requiredmessage· string — requiredreason· string — length 1–64
POST /api/v1/workspaces/{workspaceId}/groups
Create a group. Requires groups.manage. Groups are flat — no nesting (D7).
createGroup · token scope: admin:write
Path parameters
workspaceId· string (uuid) — required
Request body — application/json, required
name· string — required, length 1–200
Response 200 — application/json
id· string (uuid) — requiredworkspaceId· string (uuid) — requiredname· string — requiredmemberCount· integer — required, 0–9007199254740991createdAt· string (date-time) — required
Response default — application/json
error· object — requiredcode·"bad_request"|"validation_failed"|"unauthorized"|"forbidden"|"not_found"|"conflict"|"rate_limited"|"internal"— requiredmessage· string — requiredreason· string — length 1–64
PATCH /api/v1/workspaces/{workspaceId}/groups/{groupId}
Rename a group. Requires groups.manage.
updateGroup · token scope: admin:write
Path parameters
workspaceId· string (uuid) — requiredgroupId· string (uuid) — required
Request body — application/json, required
name· string — required, length 1–200
Response 200 — application/json
id· string (uuid) — requiredworkspaceId· string (uuid) — requiredname· string — requiredmemberCount· integer — required, 0–9007199254740991createdAt· string (date-time) — required
Response default — application/json
error· object — requiredcode·"bad_request"|"validation_failed"|"unauthorized"|"forbidden"|"not_found"|"conflict"|"rate_limited"|"internal"— requiredmessage· string — requiredreason· string — length 1–64
DELETE /api/v1/workspaces/{workspaceId}/groups/{groupId}
Delete a group. Requires groups.manage. Its permission and collection_member rows go with it, so access granted only through this group is revoked — refused (409) when that would leave a restricted block or a collection without a full subject (D6). Bumps the access generation. Returns the deleted group.
deleteGroup · token scope: admin:write
Path parameters
workspaceId· string (uuid) — requiredgroupId· string (uuid) — required
Response 200 — application/json
id· string (uuid) — requiredworkspaceId· string (uuid) — requiredname· string — requiredmemberCount· integer — required, 0–9007199254740991createdAt· string (date-time) — required
Response default — application/json
error· object — requiredcode·"bad_request"|"validation_failed"|"unauthorized"|"forbidden"|"not_found"|"conflict"|"rate_limited"|"internal"— requiredmessage· string — requiredreason· string — length 1–64
GET /api/v1/workspaces/{workspaceId}/groups/{groupId}/members
List a group's users. Readable by any member (see listGroups); changing the membership requires groups.manage. Because the read half is open to every member — a guest included — user.email follows the same D9d rule as listWorkspaceMembers: an address for a caller holding members.manage, null for everybody else.
listGroupMembers · token scope: admin:read
Path parameters
workspaceId· string (uuid) — requiredgroupId· string (uuid) — required
Query parameters
cursor· string — length 1–∞limit· integer — 1–100
Response 200 — application/json
items· object[] — requiredgroupId· string (uuid) — requireduserId· string (uuid) — requireduser· object — requiredid· string (uuid) — requiredemail· string (email) | null — requiredname· string | null — requiredimage· string | null — required
nextCursor· string | null — required
Response default — application/json
error· object — requiredcode·"bad_request"|"validation_failed"|"unauthorized"|"forbidden"|"not_found"|"conflict"|"rate_limited"|"internal"— requiredmessage· string — requiredreason· string — length 1–64
PUT /api/v1/workspaces/{workspaceId}/groups/{groupId}/members/{userId}
Add a workspace member to a group (idempotent: re-adding is a no-op that returns the existing membership). Requires groups.manage; the user must already be a member of the workspace. Bumps the access generation (D10).
addGroupMember · token scope: admin:write
Path parameters
workspaceId· string (uuid) — requiredgroupId· string (uuid) — requireduserId· string (uuid) — required
Response 200 — application/json
groupId· string (uuid) — requireduserId· string (uuid) — requireduser· object — requiredid· string (uuid) — requiredemail· string (email) | null — requiredname· string | null — requiredimage· string | null — required
Response default — application/json
error· object — requiredcode·"bad_request"|"validation_failed"|"unauthorized"|"forbidden"|"not_found"|"conflict"|"rate_limited"|"internal"— requiredmessage· string — requiredreason· string — length 1–64
DELETE /api/v1/workspaces/{workspaceId}/groups/{groupId}/members/{userId}
Remove a user from a group. Requires groups.manage. Refused (409) when the group is the only remaining full subject of a restricted block or a collection and this user is its last member (D6). Bumps the access generation. Returns the membership that was removed.
removeGroupMember · token scope: admin:write
Path parameters
workspaceId· string (uuid) — requiredgroupId· string (uuid) — requireduserId· string (uuid) — required
Response 200 — application/json
groupId· string (uuid) — requireduserId· string (uuid) — requireduser· object — requiredid· string (uuid) — requiredemail· string (email) | null — requiredname· string | null — requiredimage· string | null — required
Response default — application/json
error· object — requiredcode·"bad_request"|"validation_failed"|"unauthorized"|"forbidden"|"not_found"|"conflict"|"rate_limited"|"internal"— requiredmessage· string — requiredreason· string — length 1–64
GET /api/v1/workspaces/{workspaceId}/invitations
List the workspace's invitations (pending first, newest first). Requires invitations.manage. Tokens are never returned again — only createInvitation ever shows one.
listInvitations · token scope: admin:read
Path parameters
workspaceId· string (uuid) — required
Query parameters
cursor· string — length 1–∞limit· integer — 1–100
Response 200 — application/json
items· object[] — requiredid· string (uuid) — requiredworkspaceId· string (uuid) — requiredemail· string (email) — requiredrole·"admin"|"member"|"guest"— requiredinvitedBy· string (uuid) — requiredexpiresAt· string (date-time) — requiredacceptedAt· string (date-time) | null — requiredcreatedAt· string (date-time) — required
nextCursor· string | null — required
Response default — application/json
error· object — requiredcode·"bad_request"|"validation_failed"|"unauthorized"|"forbidden"|"not_found"|"conflict"|"rate_limited"|"internal"— requiredmessage· string — requiredreason· string — length 1–64
POST /api/v1/workspaces/{workspaceId}/invitations
Invite an email address. Requires invitations.manage; the invited role may not exceed the inviter's own, and owner is not invitable (ownership is transferred). At most one un-accepted invitation exists per (workspace, lower(email)) — a repeat invite replaces the pending one. The token is 32 CSPRNG bytes stored HASHED, single-use, 14-day expiry, and appears exactly once: in this response's url. The response never reveals whether the address already has an account.
createInvitation · token scope: admin:write
Path parameters
workspaceId· string (uuid) — required
Request body — application/json, required
email· string (email) — required, length 0–320role·"admin"|"member"|"guest"— required
Response 200 — application/json
id· string (uuid) — requiredworkspaceId· string (uuid) — requiredemail· string (email) — requiredrole·"admin"|"member"|"guest"— requiredinvitedBy· string (uuid) — requiredexpiresAt· string (date-time) — requiredacceptedAt· string (date-time) | null — requiredcreatedAt· string (date-time) — requiredurl· string — requiredmailed· boolean — required
Response default — application/json
error· object — requiredcode·"bad_request"|"validation_failed"|"unauthorized"|"forbidden"|"not_found"|"conflict"|"rate_limited"|"internal"— requiredmessage· string — requiredreason· string — length 1–64
DELETE /api/v1/workspaces/{workspaceId}/invitations/{invitationId}
Revoke an invitation by deleting its row — the token stops working immediately. Requires invitations.manage. Returns the revoked invitation.
revokeInvitation · token scope: admin:write
Path parameters
workspaceId· string (uuid) — requiredinvitationId· string (uuid) — required
Response 200 — application/json
id· string (uuid) — requiredworkspaceId· string (uuid) — requiredemail· string (email) — requiredrole·"admin"|"member"|"guest"— requiredinvitedBy· string (uuid) — requiredexpiresAt· string (date-time) — requiredacceptedAt· string (date-time) | null — requiredcreatedAt· string (date-time) — required
Response default — application/json
error· object — requiredcode·"bad_request"|"validation_failed"|"unauthorized"|"forbidden"|"not_found"|"conflict"|"rate_limited"|"internal"— requiredmessage· string — requiredreason· string — length 1–64
POST /api/v1/invitations/accept
Redeem an invitation token and join its workspace. Deliberately OUTSIDE the /workspaces/:workspaceId scope (ADR-0016 D11): the membership guard would 404 the very person being invited. Requires a session and matches on its email (lower(email) on both sides; plus-addressing NOT stripped). The invited role is re-validated against the INVITER's current role at accept time. insert … on conflict do nothing on workspace_member, so an existing membership wins and a second acceptance cannot change a role. Returns the resulting membership (existing or new).
acceptInvitation · token scope: session only
Request body — application/json, required
token· string — required, length 1–200
Response 200 — application/json
workspaceId· string (uuid) — requireduserId· string (uuid) — requiredrole·"owner"|"admin"|"member"|"guest"— requireduser· object — requiredid· string (uuid) — requiredemail· string (email) | null — requiredname· string | null — requiredimage· string | null — required
joinedAt· string (date-time) — required
Response default — application/json
error· object — requiredcode·"bad_request"|"validation_failed"|"unauthorized"|"forbidden"|"not_found"|"conflict"|"rate_limited"|"internal"— requiredmessage· string — requiredreason· string — length 1–64