Basalt docs
GitHub

Workspaces and people

Workspaces, their members, groups and invitations, the capabilities of the calling account, and what the workspace has used of its plan.

24 operations. Every path is relative to the instance origin; every response body is JSON unless stated. 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 200application/json

  • items · object[] — required
    • id · string (uuid) — required
    • slug · string — required, length 1–100
    • name · string — required
    • icon · string | null — required
    • role · "owner" | "admin" | "member" | "guest" — required
    • createdAt · string (date-time) — required
  • nextCursor · string | null — required

Response defaultapplication/json

  • error · object — required
    • code · "bad_request" | "validation_failed" | "unauthorized" | "forbidden" | "not_found" | "conflict" | "rate_limited" | "internal" — required
    • message · string — required

POST /api/v1/workspaces

Create a workspace; the caller becomes its owner.

createWorkspace · token scope: session only

Request bodyapplication/json, required

  • name · string — required, length 1–200
  • slug · string — length 1–100

Response 200application/json

  • id · string (uuid) — required
  • slug · string — required, length 1–100
  • name · string — required
  • icon · string | null — required
  • role · "owner" | "admin" | "member" | "guest" — required
  • createdAt · string (date-time) — required

Response defaultapplication/json

  • error · object — required
    • code · "bad_request" | "validation_failed" | "unauthorized" | "forbidden" | "not_found" | "conflict" | "rate_limited" | "internal" — required
    • message · string — required

GET /api/v1/workspaces/{workspaceId}

Get a single workspace.

getWorkspace · token scope: content:read

Path parameters

  • workspaceId · string (uuid) — required

Response 200application/json

  • id · string (uuid) — required
  • slug · string — required, length 1–100
  • name · string — required
  • icon · string | null — required
  • role · "owner" | "admin" | "member" | "guest" — required
  • createdAt · string (date-time) — required

Response defaultapplication/json

  • error · object — required
    • code · "bad_request" | "validation_failed" | "unauthorized" | "forbidden" | "not_found" | "conflict" | "rate_limited" | "internal" — required
    • message · string — required

PATCH /api/v1/workspaces/{workspaceId}

Update a workspace name and/or icon.

updateWorkspace · token scope: admin:write

Path parameters

  • workspaceId · string (uuid) — required

Request bodyapplication/json, required

  • name · string — length 1–200
  • icon · string | null

Response 200application/json

  • id · string (uuid) — required
  • slug · string — required, length 1–100
  • name · string — required
  • icon · string | null — required
  • role · "owner" | "admin" | "member" | "guest" — required
  • createdAt · string (date-time) — required

Response defaultapplication/json

  • error · object — required
    • code · "bad_request" | "validation_failed" | "unauthorized" | "forbidden" | "not_found" | "conflict" | "rate_limited" | "internal" — required
    • message · string — required

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 200application/json

  • id · string (uuid) — required
  • slug · string — required, length 1–100
  • name · string — required
  • icon · string | null — required
  • role · "owner" | "admin" | "member" | "guest" — required
  • createdAt · string (date-time) — required

Response defaultapplication/json

  • error · object — required
    • code · "bad_request" | "validation_failed" | "unauthorized" | "forbidden" | "not_found" | "conflict" | "rate_limited" | "internal" — required
    • message · string — required

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 200application/json

  • items · object[] — required
    • workspaceId · string (uuid) — required
    • userId · string (uuid) — required
    • role · "owner" | "admin" | "member" | "guest" — required
    • user · object — required
      • id · string (uuid) — required
      • email · string (email) | null — required
      • name · string | null — required
      • image · string | null — required
    • joinedAt · string (date-time) — required
  • nextCursor · string | null — required

Response defaultapplication/json

  • error · object — required
    • code · "bad_request" | "validation_failed" | "unauthorized" | "forbidden" | "not_found" | "conflict" | "rate_limited" | "internal" — required
    • message · string — required

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) — required
  • collectionId · string (uuid) — required

Query parameters

  • cursor · string — length 1–∞
  • limit · integer — 1–100

Response 200application/json

  • items · object[] — required
    • collectionId · string (uuid) — required
    • subjectType · "user" | "group" — required
    • subjectId · string (uuid) — required
    • role · "read" | "comment" | "edit" | "full" — required
    • subject · object — required
      • subjectType · "user" | "group" — required
      • subjectId · string (uuid) — required
      • name · string — required
      • image · string | null — required
  • nextCursor · string | null — required

Response defaultapplication/json

  • error · object — required
    • code · "bad_request" | "validation_failed" | "unauthorized" | "forbidden" | "not_found" | "conflict" | "rate_limited" | "internal" — required
    • message · string — required

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) — required
  • collectionId · string (uuid) — required

Request bodyapplication/json, required

  • subjectType · "user" | "group" — required
  • subjectId · string (uuid) — required
  • role · "read" | "comment" | "edit" | "full" — required

Response 200application/json

  • collectionId · string (uuid) — required
  • subjectType · "user" | "group" — required
  • subjectId · string (uuid) — required
  • role · "read" | "comment" | "edit" | "full" — required
  • subject · object — required
    • subjectType · "user" | "group" — required
    • subjectId · string (uuid) — required
    • name · string — required
    • image · string | null — required

Response defaultapplication/json

  • error · object — required
    • code · "bad_request" | "validation_failed" | "unauthorized" | "forbidden" | "not_found" | "conflict" | "rate_limited" | "internal" — required
    • message · string — required

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) — required
  • collectionId · string (uuid) — required
  • subjectType · "user" | "group" — required
  • subjectId · string (uuid) — required

Response 200application/json

  • collectionId · string (uuid) — required
  • subjectType · "user" | "group" — required
  • subjectId · string (uuid) — required
  • role · "read" | "comment" | "edit" | "full" — required
  • subject · object — required
    • subjectType · "user" | "group" — required
    • subjectId · string (uuid) — required
    • name · string — required
    • image · string | null — required

Response defaultapplication/json

  • error · object — required
    • code · "bad_request" | "validation_failed" | "unauthorized" | "forbidden" | "not_found" | "conflict" | "rate_limited" | "internal" — required
    • message · string — required

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 200application/json

  • workspaceId · string (uuid) — required
  • plan · "free" | "team" | "business" | "self_hosted" — required
  • limits · object — required
    • storageBytes · integer | null — required
    • fileSizeBytes · integer | null — required
    • members · integer | null — required
    • guests · integer | null — required
    • versionHistoryDays · integer | null — required
  • storageBytes · integer — required, 0–9007199254740991
  • attachmentCount · integer — required, 0–9007199254740991
  • members · integer — required, 0–9007199254740991
  • guests · integer — required, 0–9007199254740991
  • over · "storageBytes" | "fileSizeBytes" | "members" | "guests" | "versionHistoryDays"[] — required

Response defaultapplication/json

  • error · object — required
    • code · "bad_request" | "validation_failed" | "unauthorized" | "forbidden" | "not_found" | "conflict" | "rate_limited" | "internal" — required
    • message · string — required

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 200application/json

  • workspaceId · string (uuid) — required
  • userId · string (uuid) — required
  • role · "owner" | "admin" | "member" | "guest" — required
  • capabilities · "workspace.delete" | "workspace.transferOwnership" | "workspace.manageOwnerRole" | "workspace.manage" | "members.manage" | "groups.manage" | "invitations.manage" | "fields.manage" | "collections.create" | "collections.delete" | "purge"[] — required

Response defaultapplication/json

  • error · object — required
    • code · "bad_request" | "validation_failed" | "unauthorized" | "forbidden" | "not_found" | "conflict" | "rate_limited" | "internal" — required
    • message · string — required

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) — required
  • userId · string (uuid) — required

Request bodyapplication/json, required

  • role · "owner" | "admin" | "member" | "guest" — required

Response 200application/json

  • workspaceId · string (uuid) — required
  • userId · string (uuid) — required
  • role · "owner" | "admin" | "member" | "guest" — required
  • user · object — required
    • id · string (uuid) — required
    • email · string (email) | null — required
    • name · string | null — required
    • image · string | null — required
  • joinedAt · string (date-time) — required

Response defaultapplication/json

  • error · object — required
    • code · "bad_request" | "validation_failed" | "unauthorized" | "forbidden" | "not_found" | "conflict" | "rate_limited" | "internal" — required
    • message · string — required

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) — required
  • userId · string (uuid) — required

Response 200application/json

  • workspaceId · string (uuid) — required
  • userId · string (uuid) — required
  • role · "owner" | "admin" | "member" | "guest" — required
  • user · object — required
    • id · string (uuid) — required
    • email · string (email) | null — required
    • name · string | null — required
    • image · string | null — required
  • joinedAt · string (date-time) — required

Response defaultapplication/json

  • error · object — required
    • code · "bad_request" | "validation_failed" | "unauthorized" | "forbidden" | "not_found" | "conflict" | "rate_limited" | "internal" — required
    • message · string — required

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 200application/json

  • items · object[] — required
    • id · string (uuid) — required
    • workspaceId · string (uuid) — required
    • name · string — required
    • memberCount · integer — required, 0–9007199254740991
    • createdAt · string (date-time) — required
  • nextCursor · string | null — required

Response defaultapplication/json

  • error · object — required
    • code · "bad_request" | "validation_failed" | "unauthorized" | "forbidden" | "not_found" | "conflict" | "rate_limited" | "internal" — required
    • message · string — required

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 bodyapplication/json, required

  • name · string — required, length 1–200

Response 200application/json

  • id · string (uuid) — required
  • workspaceId · string (uuid) — required
  • name · string — required
  • memberCount · integer — required, 0–9007199254740991
  • createdAt · string (date-time) — required

Response defaultapplication/json

  • error · object — required
    • code · "bad_request" | "validation_failed" | "unauthorized" | "forbidden" | "not_found" | "conflict" | "rate_limited" | "internal" — required
    • message · string — required

PATCH /api/v1/workspaces/{workspaceId}/groups/{groupId}

Rename a group. Requires groups.manage.

updateGroup · token scope: admin:write

Path parameters

  • workspaceId · string (uuid) — required
  • groupId · string (uuid) — required

Request bodyapplication/json, required

  • name · string — required, length 1–200

Response 200application/json

  • id · string (uuid) — required
  • workspaceId · string (uuid) — required
  • name · string — required
  • memberCount · integer — required, 0–9007199254740991
  • createdAt · string (date-time) — required

Response defaultapplication/json

  • error · object — required
    • code · "bad_request" | "validation_failed" | "unauthorized" | "forbidden" | "not_found" | "conflict" | "rate_limited" | "internal" — required
    • message · string — required

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) — required
  • groupId · string (uuid) — required

Response 200application/json

  • id · string (uuid) — required
  • workspaceId · string (uuid) — required
  • name · string — required
  • memberCount · integer — required, 0–9007199254740991
  • createdAt · string (date-time) — required

Response defaultapplication/json

  • error · object — required
    • code · "bad_request" | "validation_failed" | "unauthorized" | "forbidden" | "not_found" | "conflict" | "rate_limited" | "internal" — required
    • message · string — required

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) — required
  • groupId · string (uuid) — required

Query parameters

  • cursor · string — length 1–∞
  • limit · integer — 1–100

Response 200application/json

  • items · object[] — required
    • groupId · string (uuid) — required
    • userId · string (uuid) — required
    • user · object — required
      • id · string (uuid) — required
      • email · string (email) | null — required
      • name · string | null — required
      • image · string | null — required
  • nextCursor · string | null — required

Response defaultapplication/json

  • error · object — required
    • code · "bad_request" | "validation_failed" | "unauthorized" | "forbidden" | "not_found" | "conflict" | "rate_limited" | "internal" — required
    • message · string — required

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) — required
  • groupId · string (uuid) — required
  • userId · string (uuid) — required

Response 200application/json

  • groupId · string (uuid) — required
  • userId · string (uuid) — required
  • user · object — required
    • id · string (uuid) — required
    • email · string (email) | null — required
    • name · string | null — required
    • image · string | null — required

Response defaultapplication/json

  • error · object — required
    • code · "bad_request" | "validation_failed" | "unauthorized" | "forbidden" | "not_found" | "conflict" | "rate_limited" | "internal" — required
    • message · string — required

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) — required
  • groupId · string (uuid) — required
  • userId · string (uuid) — required

Response 200application/json

  • groupId · string (uuid) — required
  • userId · string (uuid) — required
  • user · object — required
    • id · string (uuid) — required
    • email · string (email) | null — required
    • name · string | null — required
    • image · string | null — required

Response defaultapplication/json

  • error · object — required
    • code · "bad_request" | "validation_failed" | "unauthorized" | "forbidden" | "not_found" | "conflict" | "rate_limited" | "internal" — required
    • message · string — required

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 200application/json

  • items · object[] — required
    • id · string (uuid) — required
    • workspaceId · string (uuid) — required
    • email · string (email) — required
    • role · "admin" | "member" | "guest" — required
    • invitedBy · string (uuid) — required
    • expiresAt · string (date-time) — required
    • acceptedAt · string (date-time) | null — required
    • createdAt · string (date-time) — required
  • nextCursor · string | null — required

Response defaultapplication/json

  • error · object — required
    • code · "bad_request" | "validation_failed" | "unauthorized" | "forbidden" | "not_found" | "conflict" | "rate_limited" | "internal" — required
    • message · string — required

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 bodyapplication/json, required

  • email · string (email) — required, length 0–320
  • role · "admin" | "member" | "guest" — required

Response 200application/json

  • id · string (uuid) — required
  • workspaceId · string (uuid) — required
  • email · string (email) — required
  • role · "admin" | "member" | "guest" — required
  • invitedBy · string (uuid) — required
  • expiresAt · string (date-time) — required
  • acceptedAt · string (date-time) | null — required
  • createdAt · string (date-time) — required
  • url · string — required
  • mailed · boolean — required

Response defaultapplication/json

  • error · object — required
    • code · "bad_request" | "validation_failed" | "unauthorized" | "forbidden" | "not_found" | "conflict" | "rate_limited" | "internal" — required
    • message · string — required

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) — required
  • invitationId · string (uuid) — required

Response 200application/json

  • id · string (uuid) — required
  • workspaceId · string (uuid) — required
  • email · string (email) — required
  • role · "admin" | "member" | "guest" — required
  • invitedBy · string (uuid) — required
  • expiresAt · string (date-time) — required
  • acceptedAt · string (date-time) | null — required
  • createdAt · string (date-time) — required

Response defaultapplication/json

  • error · object — required
    • code · "bad_request" | "validation_failed" | "unauthorized" | "forbidden" | "not_found" | "conflict" | "rate_limited" | "internal" — required
    • message · string — required

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 bodyapplication/json, required

  • token · string — required, length 1–200

Response 200application/json

  • workspaceId · string (uuid) — required
  • userId · string (uuid) — required
  • role · "owner" | "admin" | "member" | "guest" — required
  • user · object — required
    • id · string (uuid) — required
    • email · string (email) | null — required
    • name · string | null — required
    • image · string | null — required
  • joinedAt · string (date-time) — required

Response defaultapplication/json

  • error · object — required
    • code · "bad_request" | "validation_failed" | "unauthorized" | "forbidden" | "not_found" | "conflict" | "rate_limited" | "internal" — required
    • message · string — required