Basalt docs
GitHub

Permissions and sharing

Per-page access control lists, restriction boundaries, public share links, and the token routes a stranger reads them through.

10 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/public/shares/{token}

Read the page a public link points at. Unauthenticated; read-only; sanitized (no identities, no comments, no history, no field values).

readPublicPage · token scope: session only

Path parameters

  • token · string — required, length 20–64

Query parameters

  • format · "markdown"

Response 200application/json

  • pageId · string (uuid) — required
  • title · string — required
  • icon · string | null — required
  • blocks · any[] — required
  • children · object[] — required
    • pageId · string (uuid) — required
    • title · string — required
    • icon · string | null — required
  • isRoot · boolean — required
  • rootPageId · string (uuid) — 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/public/shares/{token}/pages/{pageId}

Read one page inside a public link’s scope. 404 for any page the share does not cover — the same answer an unknown id gets.

readPublicSharePage · token scope: session only

Path parameters

  • token · string — required, length 20–64
  • pageId · string (uuid) — required

Query parameters

  • format · "markdown"

Response 200application/json

  • pageId · string (uuid) — required
  • title · string — required
  • icon · string | null — required
  • blocks · any[] — required
  • children · object[] — required
    • pageId · string (uuid) — required
    • title · string — required
    • icon · string | null — required
  • isRoot · boolean — required
  • rootPageId · string (uuid) — 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/public/shares/{token}/attachments/{attachmentId}

Serve an attachment referenced by a page inside this share’s scope. Scoped to the token, never globally public (ADR-0016 D13a).

readPublicShareAttachment · token scope: session only

Path parameters

  • token · string — required, length 20–64
  • attachmentId · string (uuid) — required

Response 200application/json

  • string

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}/pages/{pageId}/public-share

Get this page's public link, or null when it is not published.

getPublicShare · token scope: admin:read

Path parameters

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

Response 200application/json

  • share · object | null — required
    • pageId · string (uuid) — required
    • workspaceId · string (uuid) — required
    • token · string — required, length 20–64
    • url · string — required
    • includeChildren · boolean — required
    • expiresAt · string (date-time) | null — required
    • createdAt · string (date-time) — required
    • createdBy · string (uuid) | 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}/pages/{pageId}/public-share

Publish this page as a read-only public link, or change an existing publication (child inclusion, expiry, token rotation). Requires full on the page and at least the member workspace role.

setPublicShare · token scope: admin:write

Path parameters

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

Request bodyapplication/json, required

  • includeChildren · boolean
  • expiresAt · string (date-time) | null
  • rotate · boolean

Response 200application/json

  • pageId · string (uuid) — required
  • workspaceId · string (uuid) — required
  • token · string — required, length 20–64
  • url · string — required
  • includeChildren · boolean — required
  • expiresAt · string (date-time) | null — required
  • createdAt · string (date-time) — required
  • createdBy · string (uuid) | 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}/pages/{pageId}/public-share

Withdraw the public link immediately. The row is deleted; nothing authorizes a public read afterwards.

revokePublicShare · token scope: admin:write

Path parameters

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

Response 200application/json

  • revoked · 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

GET /api/v1/workspaces/{workspaceId}/pages/{pageId}/access

The Share dialog's read model (ADR-0016 D5/D6): the CALLER's effective level with the source that decided it and, for an inherited grant, the ancestor it came from; whether the page carries its own restriction boundary; and every ACL row that currently applies (those on the page plus the inherited ones, each carrying the blockId it was written on), cut at the nearest boundary exactly as resolution is. Requires read on the page; editing any of it requires full.

getPageAccess · token scope: admin:read

Path parameters

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

Response 200application/json

  • pageId · string (uuid) — required
  • level · "read" | "comment" | "edit" | "full" | null — required
  • source · "acl" | "collection-member" | "collection-visibility" | "workspace-role" | "none" — required
  • inheritedFrom · string (uuid) | null — required
  • restricted · boolean — required
  • permissions · object[] — required
    • blockId · 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

PUT /api/v1/workspaces/{workspaceId}/pages/{pageId}/permissions

Write an ACL row on a page — upsert on (block, subjectType, subjectId), so re-sending changes the level. Requires full on the page (D4); an owner may always write themselves in at full, and that grant then shows up in this very list like any other (D1). There is no per-subject deny (D6). Bumps the workspace access generation.

setPagePermission · token scope: admin:write

Path parameters

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

Request bodyapplication/json, required

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

Response 200application/json

  • blockId · 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}/pages/{pageId}/permissions/{subjectType}/{subjectId}

Remove an ACL row from a page. Requires full on the page. Only rows written ON this page can be removed — an inherited row is deleted where it lives (404 otherwise). Refused (409) when the page is restricted and this is its last full subject: the invariant is transaction-checked on every ACL delete, not just at the restrict toggle (D6). Returns the removed row.

removePagePermission · token scope: admin:write

Path parameters

  • workspaceId · string (uuid) — required
  • pageId · string (uuid) — required
  • subjectType · "user" | "group" — required
  • subjectId · string (uuid) — required

Response 200application/json

  • blockId · 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

POST /api/v1/workspaces/{workspaceId}/pages/{pageId}/restrict

Toggle a page's restriction boundary (ADR-0016 D6): below a restricted page only ACLs written on it and its descendants apply — both the ancestor search and the collection fallback are cut. Requires full on the page. Turning it ON seeds the page ACL in the SAME transaction (the actor at full, plus every subject currently holding effective full, narrowed by an optional keep), so the page is never stranded; a keep leaving no full subject is refused (409). Turning it OFF restores inheritance and leaves the written rows in place. Returns the page access read model.

restrictPage · token scope: admin:write

Path parameters

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

Request bodyapplication/json, required

  • restricted · boolean — required
  • keep · object[]
    • subjectType · "user" | "group" — required
    • subjectId · string (uuid) — required

Response 200application/json

  • pageId · string (uuid) — required
  • level · "read" | "comment" | "edit" | "full" | null — required
  • source · "acl" | "collection-member" | "collection-visibility" | "workspace-role" | "none" — required
  • inheritedFrom · string (uuid) | null — required
  • restricted · boolean — required
  • permissions · object[] — required
    • blockId · 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