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 200 — application/json
pageId· string (uuid) — requiredtitle· string — requiredicon· string | null — requiredblocks· any[] — requiredchildren· object[] — requiredpageId· string (uuid) — requiredtitle· string — requiredicon· string | null — required
isRoot· boolean — requiredrootPageId· string (uuid) — required
Response default — application/json
error· object — requiredcode·"bad_request"|"validation_failed"|"unauthorized"|"forbidden"|"not_found"|"conflict"|"rate_limited"|"internal"— requiredmessage· 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–64pageId· string (uuid) — required
Query parameters
format·"markdown"
Response 200 — application/json
pageId· string (uuid) — requiredtitle· string — requiredicon· string | null — requiredblocks· any[] — requiredchildren· object[] — requiredpageId· string (uuid) — requiredtitle· string — requiredicon· string | null — required
isRoot· boolean — requiredrootPageId· string (uuid) — required
Response default — application/json
error· object — requiredcode·"bad_request"|"validation_failed"|"unauthorized"|"forbidden"|"not_found"|"conflict"|"rate_limited"|"internal"— requiredmessage· 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–64attachmentId· 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 — 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) — requiredpageId· string (uuid) — required
Response 200 — application/json
share· object | null — requiredpageId· string (uuid) — requiredworkspaceId· string (uuid) — requiredtoken· string — required, length 20–64url· string — requiredincludeChildren· boolean — requiredexpiresAt· string (date-time) | null — requiredcreatedAt· string (date-time) — requiredcreatedBy· string (uuid) | null — required
Response default — application/json
error· object — requiredcode·"bad_request"|"validation_failed"|"unauthorized"|"forbidden"|"not_found"|"conflict"|"rate_limited"|"internal"— requiredmessage· 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) — requiredpageId· string (uuid) — required
Request body — application/json, required
includeChildren· booleanexpiresAt· string (date-time) | nullrotate· boolean
Response 200 — application/json
pageId· string (uuid) — requiredworkspaceId· string (uuid) — requiredtoken· string — required, length 20–64url· string — requiredincludeChildren· boolean — requiredexpiresAt· string (date-time) | null — requiredcreatedAt· string (date-time) — requiredcreatedBy· string (uuid) | null — required
Response default — application/json
error· object — requiredcode·"bad_request"|"validation_failed"|"unauthorized"|"forbidden"|"not_found"|"conflict"|"rate_limited"|"internal"— requiredmessage· 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) — requiredpageId· string (uuid) — required
Response 200 — application/json
revoked· boolean — required
Response default — application/json
error· object — requiredcode·"bad_request"|"validation_failed"|"unauthorized"|"forbidden"|"not_found"|"conflict"|"rate_limited"|"internal"— requiredmessage· 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) — requiredpageId· string (uuid) — required
Response 200 — application/json
pageId· string (uuid) — requiredlevel·"read"|"comment"|"edit"|"full"| null — requiredsource·"acl"|"collection-member"|"collection-visibility"|"workspace-role"|"none"— requiredinheritedFrom· string (uuid) | null — requiredrestricted· boolean — requiredpermissions· object[] — requiredblockId· 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 — 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) — requiredpageId· string (uuid) — required
Request body — application/json, required
subjectType·"user"|"group"— requiredsubjectId· string (uuid) — requiredrole·"read"|"comment"|"edit"|"full"— required
Response 200 — application/json
blockId· 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 — 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) — requiredpageId· string (uuid) — requiredsubjectType·"user"|"group"— requiredsubjectId· string (uuid) — required
Response 200 — application/json
blockId· 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 — 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) — requiredpageId· string (uuid) — required
Request body — application/json, required
restricted· boolean — requiredkeep· object[]subjectType·"user"|"group"— requiredsubjectId· string (uuid) — required
Response 200 — application/json
pageId· string (uuid) — requiredlevel·"read"|"comment"|"edit"|"full"| null — requiredsource·"acl"|"collection-member"|"collection-visibility"|"workspace-role"|"none"— requiredinheritedFrom· string (uuid) | null — requiredrestricted· boolean — requiredpermissions· object[] — requiredblockId· 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 — required