Knowledge Base API
Manage rich-text knowledge base pages with a hierarchical tree structure. Pages support structured JSON content, icons, and parent-child relationships.
Pages are stored using the system DOCUMENTS table.
Editor UX note: content width is adjusted via Confluence-style hover drag handles, and both Comments and AI sidebar composers are bottom-anchored. The editor supports two entity reference insertion methods: / slash commands insert block-level entity embeds (4 display modes), while @ inserts inline entity mentions (chip or link mode) within paragraph text. Both use the /api/search endpoint for entity lookup. These are UI-level behaviors and do not change API contracts.
Endpoints
| Method | Endpoint | Description |
|---|---|---|
GET | /api/documents | List all pages |
POST | /api/documents | Create a page |
GET | /api/documents/tree | Get page tree (no content) |
PATCH | /api/documents/tree | Reorder or reparent pages in the tree |
GET | /api/documents/doc/:id | Get page with content |
PUT | /api/documents/doc/:id | Update page content or metadata |
DELETE | /api/documents/doc/:id | Delete a page |
GET | /api/documents/doc/:id/embed | Get embeddable HTML or JSON metadata |
GET | /api/documents/doc/:id/export | Export a single page as markdown, docx, doc, or pdf |
GET | /api/documents/doc/:id/export-all | Export page and all descendants as a single combined file |
GET | /api/documents/doc/:id/sharing | Get sharing settings and collaborators |
PUT | /api/documents/doc/:id/sharing | Update sharing settings |
POST | /api/documents/doc/:id/sharing | Generate a public embed link |
GET | /api/documents/doc/:id/versions | List version history |
GET | /api/documents/doc/:id/versions/:versionId | Get a specific version's content |
POST | /api/documents/doc/:id/versions/:versionId | Restore a previous version |
PATCH | /api/documents/doc/:id/move | Move (reparent and/or reorder) a page |
GET | /api/documents/doc/:id/comments | List threaded comments |
POST | /api/documents/doc/:id/comments | Create a comment or reply |
PATCH | /api/documents/doc/:id/comments/:commentId | Update or resolve a comment |
DELETE | /api/documents/doc/:id/comments/:commentId | Delete a comment |
POST | /api/documents/doc/:id/reactions | Toggle a reaction on a comment |
GET | /api/documents/doc/:id/labels | List labels on a page |
POST | /api/documents/doc/:id/labels | Add a label to a page |
DELETE | /api/documents/doc/:id/labels | Remove a label from a page |
GET | /api/documents/labels | List all workspace-wide labels (autocomplete) |
GET | /api/documents/doc/:id/signoffs | List signoffs for a page |
POST | /api/documents/doc/:id/signoffs | Add a signatory to a page |
PATCH | /api/documents/doc/:id/signoffs/:signoffId | Sign or reject a signoff |
DELETE | /api/documents/doc/:id/signoffs/:signoffId | Remove a signatory from a page |
GET | /api/documents/doc/:id/watch | Get watch status and watcher count |
POST | /api/documents/doc/:id/watch | Toggle watch on a page |
GET | /api/documents/doc/:id/views | Get view analytics |
POST | /api/documents/doc/:id/views | Record a page view (throttled) |
GET | /api/documents/favourites | List user's favourited pages |
POST | /api/documents/favourites | Toggle favourite on a page |
POST | /api/documents/doc/:id/archive | Archive (soft-delete) a page |
GET | /api/documents/doc/:id/access-request | List pending access requests |
POST | /api/documents/doc/:id/access-request | Request access to a restricted page |
POST | /api/documents/doc/:id/access-request/decision | Approve or deny an access request |
GET | /api/documents/templates | List available page templates |
GET | /api/documents/public-embed/:token | Get public embed content via JWT token |
List Pages
Returns all knowledge base pages for your workspace. Use the tree endpoint for a lightweight hierarchy view.
GET /api/documents
Response
[
{
"id": "cm...",
"name": "Getting Started Guide",
"parentId": null,
"icon": "BookOpen",
"iconColor": "#3b82f6",
"updatedAt": "2026-02-10T14:30:00.000Z",
"createdAt": "2026-01-20T09:00:00.000Z"
}
]
Page Tree
Returns a lightweight tree structure without content fields, ideal for building navigation sidebars. Each item includes a sortOrder field for drag-and-drop ordering.
GET /api/documents/tree
Response
[
{
"id": "cm...",
"name": "Getting Started",
"parentId": null,
"icon": "BookOpen",
"iconColor": "#3b82f6",
"isFolder": false,
"sortOrder": 1000,
"updatedAt": "2026-02-18T14:30:00.000Z"
}
]
Get a Page
Returns a single page with its full content (structured JSON or legacy markdown). Includes a createdBy object with the creator's user info, or null for system-created pages (e.g. transcript sync) or when the creator has been deleted.
GET /api/documents/doc/:id
Response
{
"id": "cm...",
"name": "Getting Started Guide",
"parentId": null,
"content": {
"type": "doc",
"content": [
{ "type": "heading", "attrs": { "level": 1 }, "content": [] },
{ "type": "paragraph", "content": [] }
]
},
"icon": "BookOpen",
"iconColor": "#3b82f6",
"metadata": {
"properties": [
{ "key": "author_1708...", "label": "Author", "type": "text", "value": "Jane Doe" },
{ "key": "review_date_1708...", "label": "Review Date", "type": "date", "value": "2026-06-01" }
]
},
"version": 4,
"createdBy": {
"id": "cm...",
"name": "Jane Doe",
"email": "jane@example.com",
"image": null
},
"updatedAt": "2026-02-10T14:30:00.000Z",
"createdAt": "2026-01-20T09:00:00.000Z"
}
Create a Page
Creates a new empty page. Set parentId to nest it inside another page.
POST /api/documents
Content-Type: application/json
{
"name": "Project Notes",
"parentId": "cm...",
"icon": "FileText",
"iconColor": "#8b5cf6"
}
Body Parameters
| Field | Type | Required | Description |
|---|---|---|---|
name | string | Yes | Page title (1-255 chars) |
parentId | string | No | Parent page ID for nesting |
icon | string | No | Lucide icon name |
iconColor | string | No | Icon color (hex value) |
content | object | No | Initial editor content (structured JSON) |
isFolder | boolean | No | Create as folder node (default: false) |
Update a Page
Updates page content, name, icon, parent, archive state, or custom metadata properties. Content changes increment the version number and create a version snapshot. Set archived: true to archive a page (archived pages cannot be edited). Supports autosave from the editor.
PUT /api/documents/doc/:id
Content-Type: application/json
{
"name": "Updated Title",
"content": { "type": "doc", "content": [] },
"parentId": "cm...",
"icon": "Star",
"iconColor": "#f59e0b",
"metadata": {
"properties": [
{ "key": "author_1708...", "label": "Author", "type": "text", "value": "Jane Doe" }
]
}
}
Metadata Properties
Custom key-value properties attached to the page. Max 20 properties per page. Set to null to clear all properties.
| Field | Type | Description |
|---|---|---|
key | string | Unique identifier for the property |
label | string | Display name (1-100 chars) |
type | string | text, date, link, or user |
value | string | Property value (max 2000 chars) |
Delete a Page
Permanently deletes a page. Child pages are reparented to the deleted page's parent. Requires ADMIN or OWNER role.
DELETE /api/documents/doc/:id
Note: Child pages are not deleted - they are moved up to the parent level.
Embed a Page
Returns page content as embeddable HTML or JSON metadata with embed/iframe URLs. Used by the KB page embed field type in forms and for public sharing.
GET /api/documents/doc/:id/embed?mode=json
Query Parameters
| Parameter | Type | Description |
|---|---|---|
mode | string | html (default) returns standalone HTML page; json returns metadata with content and embed URLs |
public | string | Set to 1 to generate a signed public embed token (requires manage permission) |
expiresInMinutes | number | Token expiry in minutes (default: 1440, max: 43200) |
JSON Mode Response
{
"id": "cm...",
"title": "Getting Started Guide",
"html": "<h1>Getting Started</h1><p>...</p>",
"embedUrl": "/api/documents/doc/cm.../embed",
"iframeSrc": "<iframe src=\"/api/documents/doc/cm.../embed\" ...></iframe>",
"publicEmbedUrl": "https://app.example.com/api/documents/public-embed/token...",
"publicIframe": "<iframe src=\"https://app.example.com/api/documents/public-embed/token...\" ...></iframe>",
"publicExpiresAt": "2026-02-14T14:30:00.000Z"
}
Export a Page
Exports a page as a downloadable file in the specified format. Returns the file with appropriate Content-Disposition headers for browser download.
GET /api/documents/doc/:id/export?format=markdown
Supported Formats
| Format | MIME Type | Extension |
|---|---|---|
markdown / md | text/markdown | .md |
docx / word | application/vnd.openxmlformats-officedocument | .docx |
doc | application/msword | .doc |
pdf | application/pdf |
Export All Pages (Combined)
Exports a page and all its descendant pages as a single combined file. Pages are concatenated in depth-first tree order, sorted alphabetically at each level. Only pages the user has view permission for are included.
GET /api/documents/doc/:id/export-all?format=markdown
Supported Formats
| Format | Output |
|---|---|
markdown / md | Single .md file with --- separators between pages |
docx / word | Single .docx with horizontal rules between pages |
pdf | Single .pdf with --- separators between pages |
For exporting each page as a separate file in a zip archive, use the single-page /export endpoint per page. The UI handles zip generation client-side with progress tracking.
Page Sharing
Manage page visibility, collaborator access, and generate public embed links. Pages support workspace (all org members) or restricted (specific collaborators) visibility.
Get Sharing Settings
GET /api/documents/doc/:id/sharing
{
"canManage": true,
"sharing": {
"visibility": "workspace",
"workspaceRole": "viewer",
"collaborators": [
{ "userId": "cm...", "role": "editor" }
],
"grants": [
{ "targetType": "oversight_workspace", "targetId": "cm_ws_overseer", "accessLevel": "viewer" }
]
},
"members": [
{
"userId": "cm...",
"name": "Jane Doe",
"email": "jane@example.com",
"organizationRole": "ADMIN",
"collaboratorRole": "editor"
}
],
"overseerWorkspaces": [
{
"workspaceId": "cm_ws_overseer",
"workspaceName": "Regulator Workspace",
"members": [
{ "userId": "cm...", "name": "Inspector", "email": "inspect@example.com", "role": "ADMIN" }
]
}
]
}
Update Sharing Settings
PUT /api/documents/doc/:id/sharing
Content-Type: application/json
{
"visibility": "restricted",
"workspaceRole": "viewer",
"collaborators": [
{ "userId": "cm...", "role": "editor" },
{ "userId": "cm...", "role": "viewer" }
],
"grants": [
{ "targetType": "workspace_role", "targetId": "ADMIN", "accessLevel": "editor" },
{ "targetType": "oversight_workspace", "targetId": "cm_ws_overseer", "accessLevel": "viewer" }
],
"applyToChildren": true
}
Requires manage permission. All user IDs must be members of your workspace. The optional grants array supports three target types: user (specific user), workspace_role (role-based grant), and oversight_workspace (all members of an overseer workspace). Oversight workspace grants are validated against active oversight relationships; invalid refs are silently dropped.
Set applyToChildren to true to recursively propagate the sharing settings to all descendant pages and folders. This uses a BFS traversal and applies the same visibility, workspace role, and collaborator list to every child in a single batch transaction. Useful for setting folder-wide access policies.
Generate Public Embed Link
POST /api/documents/doc/:id/sharing
Content-Type: application/json
{
"expiresInMinutes": 10080
}
{
"publicEmbedUrl": "https://app.example.com/api/documents/public-embed/token...",
"publicIframe": "<iframe src=\"...\" ...></iframe>",
"publicExpiresAt": "2026-02-20T14:30:00.000Z"
}
Creates a signed JWT token for unauthenticated access. Default expiry is 7 days (10080 minutes). Requires manage permission.
Version History
Pages automatically track version history. A snapshot is saved when content changes (throttled to max 1 per 5 minutes to avoid excessive snapshots from autosave). Version history allows viewing past versions and restoring content.
List Versions
GET /api/documents/doc/:id/versions
{
"versions": [
{
"id": "cm...",
"version": 3,
"title": "Getting Started Guide",
"createdAt": "2026-02-18T10:00:00.000Z",
"createdBy": {
"id": "cm...",
"name": "Jane Doe",
"email": "jane@example.com",
"image": null
}
}
]
}
Get a Specific Version
GET /api/documents/doc/:id/versions/:versionId
{
"id": "cm...",
"version": 3,
"title": "Getting Started Guide",
"content": { "type": "doc", "content": [] },
"createdAt": "2026-02-18T10:00:00.000Z",
"createdBy": { "id": "cm...", "name": "Jane Doe", "email": "jane@example.com", "image": null }
}
Restore a Version
POST /api/documents/doc/:id/versions/:versionId
Restores the page content to the specified version. The current content is automatically saved as a new snapshot before restoring. Requires edit permission. Returns the updated page object.
Note: Restoring a version only replaces the page content - metadata, icon, and other fields are not affected.
Move a Page
Reparent and/or reorder a page within the tree. Used by the sidebar drag-and-drop interface. Validates against cycles (a page cannot become a descendant of itself).
PATCH /api/documents/doc/:id/move
Content-Type: application/json
{
"parentId": "cm...",
"sortOrder": 2000
}
Body Parameters
| Field | Type | Required | Description |
|---|---|---|---|
parentId | string | null | Yes | New parent page ID, or null for root level |
sortOrder | number | Yes | Numeric sort position among siblings (lower values appear first) |
Response
Returns the full updated page object (same shape as GET /api/documents/doc/:id).
Error Responses
| Status | Error | Cause |
|---|---|---|
400 | A document cannot be its own parent | parentId equals the page ID |
400 | Invalid parent document (cycle detected) | Moving a page under one of its own descendants |
403 | Forbidden | User lacks edit permission on the page |
409 | Document is archived | Attempting to move an archived page |
Page Comments
Threaded comments on knowledge base pages. Supports replies, resolve/unresolve, and emoji reactions. Only the author can edit a comment's content, but any org member can resolve it.
List Comments
GET /api/documents/doc/:id/comments
{
"comments": [
{
"id": "cm...",
"content": "This section needs a review.",
"parentId": null,
"resolved": false,
"resolvedBy": null,
"inlineAnchor": {
"from": 42,
"to": 78,
"text": "compliance review process"
},
"createdAt": "2026-02-18T10:00:00.000Z",
"user": { "id": "cm...", "name": "Jane", "email": "jane@co.com", "image": null },
"reactions": [
{ "id": "cm...", "emoji": "thumbs-up", "userId": "cm..." }
],
"replies": [
{
"id": "cm...",
"content": "I'll handle it.",
"parentId": "cm...",
"createdAt": "2026-02-18T10:05:00.000Z",
"user": { "id": "cm...", "name": "Bob", "email": "bob@co.com", "image": null },
"reactions": []
}
]
}
]
}
Create a Comment
POST /api/documents/doc/:id/comments
Content-Type: application/json
{
"content": "Looks good!",
"parentId": "cm...",
"inlineAnchor": {
"from": 42,
"to": 78,
"text": "compliance review process"
}
}
parentId is optional (set to reply to a comment). inlineAnchor is optional (anchors the comment to selected text).
Inline Text-Anchored Comments
Comments can optionally be anchored to a specific text selection in the page content (Confluence-style inline comments). When inlineAnchor is provided, from and to are ProseMirror document positions marking the highlighted range, and text is the selected text snippet (max 500 characters). The anchor is returned on the comment object in GET responses when present.
| Field | Type | Description |
|---|---|---|
from | number | Start position in the ProseMirror document |
to | number | End position in the ProseMirror document |
text | string | Selected text snippet (max 500 chars) |
Update / Resolve a Comment
PATCH /api/documents/doc/:id/comments/:commentId
Content-Type: application/json
{ "content": "Updated text" }
{ "resolved": true }
Edit content is author-only; resolve/unresolve is available to any org member.
Delete a Comment
DELETE /api/documents/doc/:id/comments/:commentId
Author or ADMIN/OWNER can delete. Cascading delete removes all replies and emoji responses.
Emoji Responses
Toggle an emoji response on a comment. If the user has already responded with the same emoji, it is removed. Each user can add one response of each emoji type per comment.
POST /api/documents/doc/:id/reactions
Content-Type: application/json
{
"commentId": "cm...",
"emoji": "thumbs-up"
}
{ "action": "added" }
Returns { "action": "removed" } if the emoji response was already present.
Labels
Tag pages with labels for categorisation. Labels are lowercased and trimmed automatically. Adding a label that already exists on the page is a no-op (idempotent upsert).
List Labels on a Page
GET /api/documents/doc/:id/labels
{
"labels": [
{ "id": "cm...", "label": "onboarding", "createdAt": "2026-02-18T..." },
{ "id": "cm...", "label": "policy", "createdAt": "2026-02-17T..." }
]
}
Add a Label
POST /api/documents/doc/:id/labels
Content-Type: application/json
{ "label": "onboarding" }
Remove a Label
DELETE /api/documents/doc/:id/labels?label=onboarding
Workspace-wide Label Suggestions
Returns all unique labels used across the workspace with usage counts, sorted by popularity. Used for autocomplete in the label picker.
GET /api/documents/labels
{
"labels": [
{ "label": "policy", "count": 12 },
{ "label": "onboarding", "count": 8 },
{ "label": "draft", "count": 3 }
]
}
Signoffs
Sequential document signoff workflow. Add designated signatories who must review and sign off on a page in order. Supports compliance use cases (policy approvals, SOC 2 document reviews) and general document governance. Signatories are notified sequentially - each person is notified only when the previous signatory has signed.
List Signoffs
GET /api/documents/doc/:id/signoffs
{
"signoffs": [
{
"id": "cm...",
"documentId": "cm...",
"userId": "cm...",
"order": 0,
"status": "SIGNED",
"comment": "Looks good",
"signedAt": "2026-03-01T10:00:00Z",
"createdById": "cm...",
"createdAt": "2026-02-28T09:00:00Z",
"user": { "id": "cm...", "name": "Jane Smith", "email": "jane@example.com", "image": null },
"createdBy": { "id": "cm...", "name": "Will Admin", "email": "will@example.com" }
},
{
"id": "cm...",
"documentId": "cm...",
"userId": "cm...",
"order": 1,
"status": "PENDING",
"comment": null,
"signedAt": null,
"createdById": "cm...",
"createdAt": "2026-02-28T09:01:00Z",
"user": { "id": "cm...", "name": "John Doe", "email": "john@example.com", "image": null },
"createdBy": { "id": "cm...", "name": "Will Admin", "email": "will@example.com" }
}
],
"currentUserId": "cm...",
"currentUserRole": "ADMIN"
}
Add a Signatory
POST /api/documents/doc/:id/signoffs
Content-Type: application/json
{ "userId": "cm..." }
Returns 201 Created with the created signoff object, or 409 Conflict if the user is already a signatory.
| Field | Type | Required | Description |
|---|---|---|---|
userId | string | Yes | CUID of a workspace member to add as signatory |
Sequential flow: The first signatory (order 0) is notified immediately. Subsequent signatories are notified only when the previous signatory signs. If the first signatory is the only one, they are notified on creation.
Sign or Reject
PATCH /api/documents/doc/:id/signoffs/:signoffId
Content-Type: application/json
{ "action": "sign", "comment": "Approved - looks good" }
{ "action": "reject", "comment": "Needs revision in section 3" }
| Field | Type | Required | Description |
|---|---|---|---|
action | "sign" | "reject" | Yes | Sign off or reject the document |
comment | string | No | Optional comment (max 1000 chars) |
Constraints: Only the assigned signatory can sign/reject. Only the current active signoff (lowest-order PENDING) can be actioned. Attempting to sign out of order returns 400.
Remove a Signatory
DELETE /api/documents/doc/:id/signoffs/:signoffId
Only the person who added signoffs or ADMIN/OWNER can remove. Remaining signoffs are automatically re-ordered.
Notification Types
| Type | Recipient | Trigger |
|---|---|---|
DOCUMENT_SIGNOFF_REQUESTED | Next signatory | Previous signatory signs, or first signatory added |
DOCUMENT_SIGNOFF_SIGNED | Signoff creator | A signatory signs |
DOCUMENT_SIGNOFF_REJECTED | Signoff creator | A signatory rejects |
DOCUMENT_SIGNOFF_COMPLETED | Signoff creator | All signatories have signed |
Watch / Follow
Follow pages to receive notifications about changes. The POST endpoint toggles the watch state - calling it when already watching removes the watch.
Get Watch Status
GET /api/documents/doc/:id/watch
{ "watching": true, "watcherCount": 5 }
Toggle Watch
POST /api/documents/doc/:id/watch
{ "watching": true }
Returns { "watching": false } if un-watched.
Page Views & Analytics
Track who has viewed a page. View recording is throttled to max 1 per user per 5 minutes per page to avoid inflated counts from autosave refreshes. The GET endpoint returns total views, unique viewer count, recent viewers, a per-user view count leaderboard (top 20), and daily views over the last 30 days.
Get View Analytics
GET /api/documents/doc/:id/views
{
"totalViews": 47,
"uniqueViewers": 12,
"recentViewers": [
{
"viewedAt": "2026-02-18T14:00:00.000Z",
"user": { "id": "cm...", "name": "Jane", "email": "jane@co.com", "image": null }
}
],
"leaderboard": [
{ "userId": "cm...", "name": "Jane", "email": "jane@co.com", "image": null, "views": 15 },
{ "userId": "cm...", "name": "Will", "email": "will@co.com", "image": "/uploads/...", "views": 8 }
],
"viewsOverTime": [
{ "date": "2026-01-20", "views": 3 },
{ "date": "2026-01-21", "views": 7 }
]
}
Record a View
POST /api/documents/doc/:id/views
{ "recorded": true }
Returns { "recorded": false } if throttled (viewed within last 5 min).
Note: Views are recorded automatically when a page is opened in the editor. You do not need to call this endpoint manually.
Favourites
Star pages for quick access. Each user has their own list of favourites. The POST endpoint toggles the favourite state.
List Favourites
GET /api/documents/favourites
{
"favourites": [
{ "documentId": "cm...", "createdAt": "2026-02-18T10:00:00.000Z" },
{ "documentId": "cm...", "createdAt": "2026-02-17T09:00:00.000Z" }
]
}
Toggle Favourite
POST /api/documents/favourites
Content-Type: application/json
{ "documentId": "cm..." }
{ "favourited": true }
Returns { "favourited": false } if un-favourited.
Archive a Page
Soft-deletes a page by setting archived: true. Archived pages are excluded from tree views, search, and stats but can be restored. The DELETE endpoint on /api/documents/doc/:id permanently removes the record.
POST /api/documents/doc/:id/archive
{ "archived": true }
Reorder Pages
Move a page within the tree - change its parent (reparent) and/or reorder it among siblings. Used by the drag-and-drop sidebar.
PATCH /api/documents/tree
Content-Type: application/json
{
"documentId": "cm...",
"parentId": "cm...",
"sortOrder": 2000
}
parentId is the new parent (null for root). sortOrder is the sort position among siblings.
Access Requests
When a page has restricted sharing, non-collaborators can request access. Page owners and editors can approve or deny requests.
POST /api/documents/doc/:id/access-request
{ "id": "cm...", "status": "PENDING" }
List Pending Requests
GET /api/documents/doc/:id/access-request
Editors and owners only.
Approve or Deny
POST /api/documents/doc/:id/access-request/decision
Content-Type: application/json
{
"requestId": "cm...",
"decision": "approve"
}
decision accepts approve or deny.
Page Templates
List available page templates with pre-built content structures (e.g. risk assessment).
GET /api/documents/templates
[
{
"key": "risk-assessment",
"name": "Risk Assessment",
"description": "Structured risk assessment document",
"icon": "Shield"
}
]
Public Embed
Access page content via a JWT-signed public embed token. Tokens are generated via POST /api/documents/doc/:id/sharing and have configurable expiry (5 min to 30 days).
GET /api/documents/public-embed/:token
{
"id": "cm...",
"name": "Getting Started",
"content": {},
"icon": "BookOpen"
}