Opbox

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

MethodEndpointDescription
GET/api/documentsList all pages
POST/api/documentsCreate a page
GET/api/documents/treeGet page tree (no content)
PATCH/api/documents/treeReorder or reparent pages in the tree
GET/api/documents/doc/:idGet page with content
PUT/api/documents/doc/:idUpdate page content or metadata
DELETE/api/documents/doc/:idDelete a page
GET/api/documents/doc/:id/embedGet embeddable HTML or JSON metadata
GET/api/documents/doc/:id/exportExport a single page as markdown, docx, doc, or pdf
GET/api/documents/doc/:id/export-allExport page and all descendants as a single combined file
GET/api/documents/doc/:id/sharingGet sharing settings and collaborators
PUT/api/documents/doc/:id/sharingUpdate sharing settings
POST/api/documents/doc/:id/sharingGenerate a public embed link
GET/api/documents/doc/:id/versionsList version history
GET/api/documents/doc/:id/versions/:versionIdGet a specific version's content
POST/api/documents/doc/:id/versions/:versionIdRestore a previous version
PATCH/api/documents/doc/:id/moveMove (reparent and/or reorder) a page
GET/api/documents/doc/:id/commentsList threaded comments
POST/api/documents/doc/:id/commentsCreate a comment or reply
PATCH/api/documents/doc/:id/comments/:commentIdUpdate or resolve a comment
DELETE/api/documents/doc/:id/comments/:commentIdDelete a comment
POST/api/documents/doc/:id/reactionsToggle a reaction on a comment
GET/api/documents/doc/:id/labelsList labels on a page
POST/api/documents/doc/:id/labelsAdd a label to a page
DELETE/api/documents/doc/:id/labelsRemove a label from a page
GET/api/documents/labelsList all workspace-wide labels (autocomplete)
GET/api/documents/doc/:id/signoffsList signoffs for a page
POST/api/documents/doc/:id/signoffsAdd a signatory to a page
PATCH/api/documents/doc/:id/signoffs/:signoffIdSign or reject a signoff
DELETE/api/documents/doc/:id/signoffs/:signoffIdRemove a signatory from a page
GET/api/documents/doc/:id/watchGet watch status and watcher count
POST/api/documents/doc/:id/watchToggle watch on a page
GET/api/documents/doc/:id/viewsGet view analytics
POST/api/documents/doc/:id/viewsRecord a page view (throttled)
GET/api/documents/favouritesList user's favourited pages
POST/api/documents/favouritesToggle favourite on a page
POST/api/documents/doc/:id/archiveArchive (soft-delete) a page
GET/api/documents/doc/:id/access-requestList pending access requests
POST/api/documents/doc/:id/access-requestRequest access to a restricted page
POST/api/documents/doc/:id/access-request/decisionApprove or deny an access request
GET/api/documents/templatesList available page templates
GET/api/documents/public-embed/:tokenGet 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

FieldTypeRequiredDescription
namestringYesPage title (1-255 chars)
parentIdstringNoParent page ID for nesting
iconstringNoLucide icon name
iconColorstringNoIcon color (hex value)
contentobjectNoInitial editor content (structured JSON)
isFolderbooleanNoCreate 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.

FieldTypeDescription
keystringUnique identifier for the property
labelstringDisplay name (1-100 chars)
typestringtext, date, link, or user
valuestringProperty 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

ParameterTypeDescription
modestringhtml (default) returns standalone HTML page; json returns metadata with content and embed URLs
publicstringSet to 1 to generate a signed public embed token (requires manage permission)
expiresInMinutesnumberToken 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

FormatMIME TypeExtension
markdown / mdtext/markdown.md
docx / wordapplication/vnd.openxmlformats-officedocument.docx
docapplication/msword.doc
pdfapplication/pdf.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

FormatOutput
markdown / mdSingle .md file with --- separators between pages
docx / wordSingle .docx with horizontal rules between pages
pdfSingle .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.

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

FieldTypeRequiredDescription
parentIdstring | nullYesNew parent page ID, or null for root level
sortOrdernumberYesNumeric 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

StatusErrorCause
400A document cannot be its own parentparentId equals the page ID
400Invalid parent document (cycle detected)Moving a page under one of its own descendants
403ForbiddenUser lacks edit permission on the page
409Document is archivedAttempting 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.

FieldTypeDescription
fromnumberStart position in the ProseMirror document
tonumberEnd position in the ProseMirror document
textstringSelected 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.

FieldTypeRequiredDescription
userIdstringYesCUID 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" }
FieldTypeRequiredDescription
action"sign" | "reject"YesSign off or reject the document
commentstringNoOptional 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

TypeRecipientTrigger
DOCUMENT_SIGNOFF_REQUESTEDNext signatoryPrevious signatory signs, or first signatory added
DOCUMENT_SIGNOFF_SIGNEDSignoff creatorA signatory signs
DOCUMENT_SIGNOFF_REJECTEDSignoff creatorA signatory rejects
DOCUMENT_SIGNOFF_COMPLETEDSignoff creatorAll 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"
}