All endpoints are served by the Express application. Admin endpoints require authentication (cookie-based). Public endpoints are unauthenticated.
- Content-Type:
application/x-www-form-urlencodedfor form submissions,application/jsonfor API responses - Authentication: Cookie
pd_authcontaining a PocketBase JWT token - CSRF: State-changing requests (POST/PUT/DELETE) require a
_csrffield in the body orx-csrf-tokenheader, matching the signedpd_csrfcookie - Validation errors return HTTP 422 with field-level details
- Slugs must match
^[a-z0-9]+(?:-[a-z0-9]+)*$(lowercase alphanumeric with hyphens)
All errors follow a consistent envelope:
{
"error": {
"code": "VALIDATION_FAILED",
"message": "Human-readable description",
"details": [
{ "field": "slug", "code": "INVALID_FORMAT", "message": "Invalid slug format" }
],
"requestId": "uuid-v4"
}
}| HTTP Status | Error Code | Meaning |
|---|---|---|
| 422 | VALIDATION_FAILED |
Invalid input |
| 401 | UNAUTHORIZED |
Not logged in or token expired |
| 403 | FORBIDDEN |
Insufficient role/permissions |
| 403 | CSRF_INVALID |
CSRF token mismatch |
| 403 | IP_RESTRICTED |
Client IP not in allowlist |
| 404 | RESOURCE_NOT_FOUND |
Resource does not exist |
| 409 | CONFLICT |
Duplicate resource (e.g. slug already taken) |
| 422 | DOMAIN_ERROR |
Business rule violation |
| 429 | RATE_LIMITED |
Too many requests |
| 500 | INTERNAL_ERROR |
Unexpected server error |
| 502 | EXTERNAL_SERVICE_ERROR |
Upstream service failure |
Returns server health status and PocketBase connectivity.
Auth: None
Response (200 — healthy):
{
"status": "healthy",
"uptime_s": 3600,
"memory": {
"rss_mb": 85,
"heap_used_mb": 42,
"heap_total_mb": 64
}
}Response (503 — unhealthy):
{
"status": "unhealthy",
"uptime_s": 3600,
"checks": { "pocketbase": "unreachable" }
}Renders the owner registration form. Redirects to /auth/login if owner already exists.
Auth: None
Creates the initial owner account. Only available when no owner exists.
Rate limit: Auth limiter (10 requests / 15 min)
Body:
| Field | Type | Required | Constraints |
|---|---|---|---|
name |
string | Yes | 2–100 characters |
email |
string | Yes | Valid email, max 255 |
password |
string | Yes | 8–256 characters |
passwordConfirm |
string | Yes | Must match password |
_csrf |
string | Yes | CSRF token |
Success: Sets pd_auth cookie, redirects to /admin
Errors: 400 (validation), 409 (owner already exists)
Renders the login form. IP-restricted.
Auth: None
Authenticates a user with email and password.
Rate limit: Auth limiter (10 requests / 15 min)
IP restriction: Yes
Body:
| Field | Type | Required | Constraints |
|---|---|---|---|
email |
string | Yes | Valid email, max 255 |
password |
string | Yes | 8–256 characters |
_csrf |
string | Yes | CSRF token |
Success: Sets pd_auth cookie (httpOnly, secure in production, sameSite: strict, 7-day expiry), redirects to /admin
Errors: 400 (validation), 401 (invalid credentials)
Clears the auth session.
Auth: None (cookie is cleared regardless)
Body:
| Field | Type | Required |
|---|---|---|
_csrf |
string | Yes |
Success: Clears pd_auth cookie, redirects to /auth/login
All project routes require authentication and IP restriction. Routes under /admin/projects.
Lists all projects accessible to the current user, paginated.
Auth: Required
Roles: All
Query:
| Param | Type | Default | Constraints |
|---|---|---|---|
page |
integer | 1 | ≥ 1 |
Response: Renders project list page. Owners and admins see all projects; editors see only assigned projects.
Renders the project creation form.
Auth: Required
Roles: Admin, Owner
Creates a new project.
Auth: Required
Roles: Admin, Owner
CSRF: Yes
Body:
| Field | Type | Required | Constraints |
|---|---|---|---|
name |
string | Yes | 1–200 characters |
slug |
string | Yes | 1–120 characters, slug pattern |
description |
string | No | Max 500 characters |
visibility |
string | No | public or private (default: private) |
mode |
string | No | versioned or non_versioned (default: versioned) |
_csrf |
string | Yes | CSRF token |
Success: Redirects to /admin/projects/:projectId
Errors: 400 (validation), 409 (slug taken)
Shows project details and its versions.
For Non-Versioned projects, this route redirects to the pages list of the internal default version with Documents/FAQ/Troubleshooting tabs.
Auth: Required
Roles: Owner, Admin, Editor (with project access)
Renders the project edit form.
Auth: Required
Roles: Admin, Owner
Streams project content as a ZIP archive. Each version becomes a folder named by its slug. Documents export as {slug}.md, FAQ/Troubleshooting files export under knowledge-base/{section}/{slug}.md, and versioned projects include _CHANGELOG.md.
Auth: Required
Roles: Admin, Owner
Query:
| Param | Type | Required | Description |
|---|---|---|---|
downloadToken |
string | No | Client-generated opaque token (≤ 80 chars). When provided, the server sets a short-lived pd_download cookie so the browser know the download has started. |
Response: Content-Type: application/zip, Content-Disposition: attachment; filename="{slug}.zip"
Errors: 403 (insufficient role), 404 (project not found)
Updates project metadata.
Auth: Required
Roles: Admin, Owner
CSRF: Yes
Body: Same fields as create (all optional — partial update).
Note: mode is immutable after creation and is not accepted in update payloads.
Success: Redirects to /admin/projects/:projectId
Deletes a project and all its versions, content pages, and changelogs.
Auth: Required
Roles: Admin, Owner
CSRF: Yes
Success: Redirects to /admin/projects
All version routes require authentication and project access. Routes under /admin/projects/:projectId/versions.
Redirects to the parent project page.
Renders the version creation form, showing existing versions for cloning.
Auth: Required
Roles: Admin, Owner
Creates a new version for the project.
Auth: Required
Roles: Admin, Owner
CSRF: Yes
Body:
| Field | Type | Required | Constraints |
|---|---|---|---|
label |
string | Yes | 1–100 characters |
is_public |
boolean | No | Default: false |
clone_from |
string | No | Version ID to clone pages from (max 15 chars) |
_csrf |
string | Yes | CSRF token |
Success: Redirects to /admin/projects/:projectId
Renders the version edit form.
Auth: Required
Roles: All (with project access)
Updates a version.
Auth: Required
Roles: Admin, Editor, Owner
CSRF: Yes
Body:
| Field | Type | Required | Constraints |
|---|---|---|---|
label |
string | No | 1–100 characters |
is_public |
boolean | No | — |
order |
integer | No | ≥ 0 |
_csrf |
string | Yes | CSRF token |
Success: Redirects to project page
Deletes a version and all its content pages and changelog.
Auth: Required
Roles: Admin, Owner
CSRF: Yes
Success: Redirects to project page
All page routes require authentication and project access. Routes under /admin/projects/:projectId/versions/:versionId/pages.
Lists all pages for the version, displayed as a nested tree.
Auth: Required
Roles: All (with project access)
Renders the page editor for creating a new page.
Auth: Required
Roles: Admin, Owner
Creates a new page.
Auth: Required
Roles: Admin, Owner
CSRF: Yes
Body:
| Field | Type | Required | Constraints |
|---|---|---|---|
title |
string | Yes | 1–200 characters |
slug |
string | Yes | 1–120 characters, slug pattern |
content |
string | No | Max 500,000 characters |
parent |
string | No | Page ID (max 15 chars), empty string for root |
icon |
string | No | Max 50 characters |
_csrf |
string | Yes | CSRF token |
Success: Redirects to page editor
Bulk-imports Markdown files as pages. Relative folder paths are preserved by creating or reusing folder pages as parents before importing child pages. The server infers each page title from the first Markdown H1, falling back to the filename, and infers each slug from the filename or folder name.
Auth: Required
Roles: Admin, Owner
CSRF: Yes
Content-Type: application/json
Body:
| Field | Type | Required | Constraints |
|---|---|---|---|
files |
array | Yes | At least 1 item; limited by total content size |
files[].filename |
string | Yes | Max 500 characters, optional relative folders, .md or .markdown |
files[].content |
string | Yes | Max 500,000 characters per file; max 1,500,000 characters total |
Success (201):
{
"ok": true,
"importedCount": 2,
"pages": [
{ "id": "abc123def456789", "title": "Getting Started", "slug": "getting-started" },
{ "id": "def456abc123789", "title": "API Reference", "slug": "api-reference" }
],
"redirectUrl": "/admin/projects/proj123/versions/ver123/pages?success=2%20pages%20imported."
}Errors: 409 when an inferred page slug already exists or a folder slug exists under another parent; 422 for invalid files, duplicate imported slugs, unsupported extensions, path traversal, or oversized content.
Renders the page editor with existing content.
Auth: Required
Roles: All (with project access)
Updates an existing page.
Auth: Required
Roles: Admin, Editor, Owner
CSRF: Yes
Body: Same fields as create (all optional — partial update). Also accepts order (integer, ≥ 0).
Success: Redirects to page editor
Deletes a page.
Auth: Required
Roles: Admin, Owner
CSRF: Yes
Success: Redirects to pages list
Returns rendered Markdown as HTML (for live editor preview).
Auth: Required
Roles: All (with project access)
Body:
| Field | Type | Required |
|---|---|---|
content |
string | Yes |
Response (JSON):
{
"html": "<h1>Rendered Markdown</h1><p>Content here...</p>"
}Reorders pages and updates parent relationships.
Auth: Required
Roles: Admin, Editor, Owner
CSRF: Yes
Body:
| Field | Type | Required | Constraints |
|---|---|---|---|
pages |
array | Yes | Min 1 item |
pages[].id |
string | Yes | 1–15 characters |
pages[].order |
integer | Yes | ≥ 0 |
pages[].parent |
string | No | Page ID or empty string |
_csrf |
string | Yes | CSRF token |
Success: JSON { success: true }
Routes under /admin/projects/:projectId/versions/:versionId/changelog.
Renders the changelog editor for the version.
Auth: Required
Roles: All (with project access)
Creates or updates the changelog for the version (upsert).
Auth: Required
Roles: Admin, Owner
CSRF: Yes
Body:
| Field | Type | Required | Constraints |
|---|---|---|---|
content |
string | Yes | 1–500,000 characters |
_csrf |
string | Yes | CSRF token |
Success: Redirects to changelog editor
Returns rendered Markdown as HTML.
Auth: Required
Roles: All (with project access)
Body / Response: Same as page preview.
Routes under /admin/projects/:projectId/versions/:versionId/pages manage all fixed content sections: Documents (documents), Frequently Asked Questions (faq), and Troubleshooting (troubleshooting). The selected section is passed with the section query/body value and defaults to documents.
The existing page routes support all sections:
| Route | Purpose |
|---|---|
GET /admin/projects/:projectId/versions/:versionId/pages?section=faq |
Lists one section |
GET /admin/projects/:projectId/versions/:versionId/pages/new?section=faq |
Renders the editor for that section |
POST /admin/projects/:projectId/versions/:versionId/pages/new?section=faq |
Creates a page/article in that section |
POST /admin/projects/:projectId/versions/:versionId/pages/import?section=faq |
Imports Markdown into that section |
POST /admin/projects/:projectId/versions/:versionId/pages/reorder?section=faq |
Reorders pages/articles in that section |
GET /admin/projects/:projectId/versions/:versionId/pages/:pageId |
Edits an existing page/article |
POST /admin/projects/:projectId/versions/:versionId/pages/:pageId |
Updates an existing page/article |
POST /admin/projects/:projectId/versions/:versionId/pages/:pageId/delete |
Deletes an existing page/article |
slug values are unique per version + section. Parent pages must belong to the same version + section.
All user routes require Owner role. Routes under /admin/users.
Lists all users, paginated.
Auth: Required
Roles: Owner
Query:
| Param | Type | Default |
|---|---|---|
page |
integer | 1 |
Re-renders the users list with the specified user loaded for editing (modal).
Auth: Required
Roles: Owner
Query:
| Param | Type | Default |
|---|---|---|
page |
integer | 1 |
Creates a new user.
Auth: Required
Roles: Owner
CSRF: Yes
Body:
| Field | Type | Required | Constraints |
|---|---|---|---|
name |
string | Yes | 2–100 characters |
email |
string | Yes | Valid email, max 255 |
password |
string | Yes | 8–256 characters |
passwordConfirm |
string | Yes | Must match password |
role |
string | Yes | admin or editor |
_csrf |
string | Yes | CSRF token |
Success: Redirects to users list
Errors: 400 (validation), 409 (email taken)
Updates an existing user.
Auth: Required
Roles: Owner
CSRF: Yes
Body:
| Field | Type | Required | Constraints |
|---|---|---|---|
name |
string | Yes | 2–100 characters |
email |
string | Yes | Valid email, max 255 |
role |
string | Yes | admin or editor |
password |
string | No | 8–256 characters if provided |
passwordConfirm |
string | No | Must match password if provided |
_csrf |
string | Yes | CSRF token |
Deletes a user. Cannot delete yourself.
Auth: Required
Roles: Owner
CSRF: Yes
Success: Redirects to users list
Routes under /admin/settings.
Renders the settings page with current site settings and IP restriction configuration.
Auth: Required
Roles: Admin, Owner
Updates site settings and (optionally) IP restriction rules.
Auth: Required
Roles: Admin (site settings only), Owner (site settings + IP restriction)
CSRF: Yes
Body:
| Field | Type | Required | Constraints | Notes |
|---|---|---|---|---|
heroWord1 |
string | Yes | 1–50 characters | Home page hero word 1 |
heroWord2 |
string | Yes | 1–50 characters | Home page hero word 2 |
heroSubtitle |
string | No | Max 300 characters | Home page subtitle |
enabled |
string | No | enable or disable |
IP restriction toggle (Owner only) |
allowedIps |
string | No | Max 5,000 characters | Newline-separated IP allowlist (Owner only) |
_csrf |
string | Yes | CSRF token |
Success: Redirects to settings page
Home page showing all public projects.
Auth: None
Project landing page.
- Versioned project: redirects to the first public version's first document, then FAQ/Troubleshooting if no documents exist.
- Non-Versioned project: redirects to the first document using
/docs/:projectSlug/:pageSlug, then FAQ/Troubleshooting if no documents exist.
Auth: None
Non-Versioned document route. Renders a document directly without version slug.
If the project is versioned, routing falls through to versioned routes.
Auth: None
Version page. Redirects to the first document of the version, then FAQ/Troubleshooting if no documents exist, or to the changelog if no content exists.
Auth: None
Renders a documentation page with:
- Rendered Markdown content
- Sidebar navigation (page tree)
- Table of contents (extracted from headings)
- Previous/next page links
- Version switcher
Auth: None
Renders the version's changelog (if published).
Auth: None
Redirects Non-Versioned projects to the first article section with content, falling back to /docs/:projectSlug/_kb/faq.
Auth: None
Non-Versioned FAQ/Troubleshooting section page. section must be faq or troubleshooting.
Auth: None
Non-Versioned FAQ/Troubleshooting article page with rendered Markdown and table of contents.
Auth: None
Redirects versioned projects to the first article section with content, falling back to /docs/:projectSlug/:versionSlug/_kb/faq.
Auth: None
Versioned article section page. section must be faq or troubleshooting.
Auth: None
Versioned Knowledge Base article page with rendered Markdown and table of contents.
Auth: None
Full-text search across public Documents, FAQ, and Troubleshooting pages.
Auth: None
Query:
| Param | Type | Required | Constraints |
|---|---|---|---|
q |
string | Yes | Min 2 characters (after sanitization) |
project |
string | No | Project slug to scope search |
version |
string | No | Version ID to scope search |
Response (JSON):
{
"results": [
{
"id": "abc123def456789",
"type": "page",
"title": "Getting Started",
"slug": "getting-started",
"versionLabel": "v1.0",
"versionSlug": "v1-0",
"simpleMode": false,
"href": "/docs/example/v1-0/getting-started"
},
{
"id": "kb123def456789",
"type": "article",
"title": "Reset password",
"slug": "reset-password",
"section": "faq",
"sectionLabel": "Frequently Asked Questions",
"versionLabel": "v1.0",
"versionSlug": "v1-0",
"simpleMode": false,
"href": "/docs/example/v1-0/_kb/faq/reset-password"
}
]
}| Scope | Window | Max Requests | Applies To |
|---|---|---|---|
| General | 15 minutes | 100 (configurable) | All routes |
| Auth | 15 minutes | 10 (configurable) | /auth/*, /setup |
Rate-limited responses return HTTP 429:
{
"error": {
"code": "RATE_LIMITED",
"message": "Too many requests. Please try again later."
}
}