myDoc24 Knowledge Base — API docs
Programmatic surface for reading articles, publishing content from scripts or AI agents, and embedding KB content into other applications.
All endpoints live under /api/kb/. CORS is Access-Control-Allow-Origin: *
on read endpoints + widget.css + events so embedded widgets can
fetch cross-origin.
For the human-facing side of the product, see the user guide.
Authentication
Every /api/kb/* request needs auth. There are four flows; pick the
one that matches the caller:
| Flow | Header | Use when |
|---|---|---|
| Personal access token | Authorization: Bearer kbpat_<32 base62> |
Ad-hoc agent / script run by a signed-in user. Mint at /me/tokens. |
| Keycloak access token | Authorization: Bearer <JWT> |
Authenticated user driving the call from their browser session. |
| Service-account client | Authorization: Bearer <JWT> |
Unattended automation. KC client-credentials grant. |
| Guest API key | X-Kb-Guest-Key: <secret> |
Server-to-server reads from a paired host app. Read-only. |
| Anonymous | (no auth) | Public-read deployments only — KB_PUBLIC_READ_MODE=true. Read-only. |
Personal access tokens
Minted at /me/tokens. Scope is your current permissions,
snapshotted at issuance — a read-only user gets a read-only token.
The raw kbpat_… is shown once; the DB stores only the SHA-256 hash.
Revoke from the same page; revoked tokens 401 immediately.
Service-account flow (unattended automation)
The kb-import-agent client exists in the realm config. Get its
secret from the KC admin console → assign realm role kb:write:pages
to its service account → mint tokens with:
TOKEN=$(curl -sS -X POST \
-d "grant_type=client_credentials" \
-d "client_id=kb-import-agent" \
-d "client_secret=$KB_IMPORT_AGENT_SECRET" \
https://<kc-host>/realms/<realm>/protocol/openid-connect/token \
| jq -r .access_token)
Tokens last 5 minutes. Re-mint on demand — there's no refresh-token flow for client-credentials grants.
Guest API key
Shared secret in KB_GUEST_API_KEY, validated with
crypto.timingSafeEqual. Materialises a synthetic principal
{ sub: "kb-guest", roles: ["kb:read:pages","kb:read:assets"], groups: [] }
— read-only, rejected for any write/manage permission.
Distribute the key out-of-band to the paired host application. Rotate
from /admin/settings → Application → Guest API key. DB-backed,
30-second cache, no restart.
Read endpoints
All require kb:read:pages (or kb:read:assets for blobs), unless
KB_PUBLIC_READ_MODE=true is set.
| Endpoint | Returns |
|---|---|
GET /api/kb/folders |
Published tree of { id, name, slug, folders, articles } |
GET /api/kb/article?slug=… |
Single article body — only when status is PUBLISHED |
GET /api/kb/search?q=… |
FTS hits, ranked, max 50 |
GET /api/kb/blobs/[id] |
Binary stream with Cache-Control: public, max-age=31536000, immutable |
GET /api/kb/events |
SSE channel — pushes tree-changed on every publish / archive / folder mutation. Pass token as ?token=… (EventSource can't set headers) |
GET /api/kb/widget.css |
Stylesheet for embedded widgets. Always public, no auth |
Example:
curl -sS -H "Authorization: Bearer kbpat_…" \
"https://kb.example.com/api/kb/search?q=invoicing" | jq '.'
Write endpoint — import a Markdown article
POST /api/kb/admin/articles/import
Designed for AI agents authoring Markdown and having the server convert it to ProseMirror JSON + sanitised HTML.
Request:
curl -sS -X POST https://kb.example.com/api/kb/admin/articles/import \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{
"title": "How invoicing works",
"slug": "how-invoicing-works",
"folderSlug": "getting-started",
"keywords": "invoice, billing, customer",
"markdown": "# Heading\n\nBody text…",
"publish": true
}'
Response:
{ "id": 17, "slug": "how-invoicing-works", "status": "PUBLISHED" }
Behaviour:
- Upsert by slug. If
slugmatches an existing article, that row is updated in place. Otherwise a new article is created and the slug is derived from the title (with-2,-3collision suffixes). publish: trueruns the publish flow: status →PUBLISHED, paths/+/a/[slug]revalidated, draft row dropped, SSEtree-changedbroadcast.publish: false(default) writes tokb_article_draft— status stays DRAFT (or whatever it was) and the body is visible in the admin editor for human follow-up.- Markdown coverage: headings, paragraphs, lists, blockquotes,
fenced + inline code, bold / italic / links, hr, hard-breaks, plus
GFM-style callout blockquotes (
> [!NOTE|TIP|WARNING|DANGER]→<div class="kb-callout kb-callout-{variant}">). Tables, video, embed, and image-with-align remain editor-only. - Folder must exist and not be soft-deleted, otherwise
400. created_by/published_by/last_saved_byget the caller'ssubclaim — real KC user ID under PAT/KC flows, service-account UUID under client-credentials.
Permission matrix
| Endpoint | Required permission |
|---|---|
GET /api/kb/folders |
kb:read:pages |
GET /api/kb/article |
kb:read:pages |
GET /api/kb/search |
kb:read:pages |
GET /api/kb/blobs/[id] |
kb:read:assets |
GET /api/kb/events |
kb:read:pages (via ?token=…) |
GET /api/kb/widget.css |
public |
POST /api/kb/admin/articles |
kb:write:pages |
POST /api/kb/admin/articles/import |
kb:write:pages |
PUT /api/kb/admin/articles/[id]/draft |
kb:write:pages |
POST /api/kb/admin/articles/[id]/publish |
kb:manage:pages |
POST /api/kb/admin/articles/[id]/archive |
kb:manage:pages |
DELETE /api/kb/admin/articles/[id] |
kb:manage:pages |
POST /api/kb/admin/folders |
kb:manage:folders |
PUT /api/kb/admin/folders/[id] |
kb:manage:folders |
DELETE /api/kb/admin/folders/[id] |
kb:manage:folders |
POST /api/kb/admin/blobs |
kb:write:assets |
GET/POST /api/kb/admin/tokens |
kb:manage:groups |
DELETE /api/kb/admin/tokens/[id] |
kb:manage:groups |
GET /api/kb/admin/tokens/[id]/usage |
kb:manage:groups |
Permissions are Wiki.js-style. Roles map to KC groups
(kb-readers, kb-editors, kb-managers, kb-admins). Path-scoped
overrides live in kb_page_rule and apply only to session/Bearer
calls — PATs and the guest principal always carry groups: [],
which short-circuits the rule branch (rules cannot grant or revoke
PAT / guest access).
Errors
| Status | When |
|---|---|
400 |
Missing required field, unknown folderSlug, parse fail |
401 |
Missing / invalid / expired Bearer token |
403 |
Token valid but lacks the required permission |
404 |
Article id / slug not found |
A 401 mid-session under PAT or KC flow means the token expired — re-fetch it from the source. Under client-credentials, re-run the grant.
Where to go next
- Human-facing usage → user guide
- Full agent-API reference →
AGENT-API.mdin the repo - OpenAPI spec →
GET /api/openapi.yamlon each deployment