6.3 KiB
API contract
The API is built for the web client and trusted scoped automation. A future separate MCP server consumes the same API.
The draft OpenAPI document is in docs/api/openapi.yaml. Implementation and contract
tests must keep it current.
Conventions
- Base path:
/api/v1 - JSON: UTF-8, camelCase
- IDs: opaque UUID strings
- Times: RFC 3339 UTC
- Errors:
application/problem+json(RFC 7807) - Pagination: opaque cursor
- Concurrency: strong ETag plus
If-Match - Correlation: accept/return
X-Correlation-ID - Idempotency:
Idempotency-Keyfor retryable create/append endpoints - Locale: server returns invariant values plus localized UI copy remains client-side
Authentication
Browser
Secure cookie session plus anti-forgery token for mutations.
Service account
Authorization: Bearer ikh_<public-id>_<secret>
The full token is shown once. The server stores:
- service account ID;
- token public ID/prefix;
- SHA-256 or keyed verifier of a cryptographically random 256-bit secret;
- created/last-used/expiry/revoked timestamps;
- scopes and optional allowed network policy.
For controlled curation, pages:read authorizes page detail, roots, children,
revisions and backlinks; pages:write authorizes create, update, move and reversible
trash. Browser callers retain their role-based cookie policy. Update and move accept an
optional Idempotency-Key in addition to mandatory If-Match, so bounded automation
can retry without duplicating a successful mutation.
The API never returns or logs the secret.
Core scopes
knowledge:read
knowledge:search
pages:read
pages:write
projects:read
projects:write
project-updates:append
decisions:read
decisions:write
runbooks:read
attachments:read
attachments:write
imports:read
imports:execute
audit:read
system:read
Scopes never bypass workspace/content authorization.
Resource endpoints
System
GET /system/infoGET /healthis outside/api/v1and contains no private details
Search
GET /search?q=&types=&tags=&projectId=&cursor=&limit=
Returns permission-filtered hits and facets. No inaccessible counts or snippets.
Pages
GET /pages/{id}GET /pages/{id}/childrenGET /pages/{id}/relationsGET /pages/{id}/revisionsGET /pages/{id}/revisions/{revision}POST /pagesPUT /pages/{id}withIf-MatchPOST /pages/{id}/movewithIf-MatchPOST /pages/{id}/trashwithIf-MatchPOST /pages/{id}/restorePOST /pages/{id}/restore-revision
Page responses include:
- stable ID;
- current title/slug/breadcrumb;
- raw Markdown when scope permits;
- typed metadata;
- current revision;
- ETag;
- stable source citation object.
Projects
GET /projectsGET /projects/{id}POST /projectsPUT /projects/{id}withIf-MatchGET /projects/{id}/updatesPOST /projects/{id}/updatesGET /projects/{id}/decisionsGET /projects/{id}/context
Project context
GET /projects/{id}/context returns a bounded context bundle:
{
"project": {
"id": "...",
"name": "Ludarium",
"status": "active",
"summary": "...",
"currentStateMarkdown": "...",
"updatedAt": "..."
},
"recentUpdates": [],
"activeMilestones": [],
"decisions": [],
"runbooks": [],
"documents": [],
"knownIssues": [],
"citations": [],
"truncation": {
"truncated": false,
"omittedCounts": {}
}
}
Requirements:
- default bounded item counts;
- explicit
updatedAtand source IDs; - deterministic ordering;
- permission filtering before assembly;
- maximum serialized size;
- no secrets, raw attachment bodies or private filesystem paths;
- truncation metadata rather than silent omission.
RAG publication manifest
GET /rag/publication-manifest?space=&cursor=&limit= — a single, deliberately narrow
read-only endpoint for RAGcore's n8n sync, reusing the existing pages:read scope (no
new scope). There is no general GET /pages enumeration in this API — only
/pages/roots plus a tree walk — so this is the one reliable way an external consumer
discovers the full curated publication set. Selection and shape are entirely driven by
the rag:publish/rag:space:/rag:allow-deprecated/locale: tag convention; see
docs/32-rag-publication-convention.md for the full convention and
docs/api/openapi.yaml's /rag/publication-manifest path for the exact response shape.
Attachments
The implemented v1 routes accept either the normal cookie role or the matching
least-privilege service-account scope: attachments:read for listing/content and
attachments:write for upload. pages:write also authorizes move and the reversible
POST /pages/{id}/trash operation so an importer can roll back only the pages from
its own recorded batch; it does not grant permanent purge access.
POST /attachmentsstreaming multipartGET /attachments/{id}/metadataGET /attachments/{id}/contentDELETE /attachments/{id}logical delete
Range requests may be supported after authorization. Unsafe types download as attachment.
Imports
Administrative API only:
- create discovery/preflight;
- retrieve plan/report;
- execute accepted plan;
- validate;
- rollback.
Source paths are configured server-side; clients cannot supply arbitrary unrestricted host paths.
Service accounts
Administrative browser/API endpoints create, list, rotate and revoke accounts/tokens. Creation/rotation returns the secret once.
Error types
At minimum:
- validation error;
- authentication required;
- forbidden;
- not found (without existence leakage);
- concurrency conflict;
- idempotency conflict;
- rate limited;
- unsupported media type;
- payload too large;
- import preflight conflict;
- dependency unavailable.
Every problem includes a stable machine-readable type and correlation ID.
Rate limits
Separate policies:
- login/recovery/bootstrap;
- service-token authentication failures;
- search;
- attachment upload;
- import/backup operations;
- general API.
Limits are configurable and return standards-compatible retry guidance.
Versioning policy
- Additive compatible fields may appear within v1.
- Clients must ignore unknown fields.
- Breaking changes require
/api/v2or a documented transition. - Outbox event payloads carry their own schema version.
- The future MCP server may never query the database directly.