# 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-Key` for 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 ```http Authorization: Bearer ikh__ ``` 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 ```text 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/info` - `GET /health` is outside `/api/v1` and 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}/children` - `GET /pages/{id}/relations` - `GET /pages/{id}/revisions` - `GET /pages/{id}/revisions/{revision}` - `POST /pages` - `PUT /pages/{id}` with `If-Match` - `POST /pages/{id}/move` with `If-Match` - `POST /pages/{id}/trash` with `If-Match` - `POST /pages/{id}/restore` - `POST /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 /projects` - `GET /projects/{id}` - `POST /projects` - `PUT /projects/{id}` with `If-Match` - `GET /projects/{id}/updates` - `POST /projects/{id}/updates` - `GET /projects/{id}/decisions` - `GET /projects/{id}/context` ### Project context `GET /projects/{id}/context` returns a bounded context bundle: ```json { "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 `updatedAt` and 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 /attachments` streaming multipart - `GET /attachments/{id}/metadata` - `GET /attachments/{id}/content` - `DELETE /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/v2` or a documented transition. - Outbox event payloads carry their own schema version. - The future MCP server may never query the database directly.