241 lines
6.3 KiB
Markdown
241 lines
6.3 KiB
Markdown
# 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_<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
|
|
|
|
```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.
|