dorfteich/docs/self-hosting/public-api.md
Claude Fable 5 521ea514b4
Some checks failed
CI / Lint, typecheck, test (pull_request) Successful in 6m38s
CI / Build container images (pull_request) Successful in 4m14s
CI / Auth e2e pack (pull_request) Successful in 9m7s
CI / Import/export fidelity gate (pull_request) Successful in 1m6s
CD / Deploy to Test (push) Blocked by required conditions
CD / Smoke tests against Test (push) Blocked by required conditions
CD / Promote to Int (push) Blocked by required conditions
CI / Auth e2e pack (push) Blocked by required conditions
CI / Import/export fidelity gate (push) Blocked by required conditions
CI / Build container images (push) Blocked by required conditions
CD / Build and push images (push) Has been cancelled
CI / Lint, typecheck, test (push) Has been cancelled
#211: classification through feeds, public API, search and the no-JS shell
Feeds: classified entries carry a standard Atom <category>
(term=level, scheme=urn:dorfteich:classification, label=the fixed
wording); the feed document states the highest contained level once;
all-open feeds carry none. Public API: page representations (list+get)
gain the classification field, OpenAPI + public-api.md documented.
Search: every hit carries the level and the palette renders the marking
with the snippet (compact form of the banner, text token only). No-JS
shell: banner above and below the content, own markup for the separate
render path; unclassified pages unchanged everywhere. One test per
channel (feed categories + count, public API list/get with the switch
on, search hit levels, shell top+bottom).

Also: fidelity CI sidecars get per-job container names — the fixed
names collided across parallel runs on the shared host (run 547's red
fidelity job; a fixed-name cleanup could even kill a sibling's live
sidecars).

Co-Authored-By: Claude Fable 5 (1M context) <noreply@anthropic.com>
2026-07-31 07:23:53 +02:00

126 lines
5.6 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# Public REST API (issue #104)
Dorfteich instances can expose a token-authenticated REST API at
`/api/public/v1` for scripts and integrations. It is **off by default**,
twice: a Site Admin enables the instance switch (_Admin → Settings →
Public API_), and every pond that should be reachable opts in separately
(_Pond settings → Public API_). Anything not enabled answers 404 —
indistinguishable from an instance without the feature.
## Personal access tokens
Every user manages their tokens under _Settings → API tokens_: a name, a
scope (`read` or `read+write`), an optional expiry, and an optional
restriction to selected ponds. The secret (`dt_pat_…`) is shown exactly
once and stored hashed; tokens are revocable and show their last use.
A token acts **as its user**: the normal permission model — grants, label
scopes, the 404-vs-403 policy — applies unchanged. Scope and pond
restriction only narrow it further; they never grant anything the user
could not do in the app.
## Using the API
```sh
curl -H "Authorization: Bearer dt_pat_..." \
https://your-instance.example/api/public/v1/me
```
- The machine-readable description lives at
`/api/public/v1/openapi.json` (reachable without a token while the
instance switch is on).
- Requests are rate-limited per token (HTTP 429 + `Retry-After`).
- Errors carry the api's uniform body: `{ code, message, details? }`.
- Page content is Markdown in, Markdown + rendered HTML out. A content
update (`PATCH …/pages/{slug}` with `markdown`) **replaces** the whole
page; it is applied through the collaborative document, so open editors
converge live, and the previous state stays in the version history
(the change itself appears as a manual version named "API update").
### Endpoint overview
| Area | Endpoints |
| -------- | ---------------------------------------------------------------------------------------------------------------------------------- |
| Identity | `GET /me` |
| Ponds | `GET /ponds`, `GET /ponds/{slug}` |
| Pages | `GET/POST /ponds/{slug}/pages` (list filters `?createdSince=`/`?updatedSince=`), `GET/PATCH/DELETE /ponds/{slug}/pages/{pageSlug}` |
| Search | `GET /search?q=&pond=&label=` |
| Export | `GET /ponds/{slug}/export/markdown` (ZIP) |
| Labels | `GET/POST /ponds/{slug}/labels`, `PATCH/DELETE /ponds/{slug}/labels/{id}`, `PUT/DELETE /ponds/{slug}/pages/{pageSlug}/labels/{id}` |
| Comments | `GET/POST /ponds/{slug}/pages/{pageSlug}/comments`, `POST/DELETE …/comments/{id}/resolve` |
Deliberately not in v1 (stage 2): attachment upload, version endpoints,
webhooks.
### Classification (VS-NfD marking, issue #211 / ADR 0022)
Every page representation (`GET …/pages`, `GET …/pages/{pageSlug}`)
carries a `classification` field: `"unclassified"` or `"vs_nfd"`. It is a
**marking, not access control** — permissions are unchanged by it. API
consumers that render or re-publish page content are expected to carry
the marking onward (the fixed wording is
`VS NUR FÜR DEN DIENSTGEBRAUCH`).
The Atom feeds mark classified content with a standard `<category>`
element on both levels:
```xml
<category term="vs_nfd" scheme="urn:dorfteich:classification"
label="VS NUR FÜR DEN DIENSTGEBRAUCH"/>
```
Each classified entry carries one, and the feed document itself carries
one stating the **highest** level it contains. Unclassified entries and
all-open feeds carry none (marking everything trains readers to ignore
markings, ADR 0022).
## Connect Claude Code / MCP clients
The instance ships its own MCP endpoint (Streamable HTTP) at `/api/mcp`
no extra process. It has **its own switches**, independent of the REST
API: the instance switch under _Admin → Settings → Public API_ ("Enable
the built-in MCP endpoint") and a per-pond opt-in in the pond settings.
Authentication uses the same personal access tokens.
Claude Code (or any Streamable-HTTP client):
```sh
claude mcp add --transport http dorfteich https://your-instance.example/api/mcp \
--header "Authorization: Bearer dt_pat_..."
```
Stdio-only clients bridge with `mcp-remote`:
```json
{
"mcpServers": {
"dorfteich": {
"command": "npx",
"args": [
"mcp-remote",
"https://your-instance.example/api/mcp",
"--header",
"Authorization: Bearer dt_pat_..."
]
}
}
}
```
Tools: `list_ponds`, `list_pages`, `read_page`, `search`, `create_page`,
`update_page` (replace semantics, collab-safe like the REST PATCH),
`add_comment`, `list_labels`, `set_page_labels`, `export_pond`. A tool
call acts as the token's user; write tools need the `write` scope, and
ponds without the MCP opt-in stay invisible (404 semantics). The endpoint
is stateless — no sessions to manage, safe behind load balancers.
## Security notes
- Bearer tokens only — no cookies are involved, so there is no CSRF
surface; browser sessions cannot call the public API and tokens cannot
manage tokens.
- Token creation and revocation are audit-logged, as is every write
through the API (action `api.write`, with the token attributed).
- Treat a token like a password. Revoke it under _Settings → API tokens_
the moment it may have leaked.