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

5.6 KiB
Raw Blame History

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

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:

<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):

claude mcp add --transport http dorfteich https://your-instance.example/api/mcp \
  --header "Authorization: Bearer dt_pat_..."

Stdio-only clients bridge with mcp-remote:

{
  "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.