COMPASS docs

REST API reference

Base URL: $COMPASS_URL (contract default http://<host>:8080/api/v1). Every request except GET /version needs an X-Compass-Key header. All times are ISO-8601 UTC. There is no DELETE endpoint in this reference — statements, processes, lessons, and every other record are superseded or ended, never removed (a handful of narrow, audited exceptions exist in code but are not part of this contract; see the deviations note at the end of this page).

Errors always look like {"detail": "<plain-text sentence>", ...fields}. A 409 names the conflicting record; a 422 names the offending field. Unknown fields in a write body are rejected with 422 (every input model inherits extra="forbid").

This page lists only endpoints that exist both in the contract and in the running code, grouped the way the contract groups them. Auth column: ro = read-only key works, rw = read-write key required, bruecke = also reachable with a bruecke-scoped key.

Subjekte (subjects)

Method & path Auth What it does
GET /subjekte ro List subjects ({id, typ, ref_id, titel}).
POST /subjekte rw Create a subject; idempotent on (typ, ref_id).

Sachen (subjects being tracked)

Method & path Auth What it does
GET /sachen ro List Sachen. Filters: bereich, stufe, suche, persona (ownership), sichtfeld (visibility), alle, limit. sichtfeld and persona answer different questions on purpose — see Concepts. Each entry carries anzahl_geltend (valid statements, all kinds).
GET /sachen/{slug} ro One Sache plus its statements, split into gilt / abgeloest / offen / hypothesen. Each statement carries its check state (pruefstand, pruefweg_letzter_lauf): ungeprueft means no check path or never run.
POST /sachen rw Create a Sache; idempotent on slug (200 if it already exists). persona_slug makes it a persona rule instead of a company rule.

Regelbuch (rule book view)

Method & path Auth What it does
GET /regeln/wirkung ro Does the rule work? For every rule that came out of a lesson, how often the same error recurred after gilt_seit, compared against an equally long span before it (wiederholt_lehre_id, counted transitively). Four groups: wirkt (at least halved), haeufiger, zu_frueh (in force for less than 14 days — no number, not 0), nicht_messbar. alle=true adds rules without a lesson; they get grund='ohne_lehre' and no number either. No model, no estimate.
GET /regelbuch ro Read-only "printed" view of every Sache with a currently-held rule, grouped by area (bereich). Optional bereich= filters the shown chapter; bereiche/gesamt in the response are always the full, unfiltered summary. This is Stage A of a three-stage migration of CLAUDE.md §0 into the Core — see the guide.

Aussagen (statements)

Method & path Auth What it does
POST /aussagen rw Write a new statement. 409 with the current statement if a regel already holds and ersetzt is missing; 422 for an unknown Sache, a hypothesis without frist+pruefweg_slug, or a level violation.
GET /aussagen/{id} ro One statement plus ersetzt_von/ersetzt (the supersede chain).
POST /aussagen/{id}/status rw Change status (widerlegt|offen|vorlaeufig|gilt) — hypotheses and open/provisional statements only.
POST /aussagen/{id}/beenden rw End a currently-held statement that has no successor (status=abgeloest, gilt_bis=now()). 409 if it isn't currently gilt.
GET /gilt/{sache} ro The one currently-held rule, or 404 with {detail, offen}. persona= filters by visibility (403 if the Sache is outside it, unless alle=true).
GET /gilt-am/{sache} ro What held on a given datum, with status. Same visibility behaviour as GET /gilt/{sache}.
GET /warum/{sache} ro The rule, its decision text, its history, and the last check result.
GET /konflikte ro Sachen where something does not line up. Every entry carries art: mehrere_geltende_aussagen (several currently-held statements of the same kind on the same Sache, with differing text, none superseding the other — plus anzahl_aussagen and anzahl_subjekte), stufenverstoss, ungepruefte_stufe, or widerspricht (a recorded WIDERSPRICHT edge, with grund, quelle and gesetzt_von). The same wording twice does not count, nor does a superseded statement, nor an ended edge. And, since 29.09.2026, neither does a Sache that has merely collected answers to different questions — every statement in the group carries quelle = frage:<n>, and all of them point to different questions. Still reported: a statement without a source, one with a foreign source, or two answers to the same question standing side by side.
POST /widersprueche rw Two statements contradict each other — with a reason, and without a confidence score. Set by a human (quelle=mensch) or by an answered click-question (quelle=frage with frage_id); no automation writes here. 422 without a reason or for a statement against itself, 404 for an unknown statement, 409 if that pair already has a live edge.
POST /widersprueche/{id}/beenden rw Withdraw the contradiction: beendet_am/beendet_von are set, nothing is deleted. The edge then drops out of GET /graph and GET /konflikte but stays readable. 409 if it has already been ended.
GET /aussagen/{id}/nachweise ro Which documents support this statement. Each entry carries the document's slug, title and version, plus bestaetigt_am/bestaetigt_von — empty means nobody has looked at it yet. Withdrawn links only appear with ?beendet=true.
POST /aussagen/{id}/nachweise rw Attach a document as supporting evidence ({dokument_slug, bemerkung?}), unconfirmed. Attaching and confirming are two steps. 409 if the same document already supports the statement, or if the document has ended.
POST /nachweise/{id}/bestaetigen rw Record that someone has looked at this evidence. Sets timestamp and name — and nothing else: the statement's status stays as it is. 409 the second time.
POST /nachweise/{id}/beenden rw Withdraw the link: beendet_am is set, nothing is deleted. It drops out of the list and out of GET /warum/{sache}, stays readable, and the same document may be attached again afterwards.

Stufen (levels)

Which levels exist, and in what order, is the tenant's call — empty by default. COMPASS ships no level vocabulary of its own.

Route Right What it does
GET /sachen-stufen ro This tenant's active levels, most general first: [{slug, rang, titel, erstellt_am}]. An empty list means no levels are configured — and that means any level set on a Sache counts as unchecked, not as fine.
PUT /sachen-stufen rw Sets the whole order: {stufen: [{slug, titel?}]}. Position is the rank, most general first — there is no rang field the caller could set. Anything no longer listed gets entfernt_am and stays (nothing is deleted). 422 on a malformed slug or a duplicate entry.

Hypothesen (hypotheses)

Method & path Auth What it does
POST /hypothesen rw Create a hypothesis (art=hypothese, status=vorlaeufig). frist and pruefweg_slug are required.
POST /hypothesen/{id}/bewerten rw Resolve it: bestaetigt|widerlegt|verlaengert (with neue_frist).
GET /hypothesen ro List, optionally faellig=true for due ones.

Prüfwege (checks)

Method & path Auth What it does
POST /pruefwege rw Create or upsert a check. 422 if befehl isn't on the allow-list or contains a writing pattern (>, rm, DELETE, git push, …).
GET /pruefwege ro List checks, including the raw cause columns (erfuellt, werkzeug_fehlt_serie, reparatur_noetig, …).
POST /pruefwege/{slug}/run rw Run it (30s timeout), store the result, write a fact. A clean run resolves any open deviation state for that check.
POST /pruefwege/{slug}/beenden rw End a check — grund and von are required. It no longer runs in the nightly pass, no longer shows up as a deviation and no longer counts in the round; /run then answers 409. The row stays readable, including its last run. Nothing is deleted.

Abweichungen (deviations)

A deviation means a check's result didn't match what its rule expects. The cause is always one of three, never guessed: verstoss (violation), werkzeug_fehlt (a known missing-tool pattern beats any other reading), or unklar (the expectation text itself couldn't be evaluated).

Method & path Auth What it does
GET /abweichungen ro List deviations, with ursache, stumm (true after 3 consecutive tool-missing runs), and a ready-to-paste ticket_text. Optional seit=.
POST /abweichungen/{pruefweg_slug}/verfolgen rw Mark it tracked, optionally attach a ticket. 409 if the check currently isn't deviating.
POST /abweichungen/{pruefweg_slug}/regel-ueberholt rw Raise a Schaltzentrale question ("keep the rule or supersede it") from a deviation. 422 without a currently-held rule on the check.
POST /abweichungen/{pruefweg_slug}/pruefweg-kaputt rw Mark the check itself broken — removes it from the GET /offen alert, not from GET /abweichungen.

Prozesse (processes)

Method & path Auth What it does
POST /prozesse rw Create a process, or a new version if the slug exists (old version gets gilt_bis).
GET /prozesse ro The highest version per slug. alle=true also shows superseded/ended ones. Each entry carries anzahl_schritte and anzahl_luecken.
GET /prozesse/{slug} ro The highest version, its steps, each step's rules, and luecken (steps with neither a rule nor frei=true). ueberholt: true if ended. Plus versionen: every state of this process with gilt_seit and gilt_bis.
GET /prozesse/{slug}?version= ro That exact state, with its steps. This is how to look up what held before a change: a step taken out of the list is still there, in full, in the previous state — nothing is deleted. A state that does not exist is 404, and the answer names the ones that do.
GET /prozesse/{slug}/bild/{key} ro 302 to the step's image, or 404.
POST /prozesse/{slug}/beenden rw End a process with no successor version. 409 if already ended.

Schaltzentrale (things for a human to click)

Method & path Auth What it does
POST /fragen rw Raise a question with at least two options, each carrying a folge (consequence). 422 otherwise.
GET /offen ro Everything that needs a click, per source with state ruhig|unbekannt|achtung. Seven sources, including postfach (messages and tasks from agents to the human). Question entries carry bereich, gestellt_von and a folge per action. Every entry carries herkunft_art (mensch|agent|rundgang|null) from the origin the core has on record — null where no trace exists.
GET /fragen/seit ro Answered questions since a given timestamp — decision, answer, Sache, who answered. (Not yet in the contract; see the deviations note.)
POST /fragen/{id}/antwort rw, bruecke Answer a question; writes a decision, and supersedes the current rule if the chosen option says so.
POST /offen/hinweis/{id}/erledigt rw Mark a Hall-of-Lessons hint as read (same effect as POST /lehren/{id}/gelesen).
POST /offen/{typ}/{id}/erledigt rw Resolve any other open item (question, approval, design handoff, conflict).
Method & path Auth What it does
GET /kern/{subjekt_id} ro The held rules as Markdown, grouped by area, plus a hash.
GET /graph ro Nodes and edges around a Sache, to a tiefe (depth). Edge types actually emitted today: GEHOERT_ZU, ERSETZT, GILT_AN, GEPRUEFT_DURCH, BETRIFFT — see the deviations note for edge types the contract describes but the graph endpoint doesn't emit yet. tage= adds a time window on gilt_seit; omitted means everything.
GET /graph/ansichten ro Saved graph views of this tenant, newest first (alle=true includes the ended ones). A view is a remembered GET /graph request: starting node, tiefe, tage, name — the core does not recompute it.
POST /graph/ansichten rw Create a view. 422 for an unknown sache, an empty name, tiefe outside 1..5 or tage <= 0 (this endpoint rejects instead of clamping, unlike GET /graph); 409 if one of the open views already carries this name.
POST /graph/ansichten/{id}/beenden rw End a view — it leaves the list and stays on record (no DELETE). 409 if it is already ended.
POST /ereignisse rw, bruecke Record an event (chat|klick|messung|pr|lauf|vorlage). Idempotent on quelle_ref.
GET /ereignisse ro List events for a subject, optional seit=.
GET /suche ro Full-text search over statements and events. Answer {treffer, gesamt, abgeschnitten, weiter} — gesamt counts after the visibility filter, weiter is the next page's offset or null. persona= filters statement hits by visibility; event hits are never filtered (events carry no bereich).
GET /regelwerk/abdeckung ro {entscheidungen_gesamt, mit_aussage, ohne_aussage, quote} — how much of the decision log has a matching statement. quote is null, never 0, when the universe is empty.

Import / Export

Method & path Auth What it does
POST /import/claude-md ro Preview: a deterministic, model-free scan (art=claude-md|agents-md|cursor-rules, same scan for all three) for headings with an imperative sentence beneath them, or bullets/numbered items starting with an imperative marker. Each proposal carries regel_text verbatim. Writes nothing. The file path (file) first checks size (max 1 MB) and whether the file is text at all — both 422 with feld: "file" and byte; on failure nothing is read and nothing is guessed.
POST /import/claude-md/apply rw Write the reviewed proposal — Sache and statement in one call (regel for art=claude-md, fakt for agents-md/cursor-rules). Idempotent on quelle_ref; a conflicting existing rule or a content mismatch on the same source line is reported (konflikt/quelle_ref_widerspruch), never silently overwritten.
POST /import/decisions rw, bruecke Bulk-import decisions keyed by a Z-number. Idempotent on quelle=Z.<n>; a content mismatch under the same key, or a second gilt rule without a resolvable ersetzt_z, goes to widerspruch — the call still returns 200.
GET /export/claude-md-section0 ro The rule book as Markdown text.
GET /export/agents-md ro Same content shaped for AGENTS.md.
GET /export/cursor-rules ro A ZIP of Cursor rule files.

Customer data export ("your data is yours")

A customer's whole holding, to take with them — unlike the tool formats above (which only pour the currently valid rules into a foreign config format), this export carries every statement with its history, lessons (including ended ones), memory, processes (every version), checks, questions and decisions, personas, and company memory.

Method & path Auth What it does
GET /export/kopf ro Just {format_version, stand, kunde, zaehlung} — no records, fast.
GET /export ro ?format=json (NDJSON: one header line, then one line per record with teil) or ?format=markdown (readable rule book: per area/thing the rule in force, "earlier versions", decisions in chronological order, lessons in brief — no LLM, nothing invented). Both StreamingResponse.

Never included: keys/hashes, sessions/presence. Row-level isolation as everywhere else — another customer's own key only ever sees their own (empty) export.

Personas

Method & path Auth What it does
GET /personas ro List personas with counts (anzahl_regeln, anzahl_gedaechtnis), vorlage_name, slack. slack is a marker you set and read back here: COMPASS itself sends nothing to Slack. A service you connect can read it and act on it — without such a service it stays inert. For events COMPASS pushes out on its own, use the outgoing calls further down.
GET /personas/{slug} ro Full record plus sichtfeld, regeln, gedaechtnis_letzte (10 newest). 404 for an unknown slug.
PUT /personas/{slug} rw Full upsert — every field not sent resets to its default, this is not a partial update.
GET /personas/{slug}/brief ro The deterministic Markdown brief. ?max_gedaechtnis=&budget=&max_lehren=.
GET /personas/{slug}/brief/stats ro The same numbers the brief is built from, as structured data.
GET /personas/{slug}/gedaechtnis ro Full-text search over a persona's memory (own plus visible company memory).
POST /personas/{slug}/gedaechtnis rw Add a memory entry (fakt|feedback|ereignis).
PATCH /personas/{slug}/gedaechtnis/{id} rw End a memory entry (gilt_bis).
POST /personas/gedaechtnis/import rw Bulk-import memory, idempotent on quelle_ref. persona_slug=null writes company memory.
GET /persona-vorlagen ro The template catalog.
GET /persona-vorlagen/{slug} ro One template.
POST /persona-vorlagen/vorschlag ro Rank up to 3 templates against a free-text need (keyword match, deterministic).
POST /personas/from-vorlage rw Copy a template into a new persona. 409 on slug conflict.
GET /personas/{slug}/beurteilung ro Personnel-file view: open role proposals, wins, self-reported mistakes, relapses, human feedback.
POST /personas/{slug}/rollen-vorschlag rw Propose a change to aufgabe|koennen|stil|grenzen, backed by at least one lesson; raises a Schaltzentrale question.

Bereiche (areas)

Method & path Auth What it does
GET /bereiche ro The area registry, alphabetical.
POST /bereiche rw Add an area. 409 if it exists, 422 if the name isn't a kebab slug.

Agents: presence and inbox

Method & path Auth What it does
POST /agents/herzschlag ro Heartbeat for a session (persona_slug, sitzung, client, status?). Upsert; a session counts as online for 15 minutes after its last heartbeat.
POST /agents/offline ro Explicitly mark a session offline.
GET /agents ro Every active persona with presence, sessions, inbox counts, and live brain-load stats.
POST /agents/{persona}/nachrichten rw (or ro with von_persona set) Send a message. Exactly one of von_persona/von_mensch=true is required. art=auftrag additionally checks leitung/nimmt_auftraege_von.
GET /agents/{persona}/nachrichten ro A persona's inbox; sitzung= marks undelivered messages as delivered as a side effect.
POST /agents/nachrichten/an-mensch rw (or ro with von_persona set) A message/task from a persona TO the human (von_persona/von_sitzung required, an_persona=NULL, an_mensch=true). Appears in GET /offen as the postfach source.
GET /agents/nachrichten ro All messages for the tenant, newest first. an_mensch=true\|false narrows to the human's inbox or to persona-to-persona messages.
POST /agents/nachrichten/{id}/gelesen ro Mark one message read.
POST /agents/nachrichten/{id}/status rw Set a task message's status (offen|angenommen|erledigt|abgelehnt).

Hall of Lessons

Method & path Auth What it does
POST /lehren rw Write a lesson (fehler|gelungen|hinweis). ursache is required for fehler; bereich is required for all (use unsortiert if you don't know yet). Idempotent on quelle_ref.
PATCH /lehren/{id} rw Correct the area or wording of an existing lesson. Creator only; a closed lesson stays as it is (409). A corrected field is replaced, not appended to — sending only part of the text loses the rest. The cause cannot be changed here either; if it needs to read differently, a new lesson supersedes the old one.
GET /lehren ro List, filterable by art, status, bereich, persona (ownership), sichtfeld (visibility), q (full text), offen_hinweise.
GET /lehren/{id} ro One lesson plus its linked question and, if promoted, its rule.
POST /lehren/{id}/aufnehmen rw Mark it seen and endorsed (status=aufgenommen). 409 unless it was offen.
POST /lehren/{id}/regel-vorschlagen rw Raise a Schaltzentrale question to turn it into a rule — never writes the rule directly.
POST /lehren/{id}/beenden rw End it (gilt_bis=now()).
POST /lehren/{id}/gelesen rw Mark a hinweis read.
POST /lehren/{id}/antwort rw Reply to a hinweis; also writes a memory entry for its persona.
GET /lehren/stats ro Counts by status and kind, this week's total, unread hints.
POST /lehren/import-gedaechtnis rw One-time import of file-sourced memory feedback entries as fehler lessons. Supports trocken=true (dry run).

Hall of Fame + Daumen (thumbs)

Method & path Auth What it does
GET /hall-of-fame ro Weekly leaderboard including badges.
GET /hall-of-fame/abzeichen ro All badges, newest week first.
GET /hall-of-fame/quellen ro Which scoring sources currently fire versus are registry-only.
POST /daumen rw A thumbs-up from a human only — a persona as von gets 403. 409 on a second thumb from the same person on the same object.
POST /daumen/{id}/zuruecknehmen rw Reverse a thumb with a counter-entry (409 if already reversed).
GET /daumen ro List thumbs.
GET /personas/{slug}/daumen ro A persona's thumbs total, this week, and 10 most recent.

Statistik

Method & path Auth What it does
GET /statistik ro Six blocks (gehirn, koepfe, anwesenheit, arbeit, qualitaet, system) computed deterministically in one transaction, timestamped once as stand.

The Rounds

(previously named "Der Verbesserer" — the German word was misleading: it never improved anything, it only measures and proposes. "The rounds" carries the hospital-ward sense: someone walks through at night, checks in, notes what stands out, changes nothing.)

Method & path Auth What it does
GET /rundgang ro The result of 23 fixed checks across 6 categories — a number, a threshold, and affected records per check; never a guess. Response also carries modus (off/computing/computing+reading).
POST /rundgang/melden rw If the mode is aus (off): writes nothing. Otherwise files one hinweis lesson per category that crossed its threshold. trocken=true evaluates without writing.
GET /rundgang/einstellungen ro The tenant's current mode + a one-sentence explanation + the three levels (off/computing/computing+reading) with title/description.
PUT /rundgang/einstellungen rw Sets the mode ({modus, von}). Default for a newly created customer: rechnend (computing).
POST /rundgang/versenden rw The morning report: one bundled email per active member covering every still-pending delivery, respecting sofort/taeglich/nie (immediately/daily/never).
GET /kunden/{slug}/mitgliedschaft/rundgang-einstellungen session The caller's own delivery preference (sofort/taeglich/nie) for this customer's rundgang.
PUT /kunden/{slug}/mitgliedschaft/rundgang-einstellungen session Sets the caller's own preference ({zustellung}). Every member for themselves, no owner/manager gate.

Webhooks — events to other systems

COMPASS calls out, your system accepts. Full guide including signature verification and an example: Events to other systems.

Method & path Auth What it does
POST /webhooks rw Create a webhook: {url, ereignisse}. The response carries the geheimnis — this once only. 422 if outgoing calls are not set up on this installation, if ereignisse is empty or holds an unknown name, or if the address isn't https:// or points into a private network.
GET /webhooks ro Your webhooks, without the secret.
POST /webhooks/{id}/test rw Queue a delivery with the event ping, regardless of what you subscribed to. 409 if the webhook is deactivated.
GET /webhooks/{id}/zustellungen?limit=50 ro Deliveries, newest first — attempt, status, last HTTP code, last error. This is where you look when something doesn't arrive.
POST /webhooks/{id}/deaktivieren rw The webhook stops sending and stays readable. 409 if it already is. Nothing is deleted.

The five event names: regel.gesetzt, regel.abgeloest, abweichung.neu, abweichung.behoben, frage.beantwortet.

Firma (company memory)

Method & path Auth What it does
GET /firma ro The six company-memory fields.
PUT /firma rw + person Update named fields. Only with a signed-in person: a key alone gets 403. An agent working inside a person's signed-in session may do it. COMPASS takes the author from the sign-in — a von sent in the body is not used.
GET /firma/stats ro How many of the six fields are filled.

Mandanten (tenants) & version

Method & path Auth What it does
GET /kunde ro The tenant the presented key belongs to ({slug, stufe, menschen, menschen_limit, name}).
GET /version none {git_sha, gebaut_am, dienst, grund} — deliberately reachable without a key.

Deviations from the contract found while writing this page

  • GET /graph emits GEHOERT_ZU, ERSETZT, GILT_AN, GEPRUEFT_DURCH, BETRIFFT — and, since 28.09.2026, WIDERSPRICHT. This entry used to say that WIDERSPRICHT did "not appear in the running code" and was "documented elsewhere as derived-not-stored": the first is no longer true, and the second was already the wrong answer — it is the only stored edge, deliberately so (it follows from a judgement, not from a foreign key). Still open from the contract: ZEIGT and WIRKT_IN; neither appears in the running code today.
  • GET /fragen/seit (and its MCP tool seit_wann_neu) exists in the running code but is not yet described in the contract. Documented here because it is a stable, working read endpoint; flagged as ahead of the contract.
  • GET /api/v1/health exists in the running code but is not mentioned in the contract (which only speaks of GET /version) — left out of this reference for that reason.
  • A number of endpoints exist in the running code with no matching description anywhere in the contract: GET /aussagen/nachfolgerlos, POST /aussagen/ersetzt-nachziehen, GET /sachen/aufraeum, POST /sachen/{slug}/verabschieden, POST /sachen/{slug}/widerrufen, GET /personas/organigramm, POST /personal-vorschlaege, POST /personas/{slug}/verabschieden, DELETE /personas/{slug} (a narrow, audited exception — refuses with 409 unless the persona has zero traces), and GET/POST /gedaechtnis, GET /gedaechtnis/stats, POST /gedaechtnis/backfill-bereiche. None of these are documented here.