# REST-API Basis: `/api/v1`. Antworten sind JSON; Fehler haben die Form `{"error": "Text"}`. Authentifizierung über `Authorization: Bearer `. **API-Keys** (`Bearer mindsift_sec_…` bzw. Header `X-API-Key`) liefern nur Daten ein, über `POST /inbox` und `POST /webhooks/ingest`, und nur für die Ziele ihrer Scopes: `webhook:triage` (Inbox), `webhook:task` (Aufgaben), `webhook:list` (Listeneinträge). Alles andere, auch Live-Streams, antwortet mit `403` bzw. `401`. Zeitstempel sind ISO-8601 (UTC). Anfragekörper sind auf 2 MB begrenzt (Notiz-Sync 8 MB, Importe 101 MB). Im Demo-Modus antworten Endpunkte, die etwas verschicken, Server-Einstellungen ändern oder die gemeinsamen Demo-Konten verändern würden, mit `403` „In der Demo deaktiviert“. Quelle der Wahrheit sind die Routen in [server.go](../../internal/api/server.go). ## Öffentlich | Methode | Pfad | Zweck | |---|---|---| | GET | `/health` | Lebenszeichen | | GET | `/auth/status` | `needsSetup`, `allowRegistration`, `instanceName`, `demoMode`, `sharingMode`, `legal` (`imprintUrl`, `hasImprintContent`, `privacyUrl`, `hasPrivacyContent`, `customLinkLabel`, `customLinkUrl`) | | GET | `/legal/{imprint\|privacy}` | Hinterlegter Markdown-Text (`content`), ohne Anmeldung; `404`, wenn keiner gesetzt ist | | POST | `/share-target` | PWA Web Share Target Fallback: Weiterleitung auf `/` (`303 See Other`), falls Service Worker noch inaktiv | | POST | `/auth/setup` | Erstes Administratorkonto (`email`, `password`, `displayName`, `instanceName`, `allowRegistration`, `sharingMode`, `seedSampleData`); nur solange es keine Konten gibt | | POST | `/auth/verify-email` | Link aus der Bestätigungs-E-Mail (`token`): schaltet das Konto frei oder reicht es an die Freigabe weiter (`{"verified","pending","message"}`) | | POST | `/auth/resend-verification` | Bestätigungslink erneut schicken (`email`); antwortet immer gleich, höchstens alle 2 Minuten pro Konto | | POST | `/auth/register` | Registrierung (`email`, `password`, `displayName`, `inviteCode`); mit Code immer, sonst je nach Registrierungsmodus. `201` mit Token oder `202 {"pending":true,"verify","message"}`, wenn erst die E-Mail-Adresse bestätigt (`verify`) und/oder ein Admin freigeben muss (Login bis dahin `403`); `429` nach 20 Versuchen pro Adresse in 15 Minuten oder bei mehr als 50 wartenden Konten | | POST | `/auth/login` | Anmeldung (`login`/`email`/`username`, `password`) → `{token, user}` | | POST | `/auth/forgot-password` | `{email}` → immer `{ok: true}`; Mail mit Link nur, wenn das Konto existiert. `503`, wenn SMTP oder `BASE_URL` fehlen; `429` nach 10 Anfragen pro Adresse in 15 Minuten | | POST | `/auth/reset-password` | `{token, password}` (mind. 8 Zeichen) → neues Passwort, alle Sitzungen beendet; `400` bei ungültigem oder abgelaufenem Link | | POST | `/auth/quickstart` | Immer `403` (Gastzugang deaktiviert) | | POST | `/webhooks/ingest` | Einliefern per Webhook, Key als `X-API-Key` (empfohlen), Bearer oder `?token=` (in der URL nur API-Keys, keine Sitzungs-Tokens). Body: `title`/`content`/`text`/`message`, `priority`, `source`, `tags`, `dueDate`; `target` (oder `type`): `triage` (Standard, Inbox), `task` (optional `taskListId`), `list_item` (`listId`, `category`, `quantity`). Klartext-Body wird als Inhalt genommen. `403` ohne passenden Scope oder ohne Schreibrecht auf die Liste | | GET | `/ws`, `/ws/spaces/{id}`, `/ws/lists/{id}` | WebSocket, siehe [websocket.md](websocket.md); `?ticket=` oder `Authorization`-Header | | GET | `/events` | SSE-Fallback; `?ticket=` oder `Authorization`-Header | ## Konto und Allgemeines (angemeldet) | Methode | Pfad | Zweck | |---|---|---| | GET | `/auth/me` | Benutzer und Kennzahlen | | POST | `/events/rooms` | SSE-Raum beitreten/verlassen (`sseId`, `room`, `action`); nur eigene `sseId`, nur Räume mit Lesezugriff | | POST | `/auth/stream-ticket` | Einmal-Ticket (60 s) für WebSocket/SSE | | POST | `/voice/transcribe` | Nur im Server-Modus der Spracherkennung: `multipart/form-data` mit `file` (Aufnahme, z. B. WebM/Opus oder MP4/AAC) → `{text}`. `404` ohne `STT_URL`, `413` zu groß, `429` Stundenlimit, `503` ausgelastet, `502` Whisper-Fehler (Details im Log) | | POST | `/auth/logout` | Sitzung dieses Geräts beenden | | GET | `/auth/sessions` | Angemeldete Geräte: `[{id, createdAt, lastUsedAt, userAgent, ip, current}]` | | DELETE | `/auth/sessions/{id}` | Ein Gerät abmelden; `404` bei fremder oder unbekannter Sitzung | | POST | `/auth/revoke-all` | Alle Sitzungen beenden (`token_version` +1) | | PATCH | `/auth/settings` | `displayName`, `email`, `password`, `avatarUrl`, `alarmChannels`, `alarmEmail`, `discordWebhookUrl`, `customWebhookUrl`, `notifyBeforeMinutes`. `notificationSettings` (`paused`, `includeOwnWatched`, `events.{due,shared,taskDone,watched}.{email,discord}`, `dailySummary.{enabled,time,email,discord}`) für Benachrichtigungen. Neue `email` oder `password` nur mit `currentPassword` (`403` bei falschem, `429` bei Login-Sperre); nach neuem Passwort sind andere Geräte abgemeldet, das neue Token kommt im Header `X-Session-Token` | | POST | `/auth/alarms/test` | Test-Alarm (`channel`: `email`/`discord`/`webhook`, optional `target`) | | GET | `/users/directory`, `/collaborators` | Personen, die man sehen und mit denen man teilen kann (je nach `sharingMode`, [sharing-and-permissions.md](../sharing-and-permissions.md)) | | GET | `/stats` | Zähler fürs Dashboard (u. a. `inboxCount`, `tasksOpenCount`, `listItemsOpenCount` = offene Einträge aller sichtbaren Listen) | | GET | `/search?q=` | Globale FTS5-Suche in zugänglichen Notizen (Titel & Blöcke), Aufgaben und Listen → `{thoughts, documents, listItems, hits}` | | GET | `/export` | Datenexport als JSON; mit `?format=zip` dieselben Daten als `mindsift-backup.json` in einer ZIP (so lädt die Oberfläche das Backup) | | GET | `/export/markdown` | Obsidian-kompatibles ZIP (`mindsift-vault-DATUM.zip`), direkt gestreamt: Notizen als `.md` mit YAML-Frontmatter in der Ordnerstruktur des Nutzers (inkl. geteilter), `Aufgaben/.md` im Format des Tasks-Plugins (`📅`, `⏫`/`🔺`), `Listen/.md` | | GET/POST | `/themes` | Eigene CSS-Themes; anlegen oder mit `id` ändern (`name`, `css`, max. 200 KB) | | DELETE | `/themes/{id}` | Eigenes Theme löschen | | POST | `/import/mindsift`, `/import/obsidian` | Import (Multipart-Feld `file`, max. 100 MB): MindSift-JSON/-Archiv bzw. Obsidian-Vault-ZIP; `?preview=1` liefert nur die Zusammenfassung (`folders`, `notes`, `taskLists`, `tasks`, `lists`, `listItems`, `inbox`, `skipped`) | | — | `/import/mindsift?mode=restore` | Wiederherstellen: löscht vor dem Import alle eigenen Notizen, Ordner, Aufgaben, Gedanken und Listen (erst nachdem die Datei vollständig gelesen und geprüft ist) | | DELETE | `/user/data` | Alle eigenen Daten löschen, Konto bleibt (`{"password"}` nötig) | | DELETE | `/user/account` | Eigenes Konto mit allen Daten löschen (`{"password"}` nötig); `403` für den letzten Server-Admin. `/auth/me` liefert dafür `isLastAdmin` | | GET | `/backups`, `/backups/{name}` | Eigene automatische Server-Backups auflisten bzw. herunterladen | | POST | `/backups/{name}/restore` | Server-Backup einspielen: `mode=merge` oder `restore` (gewählte Bereiche vorher leeren), `sections=notes,tasks,lists,inbox,themes`, `preview=1` | | GET/POST | `/keys` | API-Keys auflisten bzw. anlegen (Klartext nur bei Erstellung); `POST` mit `name` und `scopes` (Standard `["webhook:triage"]`) | | DELETE | `/keys/{id}` | API-Key löschen | ### Globale Suche (`GET /api/v1/search?q=`) Führt eine Volltextsuche über SQLite FTS5 mit BM25-Relevanz-Ranking aus. - Durchsucht Aufgaben/Gedanken (Inhalt, Notizen, Unteraufgaben), Notizen (Titel sowie Inhalte aktiver Blöcke) und Listeneinträge. - Beachtet strikt alle Zugriffsregeln: Nur eigene Gedanken/Aufgaben und solche in zugänglichen Aufgabenlisten; nur eigene und geteilte Notizen (direkt, über Gruppen oder freigegebene Ordner); nur eigene und geteilte Listen. - Titel-Treffer werden höher gewichtet als Treffer im Fließtext. - Liefert rückwärtskompatible Collections (`thoughts`, `documents`, `listItems`) sowie strukturierte `hits` mit Snippet und ``-Hervorhebung: ```json { "thoughts": [...], "documents": [...], "listItems": [...], "hits": [ { "type": "document", "id": "blk_123", "title": "Projektplanung", "snippet": "... wichtige Notiz zum Release ...", "docId": "doc_abc", "blockId": "blk_123" } ] } ``` ## Gedanken, Triage, Aufgaben | Methode | Pfad | Zweck | |---|---|---| | POST | `/inbox` | Schnellerfassung. Body: `content`, `priority`, `source`, `dueDate`, `target` (`inbox`/`task`/`list_item`/`document`), `targetListId`, `taskListId` (snake_case-Varianten werden akzeptiert) | | GET | `/thoughts?status=` | Gedanken (`inbox`, `kept`, `converted`, `discarded`, `all`) | | GET/PATCH/DELETE | `/thoughts/{id}` | Einzelabruf; Teiländerung (`content`, `isCompleted`, `dueDate`, `priority`, `taskListId`, `kanbanStatus`, `notes`, `subtasks`, `tags`, `assignees`, `recurrence` = `daily`/`weekly`/`monthly`/`yearly`/leer); Löschen. `assignees` ersetzt die Zuweisungen; nur Personen mit Zugriff auf die Liste, sonst `403` | | DELETE | `/thoughts/discarded/purge` | Papierkorb leeren | | GET | `/triage/deck` | Posteingang (max. 100) | | PATCH | `/triage/{id}` | `action`: `keep`/`discard`/`restore`; mit `convertTo`: `task`/`document`/`list_item` | | GET | `/tasks?taskListId=` | Aufgaben (optional einer Liste) | | POST | `/tasks/reorder`, `/task-lists/{id}/tasks/reorder` | Reihenfolge/Spalte ändern (`items[{id, sortOrder, kanbanStatus}]`) | | POST | `/tasks/{id}/unlink` | Verknüpfung zu Notiz-Block lösen | | POST/DELETE | `/tasks/{id}/watch` | Aufgabe beobachten bzw. nicht mehr → `{watching}`; `404` ohne Zugriff. Aufgaben-Antworten tragen `isWatching` | ## Aufgabenlisten | Methode | Pfad | Zweck | |---|---|---| | GET/POST | `/task-lists` | Listen (`permission` je Liste: `owner`/`editor`/`viewer`); anlegen (`title`, `icon`, `color`, `teamId`) | | PUT/DELETE | `/task-lists/{id}` | Ändern (`title`, `icon`, `color`, `teamId`, `columns`) – nicht für Leser; Löschen nur Eigentümer | | GET | `/task-lists/{id}/shares` | Freigaben | | POST | `/task-lists/{id}/shares` | Nur Eigentümer. Body: `targetType` (`team`/`user`), `targetId`, `permission` (`editor`/`viewer`, Standard `editor`). Erneutes Senden für dasselbe Ziel ändert die Stufe | | DELETE | `/task-lists/{id}/shares/{shareId}` | Nur Eigentümer | Schreibzugriffe von Lesern auf Listen oder deren Aufgaben liefern `403` („Keine Schreibrechte für diese Aufgabenliste (nur Lesen)“). ## Workspace | Methode | Pfad | Zweck | |---|---|---| | GET/POST/DELETE | `/folders` | Ordnerliste, anlegen (`path`), löschen | | POST | `/folders/move` | `sourcePath`, `targetParentPath` | | GET/POST | `/workspace/folders/shared` | Geteilte Ordner; anlegen (`name`, `mountPath`) | | GET/DELETE | `/workspace/folders/shared/{id}` | Abruf; Löschen | | PUT | `/workspace/folders/shared/{id}/mount` | Eigenen Einhängepfad ändern (`mountPath`) | | GET/POST | `/workspace/folders/shared/{id}/shares` | Freigaben geteilter Ordner (`permission`: `read`/`write`) | | DELETE | `/workspace/folders/shared/{id}/shares/{shareId}` | Freigabe eines geteilten Ordners entfernen | | GET | `/workspace/tags`, `/workspace/tags/{tag}` | Tag-Übersicht und -Details | | PUT | `/workspace/tags/{tag}/note` | Tag-Seite speichern (`content`) | | GET/POST | `/documents` | Notizen auflisten; anlegen (`title`, `icon`, `folderPath`, `blocks`, `tags`, `isPinned`) | | GET/PUT/DELETE | `/documents/{id}` | Abruf; Ändern von `title`, `icon`, `folderPath`, `isPinned` (Blöcke → `400`, sie laufen über `/docs/{id}/sync`); Löschen = in den Papierkorb (Eigentümer) bzw. Freigabe verlassen (andere) | | GET/DELETE | `/documents/trash` | Papierkorb auflisten (`id`, `title`, `icon`, `folderPath`, `deletedAt`, `isOwner`) bzw. leeren | | GET | `/documents/{id}/backlinks` | Notizen mit `[[Titel]]`-Link auf diese Notiz (nur sichtbare, ohne Papierkorb): `docId`, `title`, `icon`, `folderPath`, bis zu 5 `blocks` (`id`, `content`), `more` | | POST | `/documents/{id}/restore` | Aus dem Papierkorb zurückholen (fehlender Ordner wird neu angelegt) | | DELETE | `/documents/{id}/permanent` | Notiz im Papierkorb endgültig löschen | | PUT | `/documents/{id}/move` | Ordner wechseln (`folderPath`) | | POST | `/documents/{id}/blocks/{blockId}/enhanced-task` | Aus einem Block eine verknüpfte Aufgabe machen (`content`, `checked`) | | GET/POST | `/documents/{id}/shares` | Notiz-Freigaben (`userId`/`email`/`teamId`, `permission`: `read`/`write`) | | DELETE | `/documents/{id}/shares/{userId}` | Notiz-Freigabe entfernen | | POST | `/documents/{id}/extract` | `{blockId, clientId}`: legt aus den Unterpunkten des Punkts eine eigene Notiz an (Titel = Text ohne Hashtags, gleicher Ordner, Freigaben übernommen, fremder Besitzer bekommt Schreibrecht) → `{document, link}`. Die Ursprungsnotiz ändert der Server nicht; der Editor ersetzt den Ast selbst durch `link` (normale eigene Änderung, Strg+Z möglich). Schreibrecht nötig; `409`, wenn jemand anderes im Ast schreibt | | GET | `/push/key` | `{enabled, publicKey}` – öffentlicher VAPID-Schlüssel für `pushManager.subscribe` (in der Demo `enabled: false`) | | GET | `/push/devices` | Eigene Geräte mit Push → `{devices: [{id, endpoint, device, createdAt}]}` | | POST | `/push/subscribe` | `{endpoint, keys: {p256dh, auth}, device}` – Gerät anmelden (nur `https`-Endpunkte) | | POST | `/push/unsubscribe` | `{endpoint}` – Gerät abmelden | | POST | `/push/test` | Test-Benachrichtigung an alle eigenen Geräte → `{devices}`; `400` ohne Gerät | | GET | `/journal` | Tage des eigenen Journals, neueste zuerst → `{days: [{date, docId, empty}]}` (`empty`: Notiz noch ohne Text; Tage im Papierkorb fehlen) | | POST | `/journal/{date}` | Notiz des Tages (`JJJJ-MM-TT`) öffnen; gibt es noch keine, wird sie angelegt (Titel z. B. „Freitag, 30. Oktober 2026“, Icon 📅) → Dokument, `201` bei Neuanlage. Die Journal-Notizen sind normale Notizen (Teilen, Suche, Sync) | | POST | `/documents/{id}/extract/undo` | Legt die ausgelagerte Notiz `{id}` wieder in den Papierkorb (nur ihr Ersteller) | | POST | `/documents/{id}/merge` | `{sourceId}`: hängt die (eigene) Quellnotiz als Überschrift mit Unterpunkten an diese Notiz an, Quelle in den Papierkorb → `{targetId, sourceId, sourceTitle, headingId}` | | POST | `/documents/{id}/merge/undo` | `{sourceId, headingId}`: Abschnitt wieder entfernen, Quelle wiederherstellen | | POST | `/documents/{id}/media` | Bild oder Audio zu einer Notiz hochladen (`multipart/form-data`, Feld `file`; Schreibrecht nötig) → `{id, kind, mime, size, url}`. `415` falsches Format, `413` zu groß oder Kontingent voll | | POST | `/documents/{id}/media/fetch` | Externes Bild kopieren: `{url}` (nur https, keine internen Adressen; nicht in der Demo) → wie oben | | GET | `/media/{id}` | Datei abrufen (Leserecht auf eine Notiz, die sie zeigt), mit Range-Anfragen; sonst `404` | | GET | `/link-preview?url=https://…` | Vorschau einer Web-Seite (`title`, `description`, `siteName`, `hasImage`, `hasIcon`, `ok`, `key`), vom Server geholt und 7 Tage zwischengespeichert; `422` ohne https, `403` in der Demo, `429` zu viele Abrufe | | GET | `/link-preview/{key}/image` bzw. `/icon` | Bild bzw. Icon einer Vorschau | Der Inhalt einer Notiz (Blöcke) wird ausschließlich über `POST /docs/{id}/sync` geändert. `POST /documents` nimmt beim Anlegen weiterhin eine Start-Blockliste an; sie wird intern in Operationen umgerechnet. ## Gemeinsames Bearbeiten von Notizen Konzept und Regeln: [modules/collaboration.md](../modules/collaboration.md). Zugriff über die Rolle `owner`/`editor`/`viewer` (`Store.DocRole`); ohne Rolle `404`. | Methode | Pfad | Zweck | |---|---|---| | GET | `/docs/{id}/snapshot` | Kompletter Blockbaum inkl. Grabsteinen, `title`, `titleVersion`, `headSeq`, `role`, `shared` | | POST | `/docs/{id}/sync` | Ein Rundlauf: eigene Operationen senden, Leases halten/abgeben, Log ab `lastSeq` abholen | | GET | `/docs/{id}/changes?since=N&wait=S` | Long-Polling: antwortet sofort, wenn `headSeq > N`, sonst bei der nächsten Änderung (auch Leases/Präsenz) oder nach höchstens 25 s. Antwort `{headSeq, changed}`; Daten holt der Client danach per `/sync` | | POST | `/docs/{id}/leave` | Sitzung beenden, alle Leases des Clients freigeben (`{clientId}`) | `/sync`-Anfrage: ```json { "clientId": "tab-7f3…", "lastSeq": 412, "ops": [ { "opId": "c1a…", "kind": "set_content", "blockId": "b9…", "baseVersion": 7, "content": "Milch und Brot" }, { "opId": "c1b…", "kind": "insert", "blockId": "bA…", "parentId": "b2…", "orderKey": "a0V", "type": "bullet", "content": "" } ], "leases": { "hold": ["b9…", "bA…"], "release": [] } } ``` - Operationsarten: `insert`, `set_content`, `set_props` (`patch`: `type`, `checked`, `collapsed`, `lang`, `clearConflict`), `move`, `delete`, `restore` (`ids`), `set_title` (`baseVersion`, `title`). - `leases.hold` ist die vollständige Liste der Blöcke, die der Client halten will; alle anderen Leases des Clients werden freigegeben. Neu eingefügte Blöcke und Konfliktkopien bekommt der Autor automatisch. - Höchstens 500 Operationen pro Anfrage. Leser dürfen keine Operationen senden (`403`), aber abfragen. - `skipLog: true` liefert kein Log zurück; gedacht für einzelne Änderungen ohne geöffneten Editor (z. B. Abhaken in der To-Do-Übersicht). Antwort: `results` (je Operation `status` `applied`/`conflict`/`rejected`, ggf. `conflictBlockId`, `version`, `seq`), `ops` (Log-Einträge `{seq, opId, userId, clientId, effect}` ab `lastSeq`, höchstens 1000; `hasMore` bei mehr), `headSeq`, `resync` (Snapshot neu laden: der Client ist dem Server voraus, oder sein `lastSeq` liegt vor der verdichteten Grenze `log_floor`; die gesendeten Operationen werden trotzdem angewendet), `role`, `shared`, `sessions`, `presence`, `leases` (`granted`, `denied`, `others`). Wiederholte Operationen (gleiche `opId`) werden nicht erneut angewendet; die Antwort enthält das gespeicherte Ergebnis. ## Geteilte Listen | Methode | Pfad | Zweck | |---|---|---| | GET/POST | `/lists` | Listen; anlegen (`title`, `icon`, `listType`) | | GET/DELETE | `/lists/{id}` | Abruf mit Einträgen; Löschen | | GET/POST | `/lists/{id}/shares` | Freigaben abrufen bzw. anlegen (`targetType`, `targetId`, `role`: `editor`/`viewer`) | | DELETE | `/lists/{id}/shares/{shareId}` | Freigabe entfernen | | POST | `/lists/{id}/items` | Eintrag (`content`, `category`, `quantity`) | | PATCH | `/lists/{id}/items/{itemId}` | Abhaken (`isCompleted`) | | PUT | `/lists/{id}/items/{itemId}` | Ändern (`content`, `category`, `quantity`) | | DELETE | `/lists/{id}/items/{itemId}` | Löschen | | POST | `/lists/{id}/items/{itemId}/move` | In eine andere Liste verschieben (`targetListId`); Bearbeitungsrecht in beiden Listen nötig, der Eintrag landet oben | | POST/PUT | `/lists/{id}/items/reorder` | Reihenfolge/Kategorie (`items[{id, sortOrder, category, content}]`) | | POST | `/lists/{id}/clear-completed` | Erledigte entfernen | ## Gruppen | Methode | Pfad | Zweck | |---|---|---| | GET/POST | `/teams` | Eigene Gruppen; anlegen (`name`, `description`) – Ersteller wird Gruppen-Admin, die Gruppe gehört zu seiner Organisation; im Modus `private` gesperrt | | PUT/DELETE | `/teams/{id}` | Nur Gruppen-Admin | | POST | `/teams/{id}/members` | Mitglied hinzufügen (`userId` oder `email`, `role`); nur sichtbare Personen, sonst `404` | | PUT/DELETE | `/teams/{id}/members/{userId}` | Rolle ändern (`admin`/`member`); entfernen | ## Administration (`role = admin`) | Methode | Pfad | Zweck | |---|---|---| | GET/POST | `/admin/users` | Benutzer auflisten, anlegen (`email`, `password`, `displayName`, `role`, optional `organizationId`) | | PUT | `/admin/users/{id}/organization` | Organisation setzen (`organizationId`, leer = keine); entzieht Freigaben, Gruppen und Zuweisungen der alten | | GET/POST | `/admin/organizations` | Organisationen mit `memberCount`; anlegen (`name`) | | PUT/DELETE | `/admin/organizations/{id}` | Umbenennen (`name`); löschen (Mitglieder sind danach ohne Organisation) | | PATCH/DELETE | `/admin/users/{id}` | `displayName`, `email`, `role`, `isActive`, `discordWebhookUrl`; löschen (bei wartenden Konten = ablehnen). `GET /admin/users` markiert wartende Konten mit `pendingApproval` | | POST | `/admin/users/{id}/approve` | Wartendes Konto freigeben; die Person bekommt eine E-Mail (`409`, wenn es nicht wartet) | | PUT | `/admin/users/{id}/password` | Passwort zurücksetzen | | GET/POST | `/admin/invites` | Einladungen auflisten bzw. anlegen (`email`, `role`, `organizationId`, `validDays`, `maxUses` 1–1000; mehrfach verwendbare nicht an eine E-Mail gebunden). Antwort mit `maxUses`/`useCount`. Als Link: `/?invite=` öffnet die Registrierung mit eingetragenem Code | | POST | `/admin/invites/{id}/send` | Einladung per E-Mail (`email`); bindet einmalige Codes an diese Adresse; braucht SMTP | | DELETE | `/admin/invites/{id}` | Einladung widerrufen/löschen | | GET/PUT | `/admin/settings` | `instanceName`, `allowRegistration`, `sharingMode` (`private`/`organizations`/`open`), SMTP-Felder; ein Moduswechsel bereinigt Freigaben. `smtpPass` kommt nie zurück (`smtpPassSet` zeigt, ob eines gespeichert ist); leer = unverändert | | GET | `/admin/settings/sharing-impact?mode=` | Probelauf: wie viele Freigaben und Gruppen-Mitgliedschaften ein Wechsel entfernen würde (`affected`) | | POST | `/admin/smtp/test` | Testmail (`toEmail`) | | GET | `/admin/voice` | Aktive Spracherkennung (`mode`: `server`/`browser`/`off`), Einstellungen aus `STT_*` ohne Schlüssel, Grenzen und im Server-Modus ein frischer Zustand (`status.reachable`, `status.modelReady`) | | GET/POST | `/admin/backups` | Datenbank-Abzüge auflisten bzw. sofort sichern (Abzug + alle Benutzer-Backups) | | GET | `/admin/backups/{name}` | Datenbank-Abzug herunterladen | | DELETE | `/admin/backups/{name}` | Datenbank-Abzug löschen (gibt die verbleibende Liste zurück; nicht in der Demo) | ## Beispiele ```bash # Gedanken per API-Key erfassen curl -X POST https://mindsift.example.com/api/v1/webhooks/ingest \ -H "X-API-Key: mindsift_sec_…" -H "Content-Type: application/json" \ -d '{"title":"Milch kaufen #einkauf","priority":"high","dueDate":"2026-11-01"}' # Einkaufsliste per API-Key ergänzen (Scope webhook:list) curl -X POST https://mindsift.example.com/api/v1/webhooks/ingest \ -H "X-API-Key: mindsift_sec_…" -H "Content-Type: application/json" \ -d '{"target":"list_item","listId":"lst_…","content":"Hafermilch","quantity":"2"}' # Aufgabenliste nur lesend freigeben curl -X POST https://mindsift.example.com/api/v1/task-lists/tkl_…/shares \ -H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" \ -d '{"targetType":"user","targetId":"usr_…","permission":"viewer"}' ```