- Extract: button on the bullet (desktop) or /extract (/notiz). The bullet becomes the title (hashtags stay at the link), its children the content of a new note in the same folder with the source's shares (a foreign source owner gets write access); the branch becomes [[title]] through the operation log, so open editors follow. - Merge: drop a note onto another in the sidebar or use "Mit anderer Notiz zusammenführen…" in the note header. The own source is appended as an h2 section with its bullets below and goes to the trash; the toast offers a complete undo. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
19 KiB
REST-API
Basis: /api/v1. Antworten sind JSON; Fehler haben die Form {"error": "Text"}. Authentifizierung über Authorization: Bearer <JWT>. 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.
Öffentlich
| Methode | Pfad | Zweck |
|---|---|---|
| GET | /health |
Lebenszeichen |
| GET | /auth/status |
needsSetup, allowRegistration, instanceName, demoMode, sharingMode, legal (imprintUrl, hasImprintContent, privacyUrl, hasPrivacyContent) |
| GET | /legal/{imprint|privacy} |
Hinterlegter Markdown-Text (content), ohne Anmeldung; 404, wenn keiner gesetzt ist |
| POST | /auth/setup |
Erstes Administratorkonto (email, password, displayName, instanceName, allowRegistration, sharingMode, seedSampleData); nur solange es keine Konten gibt |
| POST | /auth/register |
Registrierung (email, password, displayName, inviteCode); nur mit Code oder wenn freigegeben; 429 nach 20 Versuchen pro Adresse in 15 Minuten |
| 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 |
| POST | /auth/stream-ticket |
Einmal-Ticket (60 s) für WebSocket/SSE |
| 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 |
| POST | /documents/{id}/extract |
{blockId, clientId}: Punkt samt Unterpunkten wird eigene Notiz (Titel = Text ohne Hashtags, gleicher Ordner, Freigaben übernommen, fremder Besitzer bekommt Schreibrecht); der Punkt wird zu [[Titel]]. Schreibrecht nötig; 409, wenn jemand anderes im Ast schreibt |
| 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 |
| 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 |
| 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) |
| GET | /ws, /ws/spaces/{id}, /ws/lists/{id} |
WebSocket, siehe 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/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. 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) |
| 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/<Liste>.md im Format des Tasks-Plugins (📅, ⏫/🔺), Listen/<Liste>.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/DELETE | /keys, /keys/{id} |
API-Keys verwalten (Klartext nur bei Erstellung); POST mit name und scopes (Standard ["webhook:triage"]) |
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 strukturiertehitsmit Snippet und<mark>-Hervorhebung:{ "thoughts": [...], "documents": [...], "listItems": [...], "hits": [ { "type": "document", "id": "blk_123", "title": "Projektplanung", "snippet": "... wichtige Notiz zum <mark>Release</mark> ...", "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 |
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/DELETE | /workspace/folders/shared/{id}/shares[/{shareId}] |
Freigaben (permission: read/write) |
| 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/DELETE | /documents/{id}/shares[/{userId}] |
Notiz-Freigaben (userId/email/teamId, permission: read/write) |
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. 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:
{
"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.holdist 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: trueliefert 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/DELETE | /lists/{id}/shares[/{shareId}] |
Freigaben (targetType, targetId, role: editor/viewer) |
| 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/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 |
| PUT | /admin/users/{id}/password |
Passwort zurücksetzen |
| GET/POST/DELETE | /admin/invites[/{id}] |
Einladungen (email, role, organizationId, validDays) |
| 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 |
Beispiele
# 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"}'