Files
MindSift/Doku/api/rest.md
T
sandrodachandClaude Opus 5.5 4d1b37c956
MindSift CI & Docker Release / Build, Test in Container & Push (push) Successful in 1m56s
feat(workspace): turn a branch into its own note, merge notes
- 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>
2026-10-30 00:25:46 +01:00

19 KiB
Raw Blame History

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 strukturierte hits mit 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.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/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"}'