REST-API
Programmatischer Zugriff auf sol2flow mit persönlichen API-Schlüsseln. Ein Schlüssel handelt als die Person, die ihn erstellt hat, mit genau deren Berechtigungen. Die API ist ein weiterer Zugang mit denselben Berechtigungsprüfungen wie die App, nie ein Weg daran vorbei.
- Basis-URL:
<APP_URL>/api/v1 - Referenz: Jede Instanz hat ihre eigene Swagger UI unter
<APP_URL>/api/docsund das OpenAPI-3.1-Dokument unter<APP_URL>/api/v1/openapi.json, immer passend zur Version der Instanz (ein Administrator kann sie abschalten, siehe Instanz-Einstellungen). In der Swagger UI nimmt Authorize einen API-Schlüssel entgegen, und Try it out ruft die API aus Ihrem Browser auf. - Diese Seite erklärt die Konzepte; die Swagger UI listet jeden Endpunkt mit seinen Parametern und Antwortschemas.
- Um sol2flow aus einem KI-Assistenten zu nutzen (Claude, Cursor, VS Code, …), siehe den MCP-Server.
Authentifizierung
- In der App: Kontomenü (unten links) → API-Schlüssel → API-Schlüssel erstellen. Vergeben Sie einen Namen, eine
Laufzeit (30 Tage, 90 Tage (Standard), 1 Jahr, nie) und den Zugriff: Vollzugriff (Standard) oder Nur lesen
(Scopes). Zum Erstellen eines Schlüssels muss die Anmeldung weniger als 10 Minuten zurückliegen (sonst
erscheint „Bestätigen Sie, dass Sie es sind“). Der Schlüssel (
sf_<prefix>_<secret>) wird einmal angezeigt. Kopieren Sie ihn in Ihren Secret-Speicher. - Senden Sie ihn bei jeder Anfrage mit:
curl -H "Authorization: Bearer $SOL2FLOW_KEY" https://flow.example.com/api/v1/me
GET /me liefert staff_role ("OWNER", "ADMIN", "SUPPORT", "AUDITOR" oder null) statt des booleschen Felds
is_instance_admin, das entfallen ist. Skripte, die is_instance_admin geprüft haben, sollten staff_role !== null
prüfen, oder die konkrete Rolle.
- Akzeptiert wird nur der Header
Authorization: Bearer. Cookies werden ignoriert: Eine Anfrage nur mit dem Sitzungs-Cookie des Browsers erhält 401. Es gibt also keine CSRF-Angriffsfläche, und ein Skript kann sich keine Browser-Sitzung ausleihen. - Ein Schlüssel funktioniert nicht mehr, wenn er abläuft, wenn er widerrufen wird (von Ihnen oder von einem Owner /
Admin der Instanz), wenn Ihr Konto deaktiviert oder gelöscht wird, solange sich Ihr Konto nicht anmelden kann (zum
Beispiel eine Registrierung, deren E-Mail-Adresse noch nicht bestätigt ist), und solange API-Schlüssel auf der Instanz
abgeschaltet sind (403
api_disabled). - Bis zu 25 aktive Schlüssel pro Konto. Jedes Erstellen und Widerrufen eines Schlüssels löst eine Sicherheits-E-Mail an das Konto aus.
- Die Liste zeigt für jeden Schlüssel Name,
sf_<prefix>…<last 4>, Zugriff (Vollzugriff / Nur lesen), Erstellung, Ablauf und letzte Nutzung (höchstens einmal pro Minute aktualisiert).
Scopes
Ein Schlüssel hat Vollzugriff (alles, was seine Besitzerin oder sein Besitzer darf; das ist der Standard und gilt für jeden Schlüssel, der vor Einführung der Scopes erstellt wurde) oder ist nur lesend:
- Ein Nur-Lese-Schlüssel darf ausschließlich GET und HEAD aufrufen: auflisten, lesen, suchen, Anhänge herunterladen
(
GET /files/{id}). Jede andere Methode wird mit 403insufficient_scopebeantwortet, bevor irgendetwas anderes passiert. - Die Prüfung erfolgt zentral für jeden Endpunkt.
- Einige Lesezugriffe erfordern ebenfalls Vollzugriff: die Webhook-Endpunkte (ihre URLs und Zustellprotokolle sind Admin-Konfiguration).
- Das OpenAPI-Dokument nennt den Scope, den jede Operation braucht, in
x-required-scope(read/full), und die Beschreibung des Bearer-Schemas erklärt die Regel. - Ein Scope erweitert nie etwas: Ein Nur-Lese-Schlüssel sieht weiterhin nur, was seine Besitzerin oder sein Besitzer sehen darf.
Konventionen
- JSON rein und raus, in snake_case. Zeitstempel sind ISO 8601 in UTC (
2026-09-25T09:30:00.000Z); Kalendertage sindYYYY-MM-DD. IDs sind UUIDs; Workspaces werden über ihren Slug angesprochen (wie in/w/<slug>). - Erfolg:
200mit der Ressource,201beim Erstellen,202für Einladungen (per E-Mail verschickt),204(ohne Body) beim Löschen. Schreibende Anfragen liefern die aktualisierte Ressource zurück, sodass Sie selten ein zweites GET brauchen. - Personen sind
{ id, name, username }, nie mit ihrer E-Mail-Adresse (nurGET /meliefert Ihre eigene), wie in den Personenauswahlen der App. Ausnahme, wie in der App: Staff, der E-Mail-Adressen sieht (Owner, Admin, Support, Staff-Rollen), erhältemailbei Mitgliedern und Board-Personen; Einladungsadressen folgen der unten beschriebenen Maskierungsregel. - Formatierter Text (Aufgabenbeschreibungen, Kommentare) wird als reiner Text (ein Absatz pro Zeile) oder als
Tiptap-JSON-Dokument (
{"type":"doc","content":[…]}, das Format des Editors) angenommen. Beides wird genau wie Eingaben aus der App bereinigt; Erwähnungen ({"type":"mention","attrs":{"id":"<user id>","label":"Name"}}) bleiben nur für Personen erhalten, die das Board öffnen können, und diese werden benachrichtigt. Antworten enthalten das JSON (description,doc) und den reinen Text (description_text,text). Markdown funktioniert in beide Richtungen: Senden Sie"format": "markdown"mit einem String, und hängen Sie?format=markdownan, um es zurück zu erhalten, siehe Markdown. - Positionen: Listen, Aufgaben und Checklisten-Einträge werden zwischen Nachbarn verschoben:
previous_id(das Element, das danach direkt davor steht) undnext_id(direkt danach). Eine Aufgabe ohne beides zu verschieben, setzt sie ans Ende der Liste. - Schreibende Anfragen erzeugen dieselben Aktivitätseinträge und Benachrichtigungen wie die App, mit Ihnen als handelnder Person.
Paginierung
Listen-Endpunkte nehmen limit (1–100, Standard 50) und cursor entgegen und antworten mit
{ "data": [ … ], "next_cursor": "eyJ1Ijoi…" }
Geben Sie next_cursor als cursor zurück, bis er null ist. Cursor sind opak (bauen Sie sie nicht selbst); ein
fehlerhafter Cursor ergibt 400. GET /tasks ist nach letzter Änderung sortiert, älteste zuerst, sodass
updated_since=<timestamp> zusammen mit dem Cursor eine inkrementelle Synchronisierung ergibt.
Fehler
{ "error": { "code": "title_required", "message": "Title is required.", "field": "title" } }
code ist stabil, verzweigen Sie darauf; message ist englischer Text für Menschen; field nennt die Eingabe, sofern
bekannt; max ist das Limit bei too_long / file_too_large / api_key_limit; retry_after (Sekunden) kommt mit 429; plan_limit kommt mit
kind (was ausgeschöpft ist, z. B. seats oder guests), limit und used.
| Status | Wann | Typische Codes |
|---|---|---|
| 400 | Body ist kein JSON; ein Pfad- oder Query-Parameter ist ungültig (falsche UUID, falscher Cursor) | invalid_input |
| 401 | Kein Schlüssel, fehlerhafter, unbekannter, abgelaufener oder widerrufener Schlüssel; Konto deaktiviert | unauthenticated |
| 403 | Ihre Rolle erlaubt es nicht; der Schlüssel ist nur lesend; API-Schlüssel, Webhooks oder öffentliche Links abgeschaltet; nicht im Plan enthalten | forbidden, insufficient_scope, api_disabled, webhooks_disabled, public_links_disabled_workspace, plan_feature, guest_viewer_only |
| 404 | Existiert nicht oder Sie können es nicht öffnen (die API sagt nie, was von beidem) | not_found |
| 405 | Bekannter Pfad, andere Methode | method_not_allowed |
| 409 | Der aktuelle Zustand lässt es nicht zu | board_archived, label_scope, last_board_admin, last_admin_leave, last_private_admin, oidc_managed, instance_admin_leave, conflict, upload_incomplete, webhook_limit, plan_limit |
| 413 | Datei (oder JSON-Body > 1 MB) zu groß | file_too_large |
| 422 | Validierung fehlgeschlagen | title_required, name_required, too_long, dates_order, duration_invalid, invalid_input, webhook_url_blocked, webhook_url_unresolved, webhook_url_invalid, events_required, … |
| 429 | Rate-Limit: Retry-After Sekunden warten | rate_limited |
| 500 | Ein Fehler im Programm; nichts Internes wird preisgegeben | internal |
Rate-Limits
Pro Schlüssel: 600 Anfragen / 10 Minuten, davon höchstens 120 schreibende (POST, PUT, PATCH, DELETE). Anfragen
ohne gültigen Schlüssel werden pro Client-IP begrenzt (30 / 10 Minuten; danach 429 statt 401). API-Aufrufe zählen
außerdem gegen dieselben Limits pro Person wie die App (Kommentare 30 / min, Suche 240 / min, Uploads 300 / h,
Einladungen 100 Adressen / h pro einladender Person und 200 / h pro Workspace, Widerrufe 100 / h). Bei Überschreitung:
429 mit einem Retry-After-Header (Sekunden) und retry_after im Body.
Endpunkte
Die vollständige, stets aktuelle Referenz (Parameter, Bodys, Antwortschemas, Beispiele) ist die Swagger UI Ihrer Instanz unter /api/docs. Überblick:
| Bereich | Endpunkte |
|---|---|
| Konto | GET /me: Ihr Profil, einschließlich staff_role (OWNER · ADMIN · SUPPORT · AUDITOR · null) und time_zone (Ihre gespeicherte IANA-Zone aus Einstellungen → Einstellungen & Darstellung, z. B. Europe/Berlin; null = automatisch, die des Browsers; nur lesbar, API-Zeitstempel sind immer UTC) und, seit API 1.6.0, email_verified (true, sobald Ihre E-Mail-Adresse bestätigt ist; false ohne E-Mail-Adresse) und, ab API 1.8, api_key: der für die Anfrage verwendete Schlüssel (name, prefix, scope full / read, expires_at; nie sein Geheimnis) |
| Workspaces | GET /workspaces · GET /workspaces/{slug} · GET /workspaces/{slug}/search?q= |
| Boards | GET /workspaces/{slug}/boards (archived, q) · POST /workspaces/{slug}/boards · GET /boards/{id} (mit Listen) · PATCH /boards/{id} (Name, Zugriff, Standardrolle) · POST /boards/{id}/archive · POST /boards/{id}/restore |
| Listen | POST /boards/{id}/lists · PATCH /boards/{id}/lists/{listId} · POST …/lists/{listId}/move · DELETE …/lists/{listId} |
| Labels | GET /workspaces/{slug}/labels · GET /boards/{id}/labels · POST /boards/{id}/labels |
| Aufgaben | GET /tasks (board_id oder workspace; list_id, assignee, label, q, due_from, due_to, updated_since, archived=true, um archivierte Aufgaben einzuschließen) · GET /tasks/{id} · POST /tasks · PATCH /tasks/{id} · POST /tasks/{id}/move · POST /tasks/{id}/move-board (board_id, list_id, position: ein anderes Board desselben Workspace; neuer Schlüssel, der alte leitet in der App weiter) · POST /tasks/{id}/archive · Aufgaben: estimate_minutes + estimate_display (Arbeitszeit, Accept-Language), schreiben mit estimate: "3d" oder estimate_minutes · POST /tasks/{id}/unarchive (eine archivierte Aufgabe ist schreibgeschützt: 409 task_archived) · DELETE /tasks/{id} · POST /tasks/{id}/restore · PUT /tasks/{id}/labels · PUT/POST /tasks/{id}/assignees, DELETE /tasks/{id}/assignees/{userId} · dasselbe für members |
| Checkliste | POST /tasks/{id}/checklist · PATCH /checklist/{itemId} (Text, erledigt) · POST /checklist/{itemId}/move · DELETE /checklist/{itemId} |
| Aufgabenverknüpfungen | GET /tasks/{id}/links (nur Verknüpfungen zu Aufgaben, die Sie öffnen können) · POST /tasks/{id}/links (kind: relates_to, blocks, blocked_by, duplicates, duplicated_by – aus Sicht dieser Aufgabe; task_id: die andere Aufgabe, gleicher Workspace; 409 link_exists, 422 link_self) · DELETE /tasks/{id}/links/{linkId} |
| Kommentare | GET /tasks/{id}/comments (mit Antworten; `order=newest |
| Zeit | GET /time-entries?workspace= (board_id, task_id, user_id, from, to) · POST /time-entries · PATCH /time-entries/{id} · DELETE /time-entries/{id} · GET /timer · POST /timer/start · POST /timer/stop |
| Anhänge | GET /tasks/{id}/attachments · GET /files/{fileId} · Uploads, siehe unten |
| Benachrichtigungen | GET /notifications (unread) · PATCH /notifications/{id} (read) · POST /notifications/read-all |
| Freigabe | GET /boards/{id}/sharing · PATCH /boards/{id}/sharing (Zugriff, Standardrolle, öffentlicher Link) · POST /boards/{id}/members · PATCH/DELETE /boards/{id}/members/{userId} · GET /boards/{id}/invitations |
| Mitglieder | GET /workspaces/{slug}/members (role, q, include=invitations) · POST /workspaces/{slug}/members · PATCH/DELETE /workspaces/{slug}/members/{userId} · POST /workspaces/{slug}/leave |
| Einladungen | POST /workspaces/{slug}/invitations (202) · GET /workspaces/{slug}/invitations · DELETE /invitations/{id} · GET /me/invitations · POST /me/invitations/{id}/accept · POST /me/invitations/{id}/decline |
| Webhooks | GET/POST /workspaces/{slug}/webhooks · GET/PATCH/DELETE …/webhooks/{id} · POST …/{id}/rotate-secret · POST …/{id}/ping (202) · GET …/{id}/deliveries (status) · GET …/deliveries/{deliveryId} · POST …/deliveries/{deliveryId}/redeliver (202) |
Zeiteinträge: started_at ist ein Zeitpunkt; der Start wird auf die Minute genau gespeichert, wie bei Einträgen, die in
der App erfasst werden. Geben Sie entweder duration_sec (60 … 86 400) oder ended_at an. Die Sichtbarkeit folgt den
Regeln der App (Zeiterfassung).
Gäste erhalten 404 für GET /time-entries, da sie keine Zeiterfassung haben.
Kommentare: Gäste können die Kommentar-Endpunkte (schreiben, antworten, eigene bearbeiten und löschen) nur nutzen, wo Gastkommentare eingeschaltet sind; sonst 403.
Freigabe, Mitglieder und Einladungen
Es gelten dieselben Regeln wie im Teilen-Dialog, auf der Mitglieder-Seite und im Einladungsbanner (Mitglieder und Freigabe).
| Endpunkt | Wer | Hinweise |
|---|---|---|
GET /boards/{id}/sharing | alle, die das Board öffnen können | Zugriff, Standardrolle, public_link.enabled; für Board-Admins außerdem public_link.url, expires_at, password (true, wenn eines festgelegt ist), visibility, allowed und blocked_by (plan oder workspace). people: explizite Board-Mitglieder und die Workspace-Admins (locked), Gäste gekennzeichnet |
PATCH /boards/{id}/sharing | Board-Admins | access, default_role, public_link: true erstellt / behält den Link, false entfernt ihn, oder ein Objekt mit den Einstellungen (unten) |
POST /boards/{id}/members | Board-Admins | {user_id, role?, workspace_role?}; ohne role: VIEWER für Gäste, EDITOR für alle anderen. Gäste erhalten höchstens VIEWER (403 guest_viewer_only). Jemand im Workspace oder ein Konto außerhalb, das Sie in der App auswählen könnten (teilt einen Workspace mit Ihnen), wird dem Workspace als workspace_role hinzugefügt (GUEST; MEMBER erfordert einen Workspace-Admin). Alle anderen: 404, per E-Mail einladen |
PATCH / DELETE /boards/{id}/members/{userId} | Board-Admins | nur explizite Mitglieder (sonst 404); der letzte Board-Admin bleibt (409 last_board_admin); ein Gast erhält höchstens VIEWER (403 guest_viewer_only) |
GET /workspaces/{slug}/members | Admins und Mitglieder des Workspaces (Gäste: 404) | mit Cursor paginiert, Admins zuerst; role, q, include=invitations (dann folgen die offenen Einladungen). managed = OIDC-Gruppensynchronisierung (nur lesbar), automatic = Zugriff über Instanz-Admin-Rolle |
POST /workspaces/{slug}/members | Workspace-Admins | {user_ids, role}: nur Personen, die Sie sehen können (gemeinsamer Workspace; Owner / Admin im Staff: alle); der Rest wird übersprungen → {added} |
PATCH / DELETE /workspaces/{slug}/members/{userId} | Workspace-Admins | nicht Sie selbst (403, nutzen Sie leave), keine per OIDC verwalteten Einträge (403), nicht der letzte explizite Admin eines privaten Workspace (409 last_private_admin) |
POST /workspaces/{slug}/leave | jedes Mitglied | 409 last_admin_leave, last_private_admin, oidc_managed, instance_admin_leave (Regeln) |
POST /workspaces/{slug}/invitations | Workspace-Admins; Board-Admins für Gäste mit board_id | {emails, role, board_id?, board_role?} → 202 {sent}. Gäste erhalten höchstens VIEWER (EDITOR / ADMIN: 403 guest_viewer_only); über dem Gästelimit des Plans: 409 plan_limit mit kind guests |
GET /workspaces/{slug}/invitations, GET /boards/{id}/invitations | Admins und Mitglieder des Workspaces (Gäste: 404) / alle auf dem Board | offene Einladungen mit can_revoke, ein Eintrag pro Einladung (wer in den Workspace und zu zwei Boards eingeladen ist, hat drei) |
DELETE /invitations/{id} | Workspace-Admins, wer sie verschickt hat, Admins ihres Boards | 404, wenn sie nicht mehr offen ist |
GET /me/invitations, POST /me/invitations/{id}/accept, …/decline | Sie | Zuordnung nur über Ihre bestätigte E-Mail-Adresse; accept und decline beantworten nur diese eine Einladung (accept tritt bei Bedarf dem Workspace bei und fügt das Board dieser Einladung hinzu; Ihre anderen Einladungen bleiben offen); accept liefert den Workspace zurück |
Die Einstellungen des öffentlichen Links (Ein Board öffentlich teilen) stehen in einem
public_link-Objekt; jedes Feld ist optional:
| Feld | Bedeutung |
|---|---|
enabled | true / false: den Link ein- oder ausschalten |
expires_at | ein Zeitpunkt in der Zukunft (ISO 8601 mit Offset) oder null für keinen Ablauf; ein vergangener ergibt 422 expiry_past |
password | 6–128 Zeichen oder null, um es zu entfernen; ein neues oder entferntes Passwort meldet alle Besucher ab. Nur schreibbar: Es wird nie zurückgegeben |
regenerate | true: ein neuer Link; der alte funktioniert sofort nicht mehr |
visibility | was Besucher sehen, z. B. {"assignees": true, "time": false}; weggelassene Schlüssel bleiben, wie sie sind. Schlüssel: labels, dates, checklists, descriptions (bei neuen Links an), assignees, members, comments, attachments, time (aus) |
Alles außer dem Ausschalten eines Links wird mit 403 plan_feature abgelehnt, wenn der Plan keine öffentlichen Links
enthält, und mit 403 public_links_disabled_workspace, wenn im Workspace Öffentliche Links erlauben aus ist.
Datenschutz, genau wie in der App:
- E-Mail-Adressen: Adressen anderer Personen nur für Staff, der E-Mail-Adressen sieht (sonst
email: null). Einladungsadressen sehen nur dieser Staff, Workspace-Admins und die Person, die die Einladung verschickt hat, vollständig; alle anderen erhaltenj•••@client.io(email_masked: true), undqfindet nur, was Sie sehen, damit sich eine Adresse damit nicht ausforschen lässt. - Konten: Eine Einladung wird immer gleich mit
202 {"sent": n}beantwortet, egal ob zu einer Adresse ein Konto existiert. Niemand wird direkt hinzugefügt, jede Adresse erhält die E-Mail (und eine Benachrichtigung in der App, wenn ein bestätigtes Konto sie hat).POST /boards/{id}/membersundPOST /workspaces/{slug}/memberserreichen nur Personen, die Sie in der App ohnehin auswählen könnten. - Rate-Limits: Einladungen zählen gegen
invite(pro einladender Person) undinvite-workspace(pro Workspace), jede Adresse einmal; Widerrufe gegeninvite-revoke. Das sind dieselben Kontingente wie in der App, zusätzlich zuapi/api-write.
curl -H "$H" -H 'Content-Type: application/json' -X PATCH "$BASE/boards/$BOARD/sharing" -d '{"access":"RESTRICTED","public_link":false}'
curl -H "$H" -H 'Content-Type: application/json' -X PATCH "$BASE/boards/$BOARD/sharing" \
-d '{"public_link":{"enabled":true,"expires_at":"2026-12-31T23:59:59Z","visibility":{"assignees":true}}}'
curl -H "$H" -H 'Content-Type: application/json' -X POST "$BASE/boards/$BOARD/members" -d '{"user_id":"…","role":"EDITOR"}'
curl -H "$H" -H 'Content-Type: application/json' -X POST "$BASE/workspaces/northwind/invitations" \
-d '{"emails":["[email protected]"],"role":"GUEST","board_id":"…","board_role":"VIEWER"}' # → 202 {"sent":1}
curl -H "$H" "$BASE/workspaces/northwind/members?include=invitations&limit=100"
curl -H "$H" -X POST "$BASE/me/invitations/$INVITE/accept"
Pläne
Wo Pläne gelten, listet GET /api/v1/public/plans (ohne Schlüssel) die öffentlichen Pläne auf, und
GET /organizations/{slug}/plan zeigt den Owners und Admins einer Organisation deren Plan, Limits und Nutzung.
featuresgibt an, was ein Plan enthält, unter anderemguest_comments(Workspaces können Gäste dürfen kommentieren einschalten) undemail_notifications(aus: Benachrichtigungen aus den Workspaces der Organisation gibt es nur in der App).limits.guestsist das optionale Gästelimit (null= unbegrenzt). Gäste belegen nie einen Platz.- Seit API 1.5.0 sind
guests_freeundlimits.guests_freeveraltet: immertrue, sie werden in einer späteren Version entfernt. Verlassen Sie sich nicht darauf. highlightsenthalten „Guests don't use a seat“ nicht mehr.
Webhooks
Workspace-Webhooks (Webhooks: Ereignisse, Payloads, Signaturen, Wiederholungen, Adressregeln) über die
API, derselbe Dienst wie unter Einstellungen → Workspace → Webhooks. Nur für Workspace-Admins mit einem Schlüssel mit
Vollzugriff, auch für GET (Nur-Lese-Schlüssel: 403 insufficient_scope; andere Mitglieder: 403 forbidden);
403 webhooks_disabled, solange sie auf der Instanz abgeschaltet sind.
curl -H "$H" -H 'Content-Type: application/json' -X POST "$BASE/workspaces/northwind/webhooks" \
-d '{"url":"https://hooks.example.com/sol2flow","description":"CI","events":["task.created","comment.created"]}'
# → 201 {"id":"…","state":"active",…,"secret":"whsec_…"} (the secret is shown only here and by rotate-secret)
curl -H "$H" -H 'Content-Type: application/json' -X PATCH "$BASE/workspaces/northwind/webhooks/$HOOK" -d '{"active":false}'
curl -H "$H" -X POST "$BASE/workspaces/northwind/webhooks/$HOOK/ping" # → 202, the queued delivery
curl -H "$H" "$BASE/workspaces/northwind/webhooks/$HOOK/deliveries?status=failed"
curl -H "$H" -X POST "$BASE/workspaces/northwind/webhooks/$HOOK/deliveries/$D/redeliver"
ping und redeliver zählen gegen webhook-manual (60 / 10 Minuten pro Workspace).
Importe
Board-Importe aus kan.bn und Trello (Importe: was importiert wird und wie), wie unter Einstellungen → Workspace → Import & Export. Nur für Workspace-Admins: POST braucht einen
Schlüssel mit Vollzugriff, GET einen Lese-Schlüssel. Der Lauf ist ein Hintergrundjob: POST antwortet mit 202 und
dem Import; fragen Sie GET ab, bis status gleich DONE oder FAILED ist. Ein Import pro Workspace gleichzeitig
(409 import_running). Zugangsdaten der Quelle werden einmal im Body übertragen, nur verschlüsselt und nur so lange
aufbewahrt, wie der Lauf sie braucht, und nie zurückgegeben.
# Trello API (key + token) or kan.bn (api_key, base_url for a self-hosted Kan); boards = source board ids
curl -H "$H" -H 'Content-Type: application/json' -X POST "$BASE/workspaces/northwind/imports" \
-d '{"source":"trello","api_key":"…","token":"…","boards":["5f1b…"],"access":"RESTRICTED"}' # → 202 {"id":"…","status":"PENDING",…}
curl -H "$H" -H 'Content-Type: application/json' -X POST "$BASE/workspaces/northwind/imports" \
-d '{"source":"kanbn","base_url":"https://kan.example.com","api_key":"…","boards":["brd8Kq2mZx1a"],"people":{"mem1aB2cD3eF":"<user id>"}}'
# a Trello board JSON export (up to 50 MB) in `board`
jq -n --slurpfile b board.json '{source:"trello_file", board:$b[0]}' | curl -H "$H" -H 'Content-Type: application/json' -X POST "$BASE/workspaces/northwind/imports" -d @-
curl -H "$H" "$BASE/workspaces/northwind/imports/$IMPORT" # status, counts, errors, results[].board_key
people ordnet Personenschlüssel der Quelle Workspace-Mitgliedern zu (null = nicht zuweisen); nicht aufgeführte
Personen werden automatisch zugeordnet (E-Mail → Benutzername → eindeutiger vollständiger Name). Niemand wird
eingeladen. Ein erneuter Import fügt nur Neues hinzu.
Einen Anhang hochladen
Derselbe in Teile zerlegte, fortsetzbare Upload wie in der App:
# 1. start: the answer says how to split the file
curl -H "Authorization: Bearer $KEY" -H 'Content-Type: application/json' \
-d '{"board_id":"…","task_id":"…","name":"report.pdf","size":1048576,"mime_type":"application/pdf"}' \
$BASE/uploads # → {"upload_id":"…","chunk_size":8388608,"part_count":1,"size":1048576}
# 2. each part n = 1…part_count: exactly chunk_size bytes (the last one: the rest)
curl -X PUT -H "Authorization: Bearer $KEY" -H 'Content-Type: application/octet-stream' \
--data-binary @report.pdf $BASE/uploads/$UPLOAD_ID/parts/1
# 3. finish: the file is attached to the task
curl -X POST -H "Authorization: Bearer $KEY" $BASE/uploads/$UPLOAD_ID/complete
GET /uploads/{id} listet die angekommenen Teile auf (zum Fortsetzen), DELETE /uploads/{id} bricht ab.
Größenlimits: Admin → Allgemein → Uploads (413 file_too_large, max in MB).
Markdown
Aufgabenbeschreibungen, Kommentare und Antworten lassen sich als Markdown (GitHub-Variante) schreiben und lesen. Dabei wird dieselbe Umwandlung verwendet wie beim Einfügen von Markdown im Editor, gefolgt von derselben Bereinigung wie bei jeder anderen Eingabe.
- Schreiben:
POST /tasks,PATCH /tasks/{id}(description) sowiePOST /tasks/{id}/comments,PATCH /comments/{id}(text) akzeptieren"format": "markdown". Ohne diese Angabe ("text", der Standard) ist ein String reiner Text, ein Absatz pro Zeile; bestehende Clients sind nicht betroffen. Bei einer Tiptap-JSON-description/ einemdocwirdformatignoriert. - Lesen:
?format=markdownbeiGET /tasks/{id},POST /tasks,PATCH /tasks/{id},GET /tasks/{id}/comments(auch Antworten),POST /tasks/{id}/commentsundPATCH /comments/{id}ergänztdescription_markdown(Aufgaben;nullohne Beschreibung) odermarkdown(Kommentare;null, wenn gelöscht).description/docund der reine Text bleiben unverändert. - Syntax:
#/##→ große Überschrift,###+ → Überschrift;**bold**,*italic*/_italic_,~~strike~~,`code`, Code-Fences mit Sprachangabe,>-Zitate,-/*/1.-Listen (verschachtelt),- [ ]/- [x]Aufgabenlisten,---, Links und automatische Links, Zeilenumbrüche bleiben erhalten (ein einzelner Zeilenumbruch ist ein Zeilenumbruch). Tabellen werden zu einem Absatz pro Zeile. - Erwähnungen:
@[Name](user:<user id>)beim Schreiben und Lesen; es gelten dieselben Regeln wie für JSON-Erwähnungen (nur Personen, die das Board öffnen können, bleiben Erwähnungen und werden benachrichtigt; alle anderen werden zum Text „@Name“). - Sicherheit: Rohes HTML wird nie interpretiert:
<b>,<script>,<img onerror=…>bleiben wörtlicher Text. Links müssen http(s), mailto oder App-relativ sein (beijavascript:usw. bleibt nur der Text); Bilder nur als/api/files/<id>(andere werden zu Links). Unterstreichung gibt es in Markdown nicht; sie entfällt bei der Ausgabe.
curl -H "$H" -H 'Content-Type: application/json' -X POST "$BASE/tasks?format=markdown" \
-d '{"board_id":"…","list_id":"…","title":"Release 1.2","format":"markdown","description":"## Steps\n- [ ] Changelog\n- [x] Tag **v1.2**"}'
# → { …, "description": {"type":"doc",…}, "description_text": "Steps\nChangelog\nTag v1.2", "description_markdown": "## Steps\n\n- [ ] Changelog\n- [x] Tag **v1.2**" }
curl -H "$H" "$BASE/tasks/$TASK/comments?format=markdown"
Beispiele
BASE=https://flow.example.com/api/v1; KEY=sf_…
H="Authorization: Bearer $KEY"
curl -H "$H" "$BASE/workspaces/northwind/boards?q=website"
curl -H "$H" "$BASE/tasks?workspace=northwind&assignee=$ME&due_to=2026-10-31&limit=100"
curl -H "$H" -H 'Content-Type: application/json' -X POST "$BASE/tasks" \
-d '{"board_id":"…","list_id":"…","title":"Write release notes","description":"Cover the API.\nLink the docs.","due_date":"2026-10-01"}'
curl -H "$H" -H 'Content-Type: application/json' -X POST "$BASE/tasks/$TASK/comments" -d '{"text":"Shipped 🎉"}'
curl -H "$H" -H 'Content-Type: application/json' -X POST "$BASE/time-entries" \
-d '{"task_id":"…","started_at":"2026-09-25T08:00:00Z","duration_sec":5400,"note":"Pairing"}'
Instanz-Einstellungen
Admin → Allgemein → API:
- API-Schlüssel erlauben (standardmäßig an). Aus: Der Menüpunkt „API-Schlüssel“ verschwindet, und jeder bestehende
Schlüssel erhält 403
api_disabled; wird die Option wieder eingeschaltet, funktionieren sie wieder (sie werden nicht gelöscht). - Webhooks erlauben (standardmäßig an) und Webhooks an private Netzwerkadressen erlauben (standardmäßig aus): Webhooks.
- API-Dokumentation (standardmäßig an). Aus:
/api/docs(Swagger UI und ihre Assets) und/api/v1/openapi.jsonantworten mit 404, z. B. für eine Produktivinstanz, die ihre API nicht bekannt machen soll. Die API selbst funktioniert weiter.
Die Swagger UI wird von der Instanz selbst ausgeliefert (kein externes CDN) und folgt dem dunklen / hellen Design der App. „Authorize“ nimmt einen Schlüssel entgegen; „Try it out“ ruft die API dann damit aus dem Browser auf.
Versionierung
- Die Version steht im Pfad (
/api/v1). Innerhalb von v1 sind Änderungen ausschließlich additiv: neue Endpunkte, neue optionale Anfragefelder, neue Antwortfelder, neue Fehlercodes für neue Situationen. Clients müssen unbekannte Felder ignorieren. - Ein Feld entfernen oder umbenennen, einen Typ oder Statuscode ändern oder die Validierung eines bestehenden Felds
verschärfen erfordert
/api/v2; v1 funktioniert dann noch mindestens 6 Monate weiter, angekündigt im Changelog. info.versionim OpenAPI-Dokument folgt Semantic Versioning für den v1-Vertrag.- Ab API 1.8 trägt jede Antwort unter
/api/v1den HeaderSol2flow-Api-Version(zum Beispiel1.8.0), dieselbe Version wieinfo.version, sodass ein Client erkennt, was die Instanz unterstützt, auch wenn die API-Dokumentation abgeschaltet ist. Ältere Instanzen senden keinen solchen Header.