Zum Inhalt
flowplanDokumentationCommunityMarketplaceZur Startseite

API und Integrationen

OpenAPI und SDKs

Jede Instanz beschreibt ihre API selbst, als OpenAPI 3.1-Dokument:

6 Min. Lesezeit

Auf dieser Seite
  1. Clients
  2. Tags und Eigenschaften per API
  3. Datensatz-Versionen
  4. Sammlungen per API
  • GET /api/openapi.json – das Dokument. Der Eintrag servers trägt die Adresse der Instanz (APP_URL).
  • /api/docs – dieselbe Beschreibung zum Lesen: alle Pfade nach Bereichen, Parameter, Anfragen, Antworten und Schemas.

Beides ist nur für Angemeldete und mit einem API-Token (Authorization: Bearer fp_…, jede Berechtigung) erreichbar; ohne Anmeldung antwortet /api/openapi.json mit 401, und /api/docs bittet um die Anmeldung. Instanzen, die die Beschreibung öffentlich zeigen wollen, setzen FLOWPLAN_PUBLIC_API_DOCS=true; Besucher sehen dann unter info.version nur die Hauptversion (0.x), Angemeldete die genaue.

curl -H "Authorization: Bearer fp_…" https://flowplan.example.com/api/openapi.json > openapi.json

Darin stehen alle Endpunkte, die ein API-Token aufrufen darf: Arbeitsbereich und Seiten, Suche, Dateien, Importe und Exporte, Webhooks, der MCP-Server und POST /api/command mit einem Schema je häufig genutzter Aktion (page.create, row.create, row.update, webhook.delete …) sowie der Liste aller übrigen Aktionen. Zu jedem Endpunkt sind die Fehlercodes (400, 401, 403, 404, 409, 413, 429) und die Ratenbegrenzungen vermerkt; Kalender-Abos und CalDAV sind als externe Zugänge mit eigenem Link genannt.

Hochgeladene Bilder haben einen Platzhalter: Die Antwort von POST /api/upload nennt unter placeholder die Größe, in der das Bild erscheint (w, h), und einen ThumbHash (öffnet in neuem Tab) (hash, Base64) – eine unscharfe Vorschau in rund 25 Bytes. GET /api/files/placeholders?ids=… liefert dasselbe für bis zu 100 Dateien und Profilbilder (avatar:<Benutzer-ID>), aber nur für Dateien, die du auch herunterladen darfst; alles andere fehlt in der Antwort. Für ältere Dateien wird der Platzhalter bei der ersten Anfrage berechnet. Dateien verschlüsselter Bereiche haben nie einen – der Server sieht ihren Inhalt nicht.

Das Dokument entsteht aus dem Code: Wo der Server Eingaben mit zod prüft (Eigenschaften und Ansichten einer Datenbank, die Aktionen, Export-Optionen), werden dieselben Schemas übernommen; der Rest ist in lib/openapi.ts aus den Routen beschrieben. Ein Test hält beides zusammen – jede Route unter app/api, jede Aktion von command() und die SDKs.

Clients

Aus dem Dokument entstehen zwei schlanke Clients ohne Abhängigkeiten, im Quellcode unter sdk/ (öffnet in neuem Tab):

  • TypeScript (sdk/typescript/flowplan.ts): eine Datei, ESM, auf fetch aufgebaut, mit Typen für alle Antworten und Aktionen. Die Datei ins Projekt kopieren, fertig:
import { FlowplanClient } from "./flowplan.js";
const client = new FlowplanClient({ baseUrl: "https://flowplan.example.com", token: "fp_…" });
const { workspace, pages } = await client.bootstrap();
const { id } = await client.command({ action: "row.create", pageId: "<Datenbank-ID>", cells: { title: "Aus dem SDK" } });
  • Python (sdk/python/flowplan_client.py): nur Standardbibliothek (urllib), Python 3.11 oder neuer, TypedDicts für die Antworten:
from flowplan_client import FlowplanClient
client = FlowplanClient("https://flowplan.example.com", token="fp_…")
shell = client.bootstrap()
created = client.command({"action": "row.create", "pageId": "<Datenbank-ID>", "cells": {"title": "Aus dem SDK"}})

Die Methoden heißen wie die operationId des Dokuments (getPage / get_page, search, uploadFile / upload_file, markdownExport / markdown_export …). Antworten außerhalb von 2xx werfen FlowplanError mit Status, Meldung des Servers und bei 429 der Wartezeit. Jedes SDK bringt eine README und ein Beispielskript mit.

Beide Clients schicken bei jeder Änderung von selbst einen Idempotency-Key mit und wiederholen selbst – GET-Anfragen und Änderungen nach einem Netzwerkfehler, 429, 502, 503, 504 oder 409, solange der erste Versuch noch läuft, standardmäßig zweimal (retries, 0 schaltet es ab). Änderungen gehen mit demselben Schlüssel erneut und wirken so nur einmal. Um eine Aktion später zu wiederholen, gib einen eigenen Schlüssel mit: idempotencyKey (TypeScript, in den Optionen eines Aufrufs) oder idempotency_key= (Python).

Wer Flowplan selbst baut, erzeugt die Clients mit npm run sdk neu; der Test tests/openapi.test.ts schlägt fehl, wenn die eingecheckten Dateien nicht mehr zum Dokument passen.

Tags und Eigenschaften per API

  • GET /api/tags?workspace=<id> – Tags (id, name, color, position, pages, records: Zahl der lesbaren Datenbank-Einträge mit dem Tag) und Eigenschaften (id, name, type, options) des Arbeitsbereichs. GET /api/tags/<tagId>?workspace=<id> liefert zusätzlich die lesbaren Seiten (pages) und Einträge (records: page_id, row_id, title, database, space_id, updated_at, tags) mit diesem Tag.
  • GET /api/pages/<id> enthält properties: tags (Tag-IDs), values (nach Eigenschafts-ID) und info (erstellt/geändert am/von).
  • POST /api/command mit page.properties: { "action": "page.properties", "pageId": "…", "addTags": ["Kunde"], "values": { "Status": "Offen", "Budget": 1200 } }. Tags und Eigenschaften gehen über ID oder Namen; tags ersetzt alle Tags, addTags/removeTags ändern sie, null entfernt einen Wert.
  • POST /api/command mit page.collapseProperties: { "action": "page.collapseProperties", "pageId": "…", "collapsed": true } klappt die Leiste mit Tags und Eigenschaften für dich ein (false auf, null zurück zur Voreinstellung). Gilt nur für dich, auf allen Geräten; Leserecht auf der Seite genügt.
  • pages.tag vergibt oder entfernt Tags auf bis zu 500 Seiten auf einmal.
  • Eigentümer (für Tags und Eigenschaften eines Bereichs auch dessen Eigentümer): tag.save, tag.delete, tag.merge, tag.reorder, property.save, property.delete, property.reorder. Neue Tags (tag.save ohne tagId) mit spaceId dürfen auch Mitglieder anlegen, die in dem Bereich schreiben dürfen; ohne spaceId (oder null) gilt ein neuer Tag bzw. eine neue Eigenschaft im ganzen Arbeitsbereich (nur Eigentümer).
  • Geltungsbereiche (mehr): Tags und Eigenschaften in GET /api/tags tragen spaceId (null: ganzer Arbeitsbereich); GET /api/tags/<id>?sameName=1 nimmt gleichnamige Tags anderer Bereiche dazu. tag.scope/property.scope ({ workspaceId, tagId | propertyId, spaceId, resolve?: "block" | "copy" | "remove", mergeSameName? }, nur Eigentümer) ändern den Geltungsbereich, meta.usage ({ workspaceId, kind, id, spaceId }) zeigt vorher die Verwendung außerhalb des Ziels.
  • Verschieben: page.moveCheck (wie page.move) listet die Tags und Eigenschaften des bisherigen Bereichs, die das Ziel nicht hat (foreign, mit sameName und candidates). page.move und pages.bulk nehmen dafür meta: { "tags": { "<id>": { "action": "map", "to": "<id>" } }, "properties": { "<id>": { "action": "copy" } } } oder kurz "auto" (gleichnamige zuordnen, sonst kopieren), "copy", "remove". Fehlt die Wahl, antwortet page.move mit 409 statt Werte zu verwerfen.
  • Vorlagen: template.save gehört ohne spaceId zum Bereich der Seite, mit spaceId: null zum ganzen Arbeitsbereich (Eigentümer; Tags des Bereichs: 409 oder spaceMeta: "remove"). template.dependencies ({ workspaceId, templateId, spaceId }) listet, was eine Vorlage eines anderen Bereichs mitbringt (Schlüssel tag:<name>, property:<name>); page.create, template.apply und template.copy ({ workspaceId, templateId, spaceId, meta? }) nehmen die Wahl als meta, template.update ändert mit spaceId den Geltungsbereich einer Vorlage. GET /api/templates liefert space_id und space_name.
  • Text-Eigenschaften (außer dem Titel) enthalten Inline-Markdown – in row.create, row.update, rows.bulk, rows.import, beim Lesen und in Webhooks: **fett**, _kursiv_, ~~durchgestrichen~~, `Code`, [Text](https://…) (nur http, https, mailto), \n für einen Zeilenumbruch. Reiner Text wird maskiert gesendet: ein Backslash vor \ * _ ~ ` [ ]. Siehe Formatierter Text.
  • Datenbank-Eigenschaften mit den Tags als Optionen: in database.update eine multiselect-Eigenschaft mit "tagOptions": true (ihre options setzt der Server). Zellen enthalten Tag-Namen; ein neuer Name in row.create, row.update oder rows.bulk legt den Tag an (Mitglieder mit Bearbeitungsrecht, sonst 403); rows.import lässt Namen weg, die kein Tag werden dürfen. MCP nennt solche Eigenschaften in get_database mit tagOptions: true.
  • In GET /api/search wirken #tag, -#tag, name:wert, OR, NOT, Klammern, created_by:, created:, type:, in: und title: als Filter (Parameter tz: Zeitzone für today usw.) (siehe Suche). MCP-Clients bekommen Tags und Werte mit read_page als Frontmatter.

Datensatz-Versionen

row.update trägt die version des Datensatzes, die du gelesen hast. Hat sich der Datensatz seitdem geändert, antwortet der Server mit 409 – außer du schickst zusätzlich base mit: die Werte der geänderten Eigenschaften, so wie du sie gelesen hast. Dann wird die Änderung übernommen, solange niemand diese Eigenschaften inzwischen geändert hat; nur ein echter Konflikt (jemand hat dieselbe Eigenschaft geändert) bekommt die 409. Die Antworten auf row.update und die anderen Änderungen eines Datensatzes (row.move, row.schedule …) enthalten seine neue rowVersion; die schickst du mit der nächsten Änderung.

Sammlungen per API

  • GET /api/collections?workspace=<id> – deine und die mit dem Arbeitsbereich geteilten Sammlungen in deiner Reihenfolge: id, name, icon, query, shared, mine, canEdit, ownerName.
  • GET /api/collections/<id>?tz=Europe/Berlin&offset=0&limit=500 – führt eine Sammlung aus. Du bekommst collection, items (pageId, rowId, type, title, icon, spaceId, database, tags, createdAt, updatedAt, createdBy, creatorName; zuletzt geänderte zuerst, je Seite limit Treffer, Standard 500, höchstens 5000), total, offset, nextOffset (der offset der nächsten Seite, null auf der letzten), truncated (weitere Seiten folgen) und encryptedLeftOut. Die Treffer folgen immer den Rechten des Tokens bzw. der Person.
  • GET /api/collections/preview?workspace=<id>&q=<Abfrage> – die ersten 50 Treffer einer nicht gespeicherten Abfrage und ihre Normalform (query).
  • POST /api/command:
    • collection.create { workspaceId, name, query, icon?, shared? }
    • collection.update { collectionId, name?, query?, icon?, shared? }
    • collection.delete / collection.restore { collectionId }
    • collection.duplicate { collectionId, name? }
    • collection.reorder { workspaceId, order: [ids] } Die Abfrage wird in ihrer Normalform gespeichert und mit query zurückgegeben. search.save und search.delete funktionieren weiter und legen bzw. löschen Sammlungen.
  • MCP: list_collections, run_collection (lesen), save_collection, delete_collection (schreiben).
War das hilfreich?
JaDanke!
Nein – sag uns, was fehlt

Fehler gefunden oder etwas fehlt?Auf GitHub bearbeiten