API-Referenz

Workflows

Workflows sind Graphen aus Nodes und Drähten (ähnlich Node-RED): Ein Lauf beginnt an einem Start-Node – Start (manuell/Zeitplan), HTTP-Eingang unter /api/v1/flows/<pfad> oder Collection-Ereignis – und eine Nachricht msg (Daten in msg.payload) läuft über die Drähte. Bedienung, Nodes und Beispiele beschreibt das Handbuch.

Workflow-Objekt

{
  "id": 3,
  "name": "Bar-Board Karte",
  "description": "",
  "enabled": true,
  "flow": {
    "nodes": [
      { "id": "in", "type": "http-in", "name": "", "x": 60, "y": 120, "config": { "method": "GET", "path": "karte", "public": true } },
      { "id": "list", "type": "collection", "name": "", "x": 300, "y": 120, "config": { "action": "list", "collection": "drinks", "onlyPublished": true } },
      { "id": "out", "type": "http-response", "name": "", "x": 540, "y": 120, "config": { "status": 0, "headers": [] } }
    ],
    "wires": [ { "from": "in", "out": 0, "to": "list" }, { "from": "list", "out": 0, "to": "out" } ]
  },
  "created_at": 1791259204,
  "updated_at": 1791259204,
  "last_run_at": 1791259233123,
  "last_status": "ok",
  "next_run": 0
}
Feld Bedeutung
enabled Schaltet HTTP-Eingänge, Zeitpläne und Ereignisse; manuelle Läufe gehen immer
flow.nodes id (frei, eindeutig), type, name (Anzeige), x/y (Position im Editor), config je Typ (siehe unten)
flow.wires Draht von Ausgang out (ab 0) des Nodes from zum Eingang von to. Start-Nodes haben keinen Eingang
last_run_at, last_status Nur lesend: letzter Lauf (Millisekunden) und ok/error
next_run Nur lesend: nächster geplanter Lauf aller Start-Nodes (Unix-Sekunden), 0 = keiner

Node-Typen und config:

type Ausgänge config
inject 1 payload (Text, als JSON gelesen, wenn möglich), cron (fünf Felder, Zeitzone des Servers oder CRON_TZ=…; @every nicht erlaubt)
http-in 1 method (GET … DELETE, ANY), path (Platzhalter {name}, am Ende {name...}), public
event 1 collection (Slug), created, updated, deleted (bool)
function outputs (1–10) code, outputs
switch je Regel property (Pfad in msg), rules ([{ "op": "eq", "value": "x" }]; op: eq neq lt lte gt gte contains regex true false empty nempty else), all
http-request 1 method, url, headers ([{ "name", "value" }]), body ("" automatisch, payload, none, custom mit bodyTemplate), ret (auto, json, text), failOnError
collection 1 action (list get create update delete), collection, id (Standard {{ msg.payload.id }}), onlyPublished, published ("", yes, no)
illusiondb 1 connection (Name, siehe IllusionDB-Verbindungen), action (list get create update delete schema tables), table (Name, Datenquelle/name oder ID). list: rules ([{ "col", "op", "val" }]; op: eq neq gt gte lt lte like in not_in is_null is_not_null; in-Werte mit Komma), match (and/or), keepEmpty (Regeln mit leerem Wert nicht weglassen), where (Filterbaum der IllusionDB-API als JSON, ersetzt rules), sort ([{ "col", "desc" }]), group ([{ "col" }]), summary ([{ "col", "fn" }], fn: SUM COUNT AVG MIN MAX), view (ID einer gespeicherten Ansicht), limit (≤ 1000), page (ab 1), all (alle Seiten, ≤ 10 000), fields (Komma-getrennt: nur diese Spalten laden – ?fields= der IllusionDB-API, auch bei get; unbekannte Spalte = Fehler, ältere Versionen liefern alle), output (records, full = { records, total, limit, offset, page, pages, has_more, summary, groups }, groups, first). get: field + value (Suche über eine Spalte) oder pk (Standard {{ msg.payload.id }}); nicht gefunden = null, mit failOnMissing Fehler. update/delete: pk. create/update: Spalten aus msg.payload. cache: Sekunden, die Leseergebnisse gemerkt werden
delay 1 seconds (0 bis 300, auch Bruchteile) – andere Zweige laufen weiter
join 1 count (0 = so viele wie eingehende Drähte), mode (array = Liste der payloads, object = Objekt nach msg.topic)
mail 1 to (Komma-getrennt), subject, body (leer = msg.payload), replyTo, html – SMTP-Zugang über /system/mail
http-response 0 status (0 = msg.statusCode, sonst 200), headers
debug 0 property (payload, msg oder Pfad)
custom laut Definition node (Key), params, inputs, outputs, cron – siehe Eigene Nodes

In url, Header-Werten, bodyTemplate, id, den Feldern von mail und von illusiondb (connection, table, pk, value, Regel-Werte, limit, page, view; in where als JSON-Wert bzw. in Strings als Text) wird {{ msg.pfad }} ersetzt (Umgebungsvariablen stehen in Workflows nicht zur Verfügung).

GET /workflows/{id}/store ✏️ · DELETE /workflows/{id}/store ✏️

Speicher des Workflows (store.get/set im Function-Node): GET liefert [{ "key": "zuletzt", "value": "42", "updated_at": 1791369600 }] (value als JSON-Text). DELETE ?key=zuletzt löscht einen Schlüssel, ohne key alle. Nicht im Bundle.

GET /workflows 🔑 · GET /workflows/{id} 🔑

Alle Workflows (nach Name) bzw. einer.

POST /workflows ✏️ · PUT /workflows/{id} ✏️ · DELETE /workflows/{id} ✏️

Anlegen (201), ändern, löschen (204). PUT übernimmt nur die gesendeten Felder (der flow immer als Ganzes), unterstützt base_updated_at und sichert vorher eine Version in der Versionshistorie (kind=workflow; beim Wiederherstellen bleiben Name und enabled). 400 bei ungültigem Flow (unbekannter Typ, Draht an einen fehlenden Ausgang oder in einen Start-Node, ungültiger Pfad oder Zeitplan …), 409, wenn ein HTTP-Eingang mit Pfad und Methode schon in einem anderen aktiven Workflow steckt.

POST /workflows/{id}/run ✏️

Startet einen Lauf und wartet auf das Ende (Zeitlimit 5 Minuten). Body optional:

{ "node": "in", "payload": { "monat": "2026-10" }, "flow": { "nodes": [], "wires": [] } }

node ist der Start-Node (ohne: der erste inject), payload ersetzt dessen hinterlegten Wert, flow führt einen ungespeicherten Stand aus (so testet der Editor). Bei http-in wird ein msg.req ohne echte Anfrage erzeugt. Antwort 200 – auch wenn der Lauf fehlschlägt:

{
  "run": {
    "id": 41, "workflow_id": 3, "trigger": "manual", "status": "ok", "error": "", "result": "HTTP 200\n[…]",
    "logs": [{ "level": "debug", "node": "dbg", "label": "Debug", "message": "{ … }", "time": 1791259233124 }],
    "trace": [{ "node": "in", "time": 1791259233123, "ms": 0, "in": "{…}", "out": ["{…}"] }],
    "started_at": 1791259233123, "duration_ms": 2
  },
  "response": { "status": 200, "headers": { "Content-Type": "application/json; charset=utf-8" }, "body": "[…]" }
}

logs enthält Debug-Ausgaben, node.warn/console.log und Fehler; trace je Node-Ausführung Eingang und die an jedem Ausgang gesendeten Nachrichten (JSON, gekürzt; "" = nichts gesendet). response nur, wenn ein HTTP-Antwort-Node erreicht wurde.

Eigene Nodes

Wiederverwendbare Skript-Nodes (Handbuch). Im Flow: { "type": "custom", "config": { "node": "<key>", "params": { … }, "inputs": 1, "outputs": 2, "cron": "" } } – inputs/outputs sind der Stand der Definition (für Drähte; das Admin gleicht sie beim Öffnen ab), ausgeführt wird immer die aktuelle Definition. inputs: 0 = Start-Node (mit cron wie inject). Unbekannte Keys lehnt das Speichern eines Workflows mit 400 ab.

Definition:

{
  "id": 3, "key": "preis-formatieren", "name": "Preis formatieren", "description": "…", "color": "#f472b6",
  "inputs": 1, "outputs": ["ok", "ohne Preis"],
  "params": [{ "name": "waehrung", "label": "Währung", "type": "select", "options": ["€", "$"], "default": "€", "help": "" }],
  "code": "…", "created_at": 1791540000, "updated_at": 1791540000
}

type: text, textarea, number, checkbox, select (braucht options). name muss ein JS-Bezeichner sein (config.name). outputs 0–10 Bezeichnungen (Start-Nodes mindestens 1). Der Code wird beim Speichern auf Syntaxfehler geprüft (400).

  • GET /flow-nodes 🔑 · GET /flow-nodes/{id} 🔑
  • POST /flow-nodes ✏️ – key optional (sonst aus dem Namen); vergeben: 409
  • PUT /flow-nodes/{id} ✏️ – alles außer key; mit base_updated_at Konfliktprüfung (409). Versioniert (kind=flownode in der Versionshistorie)
  • DELETE /flow-nodes/{id} ✏️ – 409, solange ein Workflow den Node verwendet
  • GET /flow-nodes/{id}/usage 🔑 – Namen der Workflows, die ihn verwenden
  • POST /flow-nodes/test ✏️ – { "node": { …Definition, auch ungespeichert }, "params": { … }, "msg": { … } } → { "status": "ok", "error": "", "outputs": ["<JSON>", ""], "logs": [ … ], "duration_ms": 3 } (je Ausgang die gesendete Nachricht, "" = nichts); wird nicht protokolliert

Eigene Nodes sind Inhalt: Tabelle flow_nodes im Bundle.

GET /workflows/{id}/runs 🔑 · DELETE /workflows/{id}/runs ✏️

Die letzten 50 Läufe aller Auslöser (manual, cron, http, event, template – Aufruf per workflow() aus einer Seite), neueste zuerst, im Format von run oben – bzw. Protokoll leeren. Läufe sind nicht Teil von Bundles, die Workflows selbst schon.

HTTP-Eingang: /api/v1/flows/{pfad}

Pfad und Methode bestimmt der HTTP-Eingang-Node. msg.payload ist der JSON-Body (application/json), der Formular-Body (application/x-www-form-urlencoded) als Objekt, sonst der Text – ohne Body die Query-Parameter. msg.req enthält method, path, params, query (ohne token), headers (klein geschrieben, ohne Cookie/Authorization) und body. Höchstens 10 MB, Zeitlimit 30 Sekunden.

Ergebnis Antwort
HTTP-Antwort-Node erreicht dessen Status, Header und msg.payload (Objekt → JSON, Text → text/plain)
kein HTTP-Antwort-Node, kein Fehler 204
Fehler im Flow 500 mit { "error": "workflow “Name”: “Node”: … (line 3, column 7)" }

Weitere Status: 404 (kein aktiver Workflow für den Pfad), 405 (Pfad passt, Methode nicht), 401/403 bei geschützten Eingängen (Token oder Admin-Session; schreibende Methoden per Session brauchen X-IC-CSRF). Öffentliche Eingänge senden Access-Control-Allow-Origin: * und beantworten den OPTIONS-Preflight.

curl https://example.com/api/v1/flows/karte
curl -X POST https://example.com/api/v1/flows/sync -H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" -d '{"voll": true}'

Collection-Ereignisse

Nach jedem erfolgreichen POST, PUT bzw. DELETE auf /collections/{ref}/items… starten Workflows mit passendem event-Node im Hintergrund (msg.payload = Eintrag, beim Löschen der Stand davor; msg.event = created/updated/deleted; msg.collection = Slug). Lösen Workflows über ihre Änderungen weitere Ereignisse aus, endet die Kette nach drei Stufen.