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✏️ –keyoptional (sonst aus dem Namen); vergeben:409PUT /flow-nodes/{id}✏️ – alles außerkey; mitbase_updated_atKonfliktprüfung (409). Versioniert (kind=flownodein der Versionshistorie)DELETE /flow-nodes/{id}✏️ –409, solange ein Workflow den Node verwendetGET /flow-nodes/{id}/usage🔑 – Namen der Workflows, die ihn verwendenPOST /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.