API reference

Workflows

Workflows are graphs of nodes and wires (similar to Node-RED): a run begins at a start node – Start (manual/schedule), HTTP input under /api/v1/flows/<pfad> or collection event – and a message msg (data in msg.payload) travels along the wires. Usage, nodes and examples are described in the manual.

Workflow object

{
  "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
}
Field Meaning
enabled Switches HTTP inputs, schedules and events; manual runs always work
flow.nodes id (free, unique), type, name (display), x/y (position in the editor), config per type (see below)
flow.wires Wire from output out (starting at 0) of node from to the input of to. Start nodes have no input
last_run_at, last_status Read-only: last run (milliseconds) and ok/error
next_run Read-only: next scheduled run of all start nodes (Unix seconds), 0 = none

Node types and config:

type Outputs config
inject 1 payload (text, read as JSON if possible), cron (five fields, server time zone or CRON_TZ=…; @every not allowed)
http-in 1 method (GET … DELETE, ANY), path (placeholder {name}, at the end {name...}), public
event 1 collection (slug), created, updated, deleted (bool)
function outputs (1–10) code, outputs
switch one per rule property (path 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 ("" automatic, payload, none, custom with bodyTemplate), ret (auto, json, text), failOnError
collection 1 action (list get create update delete), collection, id (default {{ msg.payload.id }}), onlyPublished, published ("", yes, no)
illusiondb 1 connection (name, see IllusionDB connections), action (list get create update delete schema tables), table (name, Datenquelle/name or ID). list: rules ([{ "col", "op", "val" }]; op: eq neq gt gte lt lte like in not_in is_null is_not_null; in values separated by commas), match (and/or), keepEmpty (don’t omit rules with an empty value), where (filter tree of the IllusionDB API as JSON, replaces rules), sort ([{ "col", "desc" }]), group ([{ "col" }]), summary ([{ "col", "fn" }], fn: SUM COUNT AVG MIN MAX), view (ID of a saved view), limit (≤ 1000), page (starting at 1), all (all pages, ≤ 10,000), fields (comma-separated: load only these columns – ?fields= of the IllusionDB API, also for get; unknown column = error, older versions return all), output (records, full = { records, total, limit, offset, page, pages, has_more, summary, groups }, groups, first). get: field + value (search via one column) or pk (default {{ msg.payload.id }}); not found = null, with failOnMissing an error. update/delete: pk. create/update: columns from msg.payload. cache: seconds for which read results are remembered
delay 1 seconds (0 to 300, fractions allowed) – other branches keep running
join 1 count (0 = as many as incoming wires), mode (array = list of payloads, object = object keyed by msg.topic)
mail 1 to (comma-separated), subject, body (empty = msg.payload), replyTo, html – SMTP access via /system/mail
http-response 0 status (0 = msg.statusCode, otherwise 200), headers
debug 0 property (payload, msg or path)
custom per definition node (key), params, inputs, outputs, cron – see Custom nodes

In url, header values, bodyTemplate, id, the fields of mail and of illusiondb (connection, table, pk, value, rule values, limit, page, view; in where as a JSON value or, inside strings, as text), {{ msg.pfad }} is replaced (environment variables are not available in workflows).

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

The workflow’s storage (store.get/set in the function node): GET returns [{ "key": "zuletzt", "value": "42", "updated_at": 1791369600 }] (value as JSON text). DELETE ?key=zuletzt deletes one key, without key all of them. Not in the bundle.

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

All workflows (by name) or a single one.

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

Create (201), change, delete (204). PUT only applies the fields sent (the flow always as a whole), supports base_updated_at and first saves a version in the version history (kind=workflow; restoring keeps the name and enabled). 400 for an invalid flow (unknown type, wire to a missing output or into a start node, invalid path or schedule …), 409 if an HTTP input with that path and method already exists in another active workflow.

POST /workflows/{id}/run ✏️

Starts a run and waits for it to finish (time limit 5 minutes). Body optional:

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

node is the start node (without it: the first inject), payload replaces its stored value, flow runs an unsaved state (this is how the editor tests). For http-in, an msg.req is created without a real request. Response 200 – even if the run fails:

{
  "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 contains debug output, node.warn/console.log and errors; trace contains, per node execution, the input and the messages sent on each output (JSON, truncated; "" = nothing sent). response only if an HTTP response node was reached.

Custom nodes

Reusable script nodes (manual). In the flow: { "type": "custom", "config": { "node": "<key>", "params": { … }, "inputs": 1, "outputs": 2, "cron": "" } } – inputs/outputs reflect the state of the definition (for wires; the admin syncs them when opening), but the current definition is always what runs. inputs: 0 = start node (with cron like inject). Saving a workflow with unknown keys is rejected with 400.

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 (requires options). name must be a JS identifier (config.name). outputs: 0–10 labels (start nodes at least 1). The code is checked for syntax errors when saving (400).

  • GET /flow-nodes 🔑 · GET /flow-nodes/{id} 🔑
  • POST /flow-nodes ✏️ – key optional (otherwise derived from the name); taken: 409
  • PUT /flow-nodes/{id} ✏️ – everything except key; with base_updated_at conflict check (409). Versioned (kind=flownode in the version history)
  • DELETE /flow-nodes/{id} ✏️ – 409 as long as a workflow uses the node
  • GET /flow-nodes/{id}/usage 🔑 – names of the workflows that use it
  • POST /flow-nodes/test ✏️ – { "node": { …Definition, auch ungespeichert }, "params": { … }, "msg": { … } } → { "status": "ok", "error": "", "outputs": ["<JSON>", ""], "logs": [ … ], "duration_ms": 3 } (the message sent on each output, "" = nothing); not logged

Custom nodes are content: table flow_nodes in the bundle.

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

The last 50 runs of all triggers (manual, cron, http, event, template – call via workflow() from a page), newest first, in the format of run above – or clear the log. Runs are not part of bundles; the workflows themselves are.

HTTP input: /api/v1/flows/{pfad}

Path and method are determined by the HTTP input node. msg.payload is the JSON body (application/json), the form body (application/x-www-form-urlencoded) as an object, otherwise the text – without a body, the query parameters. msg.req contains method, path, params, query (without token), headers (lowercase, without Cookie/Authorization) and body. At most 10 MB, time limit 30 seconds.

Result Response
HTTP response node reached its status, headers and msg.payload (object → JSON, text → text/plain)
no HTTP response node, no error 204
error in the flow 500 with { "error": "workflow “Name”: “Node”: … (line 3, column 7)" }

Other statuses: 404 (no active workflow for the path), 405 (path matches, method doesn’t), 401/403 for protected inputs (token or admin session; write methods via session need X-IC-CSRF). Public inputs send Access-Control-Allow-Origin: * and answer the 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 events

After every successful POST, PUT or DELETE on /collections/{ref}/items…, workflows with a matching event node start in the background (msg.payload = entry, on deletion the state before; msg.event = created/updated/deleted; msg.collection = slug). If workflows trigger further events through their changes, the chain ends after three levels.