The same API the canvas UI uses, served on bind_addr:health_check_port (default
127.0.0.1:3000).
Auth. Every route except GET /api/health, GET /api/ready and the login endpoints requires a bearer
token (Authorization: Bearer <token>: the legacy api_token, an admin, or a named opx_... token) or an
SSO session cookie (non-GET also needs X-Opex-CSRF). Each route has a minimum role (viewer < operator <
editor < admin); a lower role gets 403 {"error", "required_role", "role"}. WebSockets accept
?token=<token>. Every response carries X-Request-Id. Unknown /api/* paths return
404 {"error":"not found"}; failures use real 4xx/5xx status codes. See Security.
| Method & path | Description |
|---|---|
GET /api/health |
Liveness (public). |
GET /api/ready |
Readiness (public): 200 {"ready":true} or 503. With ha: it adds the role: 200 {"ready":true,"role":"active"} on the serving node, 503 {"ready":false,"role":"standby"} on a standby (use it as the load-balancer check; /api/health stays 200). |
GET /api/status, /api/health/report |
Connector + adapter status (includes an ha object: role, node, leader, takeovers; {"enabled":false} without HA; an opcua_server object (endpoint, sessions, devices; {"enabled":false} when off), per-sink sinks[]: published / dropped / connected / last_error / backlog, and engine: flow version, loss counter and executor stats such as unrouted, queue_depth, node_latency); edge health snapshot. |
GET /api/devices, /api/devices/{id} |
Known devices + status. |
GET /api/history, /api/history/devices |
Retained records (device_id, from, to, limit). |
GET /api/sources, POST /api/sources, DELETE /api/sources/{key} |
Sources + live status; add/remove at runtime (persisted to sources.runtime.json). |
POST /api/sources/{key}/inject |
Fire an inject source. |
POST /api/ingest/{source}/{id}?wait_ms= |
Push a JSON object into the flow source matching {source}:{id} or {source}:* (never *) and wait for the outcome: 200 done, 422 failed, 503 retry, 202 pending, with error and the printer jobs the record caused. See Coding printers. |
POST /api/hooks/{name} |
Signed webhook ingest into the flow source webhook:{name}. No bearer token: authenticated by the hook’s HMAC signature (doku or hmac_sha256); 401 bad signature, 404 unknown hook, 413 too large, then 200/202/422/503 as for ingest (on_receipt answers 200 {"result":"accepted"}). The record is the JSON body plus _hook. Counters in GET /api/status → engine.webhooks[]. See Signed webhooks. |
GET /api/printers, GET /api/printers/lines, GET/PUT/DELETE /api/printers/{id} |
Coding printers (config incl. line, connection, job counters); printers grouped by production line { lines: [{ line, printers }] }; add/replace/remove (persisted to printers.runtime.json). |
POST /api/printers/import[?dry_run=true] |
Bulk create/replace from a YAML printers: list (the config / deploy/*/printers.yaml shape), a bare list, or JSON (Content-Type: application/json). Everything is validated first and applied atomically (200 { ok, create[], update[], errors[] }), or nothing is (422, same body). dry_run=true only reports { create, update, errors }. |
POST /api/printers/{id}/status, POST /api/printers/{id}/jobs |
Live status exchange with a printer; run a job by hand. |
GET /api/printers/jobs?printer=&invocation=&before=&limit= |
Printer job log, newest first. |
GET /api/flow |
The flow document (the canvas; the single source of truth for routing): { version, flow: { flows, nodes, edges }, seeded, warnings }. flows is a list of named canvases { id, name, disabled? } (default one main); every node has a flow id (default: the first flow). Flows only group nodes: wires never cross flows, match stays unique across all of them, and a disabled flow behaves as if all its nodes were disabled. seeded is true until the first deploy (the document was built from the config’s transform section and legacy state). warnings are the issues that need no compile (e.g. an unconnected node, a sink that is not configured on this connector). |
PUT /api/flow |
Deploy { flow, if_version }: validates and compiles every source, persists <data_dir>/flow.json atomically, then swaps all pipelines in one step. 200 { version, warnings }; 409 { error, version } when if_version is stale; 422 { errors, warnings } (each issue { node_id?, edge_id?, message }) and the running flow is untouched. Deploys are serialised. |
POST /api/flow/validate |
{ flow } → { errors, warnings } without deploying. |
POST /api/flow/test |
Dry run of one source: { flow, source_node, record? } → { ok, record_used, steps[], sink_effects[] } (per-node input / output / error / duration_us; no sink or HTTP side effects). Without record, the last real record seen for the source’s match is used (404 if none yet). |
GET /api/flow/last-record?match= |
Last real record seen for a key matching match (*, {source}:*, {source}:{machine}): { key, record, ts }, or 404. |
POST /api/script/validate, POST /api/script/generate, GET /api/script/ai-status |
Syntax-check a transform script ({ backend, script } → { valid, error? }); AI-generate or edit one ({ instruction, current?, history?, sample_record?, match?, target? }, validated + dry-run; see AI generator); AI setup status { configured, missing?, model?, base_url? }. |
GET /api/pipeline/trace/{id}, /api/pipeline/traces |
Sampled execution traces. |
GET /api/pipeline/dead-letters, GET|PUT /api/pipeline/dead-letters/{id}, POST /api/pipeline/dead-letters/{id}/replay, POST /api/pipeline/dead-letters/{id}/discard, POST /api/pipeline/dead-letters/replay, POST /api/pipeline/dead-letters/discard, GET /api/pipeline/dead-letters/audit |
The pipeline dead-letter queue: list (status, source, node, q, since, until), inspect, correct ({value?, origin?}), replay into the source or a node ({to, node, wait_ms}), discard, bulk by filter, and the audit trail (the actor is the authenticated identity). Writes need operator. See Dead letters. |
GET /api/reconcile/reports, GET /api/reconcile/reports/{id}, GET /api/reconcile/reports/{id}/csv, GET /api/reconcile/ledger, GET /api/reconcile/ledger/entries |
Reconciliation reports of reconcile nodes (filter name, day, ok), one report with its exceptions, the CSV export, and the hub-side ledger per ledger / side / day. See Reconciliation reports. |
GET /api/cases, /api/cases/summary, /api/cases/csv, GET /api/cases/{id}, POST /api/cases/{id}/assign|comment|status|link, POST /api/cases/bulk, POST /api/cases/from-report/{id}, GET|POST /api/cases/digest |
Exceptions workbench: every reconcile exception is a case (open, investigating, resolved, ignored) with assignee, comments, links, ageing, CSV and a daily digest; operators work them, every change is audited. See Finance controls. |
GET /api/approvals, GET /api/approvals/{id}, POST /api/approvals/{id}/approve|reject|resume |
Records parked by approve nodes: the inbox, decide (the node’s role / users, a rejection needs a comment), resume an interrupted one. See Finance controls. |
GET /api/posting, GET|POST /api/golive/requests, POST /api/golive/requests/{id}/approve|reject|cancel |
Go-live gate: posting modes per node with their sign-off; an editor requests, a second designated approver approves switching a ${var:} posting mode to live in a protected environment (PUT /api/vars answers 409 go_live_required). See Finance controls. |
GET /api/openapi, PUT /api/openapi/{name} |
Vendored OpenAPI specs for http nodes. |
GET /api/discovery, /api/discovery/pi; GET/POST /api/mapping, POST /api/mapping/csv |
Tag discovery + mapping table. |
GET /api/backfill |
Backfill replay progress. |
GET /api/push/status, GET/DELETE /api/push/dead, POST /api/push/dead/replay |
Push queue health; inspect / purge / replay permanently failed pushes. |
GET /api/config |
Running config with secrets redacted (***, including the token in push_endpoint). |
POST /api/config/reload |
Re-read the config file; applies push settings and poll interval (adapters need a restart). |
GET /api/license/status |
License state: { enabled, state: "valid"|"offline_grace"|"invalid"|"unlicensed"|"disabled", plan, expires_at, offline_valid_until, activation_id_short, device_id_short, key_last4, key_source: "config"|"ui"|null, last_check_at, reason?, error? } (Unix seconds; no secrets, the key shows only its last 4 characters). |
POST /api/license/activate { key }, POST /api/license/revalidate, POST /api/license/deactivate |
Enter or replace the key (stored in license.json), check now, free this device’s seat. 200 returns the new status; failures are { ok: false, error, reason }: 400 bad input, 409 not allowed here (licensing disabled, key set in the config file, not activated), 422 the worker refused (invalid, revoked, suspended, expired, seats_exhausted, deactivated), 502 worker unreachable (network). See Licensing. |
GET /metrics, GET /api/obs/{traces,series,alerts} |
Observability. Requires a build with the telemetry feature. |
POST /api/obs/test |
Send a test alert to every configured channel (telemetry.alerts: webhook, Telegram, email, Teams, WhatsApp). Every build. Returns {ok, results, channels:[{channel, ok, detail}]}. See Alerts. |
GET /api/schedules, POST /api/schedules/preview |
Deployed schedule:{id} sources with next runs and last tick outcome; preview the next fire times of {cron, timezone?, count?} (422 {ok:false, error} when invalid). See Schedule source. |
GET /api/health/sla?range= |
Health dashboard data: runs, errors, retries, availability, p50/p95/p99 and sparkline series per flow and per external system, SLA state and open incidents. Every build. See SLA dashboard. |
GET /api/reports/months, GET /api/reports/monthly?month=&format=json|html|csv&part=, POST /api/reports/monthly/send |
Monthly service report (HTML, CSV tables, JSON) and mailing it now. See Monthly service report. |
GET /api/siem/status, POST /api/siem/test |
SIEM export: sinks and counters; one test event per sink. See SIEM export. |
GET /api/openapi/published, GET /api/openapi/published.yaml |
OpenAPI 3.1 of every interface the connector exposes (webhooks with their signature schemes, ingest keys, app actions, mock services, management API). See Published OpenAPI. |
GET /api/events, GET|PUT|DELETE /api/events/{name}, PUT /api/events, POST /api/events/{name}/validate, GET /api/events/asyncapi, GET /api/events/log |
The business-event catalogue (JSON Schema, owner, producers and consumers derived from the flow), its AsyncAPI 3.0 export and the event history. See Business events. |
GET /api/lookups, PUT|DELETE /api/lookups/{name}, GET|POST /api/lookups/{name}/entries, POST /api/lookups/{name}/entries/delete, POST /api/lookups/{name}/import, GET /api/lookups/{name}/csv, GET /api/lookups/{name}/resolve |
Lookup tables (code lists, cross-references): entries, CSV import with dry run, CSV export, a single lookup. See Lookup tables. |
GET /api/environment |
The instance’s environment {configured, name, color, protected} (badge; protected refuses direct deploys). See Environments. |
WS /api/ws/collab, GET /api/collab/draft, POST /api/collab/deploy, POST /api/collab/discard |
The shared flow draft: realtime co-editing and presence socket (any role may watch, editor edits), the draft’s state and unsaved count, deploy the draft as a version with co-authors, discard it for everyone. See Editing together. |
GET /api/versions, GET /api/versions/{n}, GET /api/versions/diff?from=&to=, POST /api/versions/{n}/rollback |
Version history of every deploy (actor, message, hash, provenance), node/edge/field diff, rollback = redeploy N as a new version. PUT /api/flow takes an optional message. See Version history. |
GET /api/vars, PUT|DELETE /api/vars/{name} |
${var:NAME} variables of this instance (editor reads, admin writes). See Variables. |
GET /api/promotion, POST /api/promotions/send, POST /api/promotions, GET /api/promotions, GET /api/promotions/{id}, POST /api/promotions/{id}/approve|reject |
Signed promotion between environments: send a version to a target, receive a bundle (Ed25519, trusted sources), inbox with diff against live, approve (deploys as a new version with provenance) or reject. See Promotion. |
GET /api/apps, GET|PUT|DELETE /api/apps/{id}, POST /api/apps/validate, POST /api/apps/{id}/publish|unpublish|revert, GET /api/apps/{slug}/runtime, POST /api/apps/{slug}/data/{source}, POST /api/apps/{slug}/actions/{action} |
Apps: JSON-defined web apps. Definitions and publishing need editor; the runtime endpoints check each source’s and action’s roles on the server. See Apps. |
/mcp |
The MCP server for AI agents (streamable HTTP, same bearer token). Its tools call the same handlers as the endpoints above. |
GET /api/auth/config (public), GET /api/auth/me, POST /api/auth/logout, GET /api/auth/oidc/login, GET /api/auth/oidc/callback (public) |
Sign-in: the login options ({oidc:{enabled, login_url, provider}, token_login}), the caller ({kind: user|token|legacy, name, email?, role, csrf?, session_expires_at?, mfa}), end a browser session, and the OIDC authorization-code + PKCE redirect / callback. See Security. |
GET /api/auth/users, PUT /api/auth/users/{id}, GET|POST /api/auth/tokens, DELETE /api/auth/tokens/{id} |
admin: SSO users (role override {role_override, disabled}) and named API tokens (POST {name, role, expires_in_days?} → 201 {token, info}, the secret shown once). |
GET /api/secrets, PUT|DELETE /api/secrets/{name} |
admin, audited: the local secrets behind ${secret:name}. GET returns names and change times only ({local:{writable, items}, vault:{configured}}); PUT {value} is write-only (409 without OPEX_MASTER_KEY). See Data protection. |
GET /api/audit?actor=&action=&target=&result=&since=&until=&limit=&before=, GET /api/audit/csv |
admin: the append-only audit log (who, when, action, target, result, request id), newest first with next_before paging, and its CSV export. See Audit log. |
WS /api/ws/live |
Live record stream. |
WS /api/ws/events |
Live business events delivered to a hub sink (?type= filters, ?token= for browsers). |
WS /api/ws/flow |
Flow telemetry, see below. |
WS /api/ws/collab |
Shared draft and presence, see Editing together. |
Flow WebSocket
GET /api/ws/flow?token= pushes JSON messages:
- every second
{ "type": "stats", "ts", "nodes": { "<node id>": { "in", "out", "err", "rate", "avg_us", "last_ts", "last_error" } }, "unrouted", "sources": { "<match>": { "rate", "last_ts" } } }— counters are cumulative since start and aggregated per canvas node (a node shared by several sources counts once);rateis frames in per second over the last 5 s;unroutedcounts records no source node matched. { "type": "deployed", "version" }to every client after a successful deploy.- debug events, only after you subscribe: send
{ "debug": "all" },{ "debug": ["n2", "n7"] }or{ "debug": "off" }; then{ "type": "event", "ts", "node_id", "key", "phase": "in"|"out"|"drop"|"error", "payload", "error"? }(payloads over ~2 KB become{ "_truncated": true, "preview": "…" }). At most 50 events/s per client; overflow is reported as{ "type": "dropped", "count" }once a second. With nobody subscribed the engine builds no event payloads.