Files
app/docs/code/api.md
T
stroblmeandClaude Opus 5 bdad6d7fc2
Docs / docs (push) Successful in 37s
Playwright Tests / test-playwright (1, 2) (push) Failing after 1m35s
Playwright Tests / test-playwright (2, 2) (push) Failing after 17s
pre-commit / pre-commit (push) Failing after 2m8s
Test Backend / test-backend (push) Failing after 2m48s
Compose Smoke Test / test-compose (push) Failing after 13s
Playwright Tests / merge-reports (push) Failing after 2m25s
Make the docs state things rather than argue them
The site read as a design journal: rationale paragraphs, hedges
("deliberately", "on purpose", "genuinely"), meta-commentary about the docs
themselves, and one em-dash every ten lines carrying an aside.

Roughly twenty rationale blocks are gone or reduced to what a reader needs
in order to use the thing. Em-dashes go from 507 to 135, and what is left is
structural rather than prose: list and definition separators, table cells,
and four inside code blocks that quote what the CLI actually prints.

Also: api.example.com becomes api.fluksio.com (the emails stay, since
bootstrap.py really defaults to admin@example.com and RFC 2606 reserves it);
the mqtt table gains the two settings it had drifted behind on and inject's
wording matches the engine; llms.txt lists the two connector pages that were
in the nav but not in it; and the two device/device_policy notes now agree.

Builds clean under `zensical build --strict`.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015YrQnKV3bnQd4K342y8tKj
2026-08-31 10:49:58 +02:00

11 KiB

The HTTP API

Everything the browser does, the API does first. The dashboard is a generated client of this schema, not a privileged path into the engine, so anything you can click, you can script.

Base URL: https://api.${DOMAIN}/api/v1, or http://127.0.0.1:8000/api/v1 for a fluksio serve instance.

export FLUKSIO=http://127.0.0.1:8000/api/v1

Authenticating

export TOKEN=$(curl -s -X POST $FLUKSIO/login/access-token \
  -d "username=admin@example.com&password=..." | jq -r .access_token)

curl -s $FLUKSIO/users/me -H "Authorization: Bearer $TOKEN"

A bearer token, valid for eight days. POST /login/test-token checks one. Password recovery and reset are POST /password-recovery/{email} and POST /reset-password/.

Agents authenticate differently; see Agents over MCP.

!!! tip "The interactive schema"

When `ENVIRONMENT` is not `production`, the full OpenAPI schema is at
`/docs` (Swagger) and `/redoc`, and the raw document at
`/api/v1/openapi.json`. That is the authoritative reference; this page is
the tour.

It is closed in production: the schema enumerates every endpoint the
instance serves, including paths webhook nodes mounted at runtime.

Flows

Method Path What
GET /flows/ every flow, with node counts, draft and running state
GET /flows/{name} one flow — the draft if there is one — with node status and issues
PUT /flows/{name} save a draft (version must match, or 409)
POST /flows/{name}/publish put the draft live
POST /flows/{name}/discard-draft throw the draft away
POST /flows/{name}/rename rename it
DELETE /flows/{name} delete it
GET /flows/node-types every node type and its parameter schema
GET /flows/graph every flow as one graph — what Home draws

Reading a flow returns the draft when one exists, along with a preview of how it would run: node statuses and validation issues for the version you are editing, not the one running underneath it.

Node source

curl -s $FLUKSIO/flows/house/nodes/decide/source -H "Authorization: Bearer $TOKEN"
curl -X PUT $FLUKSIO/flows/house/nodes/decide/source -H "Authorization: Bearer $TOKEN" \
  -H 'Content-Type: application/json' -d '{"code": "def process(x):\n    return {\"y\": x * 2}\n"}'

POST .../share promotes a node's source to the shared library; POST .../unshare gives it a private copy back. GET /flows/library lists the shared sources and which flows use each.

Running and controlling

Method Path What
POST /flows/{name}/start · /stop activate or tear down its subscriptions and schedules
POST /flows/{name}/pause · /resume hold messages instead of running them
POST /flows/{name}/step release exactly one held message
POST /flows/{name}/validate the issues, without saving
POST /flows/{name}/run run every node once, from the values you pass
POST /flows/{name}/nodes/{id}/trigger feed values into one node
POST /flows/{name}/nodes/{id}/cancel kill the worker running it right now
POST /flows/{name}/nodes/{id}/acknowledge clear the failure the canvas is marking

POST /flows/{name}/run on a batch flow submits a run instead, because that is what running one means: the parameters, the series and the result are the point.

Messages

curl -s $FLUKSIO/messages/ -H "Authorization: Bearer $TOKEN"
curl -X POST $FLUKSIO/messages/house.setpoint -H "Authorization: Bearer $TOKEN" \
  -H 'Content-Type: application/json' -d '{"value": 22.5}'
curl -s $FLUKSIO/messages/house.temperature/history -H "Authorization: Bearer $TOKEN"

GET /messages/ is the catalogue: every message any published flow declares, with its last value. Publishing puts a value into the graph exactly as a dashboard control does, which means only a message some flow declares can be published to. Flows own the namespace; everything else is a client of it.

Runs

Method Path What
POST /runs/flows/{name} queue one run — {"params": {...}, "seed": 7, "draft": false, "no_cache": false}. "cause" says where it came from — api (the default), cli or sdk
POST /runs/flows/{name}/sweep queue many, sharing a group_id
GET /runs the queryable history: ?flow=, ?status=, ?group=, ?digest=, ?since=, ?before=, ?limit=, ?offset=
GET /runs/overview one row per flow that has runs, with how many are running or queued
GET /runs/export/metrics?…&name=&stride=&format= every selected run's series as one long table: run, name, step, ts, value
GET /runs/export/runs?…&params=&metrics=&format= one row per run: its inputs as columns, its final numbers, its status and provenance
GET /runs/{id} one run in full: params, result, per-node record, artifacts
POST /runs/{id}/cancel stop it
GET /runs/{id}/metrics?name=&stride= one metric's series, in step order; every series of the run without name
GET /runs/series/compare?ids=a,b,c&metric=&x= that metric across several runs. x is what to plot against: nothing or step, time (seconds since each run's own first reading), or another metric's name, joined on the step the two share

Submitting answers immediately with a queued run. Wrong parameters (an undeclared name, a value of the wrong type) come back as a 422 naming the problem, before anything executes.

?digest= filters by the hash of the parameters, which is how you find "every run that used exactly this configuration".

?before= is how a long history is paged: rows come newest first, so handing back the last row's created_at reads the next page whatever landed meanwhile, which ?offset= cannot, since a run submitted between two pages shifts every row down one. ?since= is inclusive and bounds the other end.

Both exports stream csv (the default) or jsonl, and take the selection the history takes plus ?ids=a,b,c, ?since= and ?until=. export/runs puts the inputs that vary across the selection in param. columns (the sweep axis) unless ?params= names them, and the run's numbers in metric. columns. Both are dotted paths into whatever a node returned, so ?metrics=final_metrics.train_loss selects a field of a record and the defaults reach every number inside one. The run id is on every row of both, which is what makes an exported file a join back to the run rather than a loose table.

compare answers in the same shape a chart widget draws, so three training curves side by side is a widget binding rather than a screen of its own.

See Runs: pipelines that finish.

Artifacts

curl -X PUT $FLUKSIO/artifacts -H "Authorization: Bearer $TOKEN" \
  --data-binary @model.pt
curl -s $FLUKSIO/artifacts/sha256:abc... -H "Authorization: Bearer $TOKEN" -o model.pt

Content-addressed, so uploading the same bytes twice stores them once. Node code normally reaches these through fluksio.save_artifact / fluksio.load_artifact rather than here.

Dashboards and panels

Method Path What
GET /dashboards/ every dashboard, without its contents
GET /dashboards/{name}?draft=true the published document, or the editor's copy
POST /dashboards/{name} create one
POST /dashboards/from-flow/{flow} build {flow}_results from a batch flow's declared ports — a chart per streamed metric, a stat per output. 409 if it already exists
PUT /dashboards/{name} save a draft
POST /dashboards/{name}/publish · /discard · /rename
GET PUT /panels/ which device shows which dashboards
POST /panels/pair start a pairing
GET /panels/pair/{code} what is holding a code
POST /panels/{id}/unpair drop this panel's credential, keep the panel

Secrets, modules, alerts

curl -s $FLUKSIO/secrets/ -H "Authorization: Bearer $TOKEN"          # names only
curl -X PUT $FLUKSIO/secrets/influx-token -H "Authorization: Bearer $TOKEN" \
  -H 'Content-Type: application/json' -d '{"value": "..."}'

curl -s $FLUKSIO/modules/ -H "Authorization: Bearer $TOKEN"
curl -X POST $FLUKSIO/modules/apply -H "Authorization: Bearer $TOKEN" \
  -H 'Content-Type: application/json' -d '{"requirements": "numpy>=2\n"}'

curl -s $FLUKSIO/alerts/config -H "Authorization: Bearer $TOKEN"
curl -X POST $FLUKSIO/alerts/test/my-phone -H "Authorization: Bearer $TOKEN"

Secrets are write-only over the API: you can list names and set values, never read one back.

Observability

Path What
/observability/summary engine health — always 200, degraded or not — and the version it is running
/observability/timeseries executions and failures over a window
/observability/flows per-flow rollups with a 60-slice trend
/observability/runs recent cascades, with ?flow=, ?since=, ?until=
/observability/events?kind=failure|audit what went wrong, or who changed what; narrows by ?flow=, ?run=, ?since=, ?until=
/observability/dead-letter work the engine gave up on

GET /utils/health/ is the deep health check the container probe uses: it fails when the event loop is wedged or the state backend is gone, not just when the process is dead.

Workers

Method Path What
GET /workers what is attached, its labels and how busy it is
POST /workers/tokens mint a worker credential (superuser; shown once)
GET /workers/runtime the node runner's source, for a host without pip
WS /workers/attach where a worker dials in

See Remote workers.

Live events

ws://127.0.0.1:8000/api/v1/flows/ws?token=<access token>

The websocket authenticates from its query string. It sends a snapshot on connect and then every engine event: node executions, values published, health changes, run started and finished. This is what the canvas and the dashboards draw from.

Errors

Code Means
400 the request was malformed, or the node refused it with a message
401 / 403 not signed in, or not allowed
404 no such flow, dashboard, run or message
409 someone else saved first — the body carries current_version
422 a parameter, port or binding did not typecheck
503 that subsystem is not available on this instance

A 409 on a save or a publish is not an error to retry blindly: it means the stored version moved past the one you were editing. Re-read, merge, save again.

Generating a client

The frontend's TypeScript client is generated from the OpenAPI schema (make generate-client). Any OpenAPI generator will do the same for your language: point it at /api/v1/openapi.json on a non-production instance.