# 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` installation. ```sh export FLUKSIO=http://127.0.0.1:8000/api/v1 ``` ## Authenticating ```sh 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](agents.md). !!! 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 on purpose: the schema enumerates every endpoint the installation serves, including the 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 ```sh 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, and a call that quietly did something else would be a trap. ## Messages ```sh 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}` | | `POST` | `/runs/flows/{name}/sweep` | queue many, sharing a `group_id` | | `GET` | `/runs` | the queryable history: `?flow=`, `?status=`, `?group=`, `?digest=`, `?limit=`, `?offset=` | | `GET` | `/runs/overview` | one row per flow that has runs, with how many are running or queued | | `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". `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](../concepts/runs.md). ## Artifacts ```sh 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 ```sh 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 | | `/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 | | `/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](workers.md). ## Live events ```text ws://127.0.0.1:8000/api/v1/flows/ws?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 installation | 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 installation.