Files
app/docs/code/api.md
T
Melvin StroblandClaude Opus 5.5 02b736c9e8
Docs / docs (push) Successful in 22s
Playwright Tests / test-playwright (1, 2) (push) Failing after 4m37s
Playwright Tests / test-playwright (2, 2) (push) Successful in 2m19s
pre-commit / pre-commit (push) Failing after 2m25s
Test Backend / test-backend (3.12) (push) Successful in 4m57s
Test Backend / test-backend (3.14) (push) Successful in 4m41s
Compose Smoke Test / test-compose (push) Successful in 28s
Playwright Tests / merge-reports (push) Successful in 1m38s
Live view over WebRTC (openpanel L1, server and browser)
A device reporting a `live` port streams its camera to the browser's own
RTCPeerConnection. The flows socket carries the signalling as text, so it
crosses the portal: `live` messages on a person's socket, `signal` on a
device's (start, sdp, candidate, bye, busy). The server routes by session,
holds a device to two viewers, ends a session when either socket closes or
no answer comes within 10 s, and gives each socket an outbox of its own
that ends a session rather than drop a message. TURN credentials are
minted per session in coturn's REST scheme (STUN_URLS, TURN_URLS,
TURN_SECRET), the viewer's with the device's offer; GET /devices/{id}/ice
hands them out too. The tunnel keeps a stream's early frames instead of
dropping them past eight and forgets them when the stream or link goes.

A media tile on a device's camera, and the Devices tab's preview, play
live when tapped, pause frames meanwhile, and fall back to frames with a
short note on busy, failure or no picture within 10 s. coturn is an
optional compose service behind the `turn` profile.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0138N7k5PT4dcR1iEqXaaQ9k
2026-10-17 08:37:39 +02:00

327 lines
16 KiB
Markdown

# The HTTP API
The API covers the same operations as the browser. Use it for scripts,
integrations, or your own client.
Set the base URL for your instance:
```sh
export FLUKSIO=http://127.0.0.1:8000/api/v1
```
Use the port printed by `fluksio serve`. For Docker, use
`http://localhost:8080/api/v1`, or `https://<your-domain>/api/v1` for a
deployment.
All paths below are relative to this base. Timestamps in responses are
ISO 8601 and always carry their UTC offset.
## Authenticating
`fluksio serve` signs the local CLI in automatically. To reuse its token
from a project instance, with `jq` installed:
```sh
export TOKEN=$(jq -r .token .fluksio/client.json)
```
Adjust the path for `--global` or `--data-dir`. For a remote instance,
use its login endpoint with the administrator credentials:
```sh
export TOKEN=$(curl -s -X POST "$FLUKSIO/login/access-token" \
--data-urlencode "username=admin@example.com" \
--data-urlencode "password=<your-password>" | jq -r .access_token)
curl "$FLUKSIO/users/me" -H "Authorization: Bearer $TOKEN"
```
Session tokens last eight days by default. `POST /login/test-token` checks
a token. Password recovery uses `POST /password-recovery/{email}` and
`POST /reset-password/`.
[Agents over MCP](agents.md) use separate OAuth tokens.
!!! tip "Interactive API reference"
Outside production, open `/docs` or `/redoc` on the API host.
The schema is at `/api/v1/openapi.json`. Access is limited to loopback
unless `API_DOCS_ENABLED=true`; the Docker development stack enables it.
Production disables these endpoints.
## Flows
| Method | Path | Action |
|---|---|---|
| `GET` | `/flows/` | list flows and their state |
| `GET` | `/flows/{name}` | read the draft when present, with status and issues |
| `PUT` | `/flows/{name}` | save a draft; send the current `version` |
| `POST` | `/flows/{name}/publish` | publish; send `{"version": <current-version>}` |
| `POST` | `/flows/{name}/discard-draft` | discard unpublished edits |
| `POST` | `/flows/{name}/rename` | rename |
| `POST` | `/flows/{name}/duplicate` | copy into an unpublished `{name}_copy`; code nodes need a superuser |
| `DELETE` | `/flows/{name}` | delete; refused while a run is queued or running |
| `GET` | `/flows/node-types` | list types and parameter schemas |
| `GET` | `/flows/graph` | read the graph shown on Home |
Deleting a flow keeps its run history. Delete runs separately.
### Node source
```sh
curl "$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"}'
```
Writing code requires a superuser. So does adding, retyping, or repointing
Function and Command nodes, changing Command settings, or changing
`resources.env`. Other users can edit wiring, titles, and ordinary settings.
Ordinary source edits stay in the draft until publishing. Shared library
source changes affect its users immediately, and the audit trail records them
like a publish.
`POST /flows/{name}/nodes/{id}/share` promotes a source to the library;
`/unshare` makes it private again. `GET /flows/library` lists shared sources.
`DELETE /flows/library/{name}` retires one, and answers 409 while nodes still
use it. With `?detach=true` each of those nodes keeps a copy of the code, in
the published flow and the draft, and the shared source is deleted.
### Running and controlling
| Method | Path | Action |
|---|---|---|
| `POST` | `/flows/{name}/start`, `/flows/{name}/stop` | activate or stop a live flow |
| `POST` | `/flows/{name}/pause`, `/flows/{name}/resume` | hold or release incoming work |
| `POST` | `/flows/{name}/step` | release one held message |
| `POST` | `/flows/{name}/validate` | validate the current draft |
| `POST` | `/flows/{name}/run` | run the current draft once with `{"inputs": {...}}` |
| `POST` | `/flows/{name}/nodes/{id}/trigger` | feed a node with `{"values": {...}}` |
| `POST` | `/flows/{name}/nodes/{id}/cancel` | stop active node code |
| `POST` | `/flows/{name}/nodes/{id}/acknowledge` | clear its failure marker |
A preview run can have real side effects. On a batch flow, `/run` submits a
recorded run; use the run endpoints below for parameters and tracking.
## Messages
```sh
curl "$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 "$FLUKSIO/messages/house.temperature/history" \
-H "Authorization: Bearer $TOKEN"
```
The catalogue contains messages declared by published flows and their last
values. You can publish only to declared messages, just as dashboard controls do.
## Runs
| Method | Path | Action |
|---|---|---|
| `POST` | `/runs/flows/{name}` | queue a run with `{"params": {...}, "seed": 7}` |
| `POST` | `/runs/flows/{name}/sweep` | queue runs with a shared group |
| `POST` | `/runs/import` | import up to 200 completed runs |
| `GET` | `/runs` | query history |
| `GET` | `/runs/overview` | summarize runs per flow |
| `GET` | `/runs/{id}` | read parameters, results, nodes, and artifacts |
| `GET` | `/runs/{id}/nodes/{node}/outputs` | read what one node returned |
| `POST` | `/runs/{id}/cancel`, `/runs/{id}/retry` | cancel, or create a retry |
| `DELETE` | `/runs/{id}` | delete a finished run and its records |
| `DELETE` | `/runs?flow={name}` | delete a flow's runs; refused while any are active. `history=true` also clears its cascades from the history |
| `GET` | `/runs/{id}/metrics?name=&stride=` | read metric series |
| `GET` | `/runs/metrics/names` | list metric names across a selection |
| `GET` | `/runs/series/compare?ids=a,b,c&metric=&x=` | compare curves by step, time, or another metric |
| `GET` | `/runs/export/metrics`, `/runs/export/runs` | stream CSV or JSONL |
Submissions return a queued run immediately. Invalid or undeclared parameters
return `422` before execution. Optional `draft` and `no_cache` flags select
unpublished code or skip stage caching.
History filters include `flow`, `status`, `group`, `digest`, `since`,
`before`, `limit`, and `offset`. Rows arrive newest first; use the last
row's `created_at` as `before` for stable pagination.
A node's outputs come from the stage cache, so only a node that succeeded
and may be cached has them, up to 256 kB of JSON. Any other node answers
`404`.
Exports also accept `ids` and `until`, with `format=csv` or `jsonl`.
The metrics export is a long table; the runs export is one row per run.
Use `params` and `metrics` to choose columns, including dotted paths into
record outputs. An export covers at most the newest 10 000 runs. When a
selection matches more, the response carries `X-Truncated` with that cap and
`X-Total-Count` with the number of runs the selection matched.
Import takes `{"runs": [...]}`, records completed results without executing
code, and deduplicates by `external_id`. See
[Runs](../concepts/runs.md) for examples, artifacts, caching, and exports.
## Artifacts
```sh
curl -X PUT "$FLUKSIO/artifacts?name=model.pt" \
-H "Authorization: Bearer $TOKEN" --data-binary @model.pt
curl "$FLUKSIO/artifacts/<digest>" \
-H "Authorization: Bearer $TOKEN" -o model.pt
```
The upload returns a reference with the content digest. Identical bytes are
stored once. Node code uses `save_artifact` and `load_artifact` instead.
`?volatile=true` holds the bytes in the engine's memory for a live frame, where
newer frames push them out, instead of storing them.
A superuser can remove stored bytes with `DELETE /artifacts/<digest>`. While
a run, a current message or a flow input's initial value still references the
digest, the call returns `409`. The hourly sweep removes unreferenced bytes
on its own, so this is for reclaiming space sooner.
## Dashboards and devices
| Method | Path | Action |
|---|---|---|
| `GET` | `/dashboards/` | list dashboards |
| `GET` | `/dashboards/{name}?draft=true` | read a draft; omit the query for published content |
| `POST` | `/dashboards/{name}` | create |
| `POST` | `/dashboards/from-flow/{flow}` | create a batch flow's results dashboard |
| `PUT` | `/dashboards/{name}` | save a draft |
| `POST` | `/dashboards/{name}/publish`, `/dashboards/{name}/discard`, `/dashboards/{name}/rename` | publish, discard, or rename |
| `POST` | `/dashboards/{name}/duplicate` | copy into an unpublished `{name}_copy` |
| `GET` | `/devices/` | list devices, with `online`, `last_seen`, `report` and `reported_at` |
| `POST`, `GET`, `PUT`, `DELETE` | `/devices/{id}` | create, read, save or delete one; a save sends the `version` it read and gets 409 when someone saved first. A read adds `io` and `bindings`, its ports as the device nodes of published flows wire them |
| `POST` | `/devices/pair` | start device pairing, optionally with `{name, board, firmware}`; answers `code`, `secret` and the two-character `check` the device shows beside its code; unauthenticated, five a minute per address (the portal limits its own), the oldest of 50 waiting requests from the same side gives way |
| `GET` | `/devices/pair/{code}` | poll pairing with its `secret`; answers `device` (and `panel` for older firmware); unauthenticated |
| `GET` | `/devices/pending` | devices waiting for a code, as they describe themselves, each with its `check` |
| `GET` | `/devices/pair/{code}/device` | what is waiting on a code, before it is approved |
| `POST` | `/devices/{id}/pair` | adopt by `{code}`, or by `{request}` from the pending list for a request from your network |
| `POST` | `/devices/{id}/unpair` | revoke the device credential, one the portal issued included |
| `PUT` | `/devices/{id}/report` | what the device has, from its own credential only; at most 16 KB, replaces the last report, read leniently (see [What a device reports](../interface/dashboards.md#what-a-device-reports)) |
| `GET` | `/devices/{id}/ice` | STUN and TURN servers for a live view of the device, as `{ice_servers: [{urls, username?, credential?}], ttl}`, with TURN credentials of the caller's own; not for a device credential |
`/panels/pair`, `/panels/pair/{code}` and `GET /panels/{id}` answer as their
`/devices/` counterparts, for firmware that has not updated yet.
## Secrets, modules, and alerts
| Method | Path | Action |
|---|---|---|
| `GET` | `/secrets/` | list names only |
| `PUT` | `/secrets/{name}` | set `{"value": "..."}` |
| `GET` | `/modules/` | read installed packages and the manifest |
| `POST` | `/modules/apply` | replace the manifest with `{"requirements": "numpy>=2\n"}` |
| `GET`, `PUT` | `/alerts/config` | read or update alert configuration |
| `POST` | `/alerts/test/{channel}` | test a channel |
Secrets and package changes require a superuser. Applying a manifest replaces
the whole managed environment's package list. Adopted environments are read-only
here; install their packages yourself.
Connector state keys can also be listed and cleared by a superuser; see
[the connector contract](../reference/connector-contract.md#keeping-state-across-restarts).
## Observability and workers
| Method | Path | Action |
|---|---|---|
| `GET` | `/observability/summary` | health and engine version; always returns `200` |
| `GET` | `/observability/timeseries`, `/observability/flows` | execution metrics or flow rollups |
| `GET` | `/observability/runs`, `/observability/events`, `/observability/dead-letter` | cascades, events, or abandoned work |
| `POST` | `/observability/flush` | write buffered metrics and events now; superuser only |
| `GET` | `/utils/health/` | deep health check; fails on loop or state-backend problems |
| `GET` | `/workers`, `/workers/resources` | attached workers and resource availability |
| `GET`, `POST` | `/workers/tokens` | list or mint worker credentials; superuser only |
| `POST` | `/workers/tokens/{jti}/revoke` | revoke a worker |
| `GET` | `/workers/runtime` | read node-runner source |
| `WS` | `/workers/attach` | attach with a worker token |
| `GET`, `POST` | `/flavors/` | list or create resource sizes |
| `PATCH`, `DELETE` | `/flavors/{name}` | update or delete a size |
Changing flavors requires a superuser. See [Remote workers](workers.md).
## Live events
Connect to `ws://127.0.0.1:8000/api/v1/flows/ws` with an
`Authorization: Bearer <access-token>` header.
Browsers first call `POST /flows/ws-ticket`, then connect with
`?ticket=<ticket>`. Tickets are single-use and expire after 30 seconds.
The socket sends a snapshot followed by node, value, health, and run events.
A client asks for the bytes of the media it shows with
`{"type": "media", "names": [...]}`, which replaces what it asked for before,
for at most 8 messages. Each frame held in memory then arrives as a binary
message before the value naming it: four bytes of header length (big endian),
a JSON header `{"type": "media", "name", "digest", "media_type", "ts"}`, then
the bytes. Messages are sent without per-message compression.
A device's socket is bounded to its own messages. Its snapshot carries
`"demand"`, the ports it reported as `on_demand` that someone wants now, and
`"uplink"`, `"binary"` on its own socket and `"http"` through the portal. When
that set changes it is sent `{"type": "demand", "ports": [...]}`. A port has
demand while a socket asks for the media of the message it is wired to, or a
node of a running flow reads that message.
With a `binary` uplink the device sends a camera frame as a binary message in
the same layout, with the header `{"type": "frame", "name": "<message>",
"media_type": "image/jpeg", "ts": <device ms>}`. The message has to be one of
its `in` ports wired as `image`. The frame is held in memory and published
in one step, credited to its device node. A frame that does not qualify is
logged and dropped and the socket stays open. With `http` it uploads with
`PUT /artifacts?volatile=true&name=<message>` and publishes with
`POST /messages/<message>`. A device that is unpaired or deleted while
connected is closed with 1008.
### Live view
A device that reports a `live` port streams its camera over WebRTC. The
video goes peer to peer, or through a TURN relay, and the socket carries only
the signalling, as text, so it crosses the portal too. A person's socket
sends and hears `{"type": "live", "device": "<id>", "session": "<id>",
"action": ...}`, a device's socket `{"type": "signal", "session": "<id>",
"action": ...}`. A session id is 8 to 64 letters, digits, `-` or `_`, chosen by
the viewer. In order:
| Step | Viewer's socket | Device's socket |
|---|---|---|
| 1 | sends `start` | hears `start` with `ice_servers` minted for the device |
| 2 | hears `sdp` (the offer) with `ice_servers` minted for the viewer | sends `sdp` with its offer |
| 3 | sends `sdp` with its answer | hears `sdp` |
| 4 | sends and hears `candidate`, the bare `candidate:...` line | hears and sends `candidate` |
| 5 | sends `bye`, or hears `bye` or `busy` with a `reason` | hears `bye` with a `reason`, or sends `bye` or `busy` |
Any signed-in person may watch any device. A device speaks only for the
sessions it was handed on that socket. The server answers `busy` itself once a
device has 2 sessions, and `bye` with `offline` or `unsupported` when the
device is not connected or reports no `live` port. A session the viewer has not
answered within 10 seconds ends with `timeout`, and either socket closing ends
its sessions with `closed` or `offline`. A socket that falls 64 signalling
messages behind ends the session with `overflow` instead of losing one.
`ice_servers` follow `RTCIceServer`: `{urls: [...]}` for STUN,
`{urls, username, credential}` for TURN, in coturn's REST scheme with a
username of `<expiry>:<who>`, good for an hour. A device that sends its camera's
frames on the same socket pauses them while a session negotiates, so its
offer and candidates are not queued behind frames.
## Errors and client generation
| Code | Meaning |
|---|---|
| `400` | malformed request or refused operation |
| `401` / `403` | missing authentication or permission |
| `404` | resource not found |
| `409` | version conflict, duplicate, or resource still in use |
| `413` | artifact upload exceeds the configured limit |
| `422` | invalid parameters, ports, or bindings |
| `503` | subsystem unavailable |
For a version conflict, read `current_version`, reload, merge your edits, and
save again. Other `409` responses describe their own conflict.
`make generate-client` regenerates the frontend SDK. To generate a client in
another language, use the instance's `/api/v1/openapi.json` schema.