A Service's weights or a wren node's model held only in a setting were swept once the run that produced them was deleted. Claude-Session: https://claude.ai/code/session_01H89mFby73wwhthP9htfcuB
347 lines
18 KiB
Markdown
347 lines
18 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. Such a frame is at most
|
|
`MAX_FRAME_BYTES` (4 MB), and a larger one is refused with 413. A camera only
|
|
superusers may see answers 403 to anyone else for the frames its messages
|
|
hold, the last 8 included (see [Who may watch a camera](../interface/dashboards.md#who-may-watch-a-camera)).
|
|
|
|
A superuser can remove stored bytes with `DELETE /artifacts/<digest>`. While
|
|
a run, a current message, a flow input's initial value or a node setting in a
|
|
published or draft flow 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, and 64 KB once its named `config_schemas` are put in place (413 above either), 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; 403 for whoever may not watch its camera, and 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, the last 8 it names when it names more. 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. A socket that falls so far behind
|
|
that events are dropped for it is sent a fresh snapshot, demand included, and
|
|
is closed with 1008 if it was unpaired meanwhile.
|
|
|
|
A person who may not watch a camera (see
|
|
[Who may watch a camera](../interface/dashboards.md#who-may-watch-a-camera))
|
|
gets none of its values in the snapshot or after it, and no frames when asking
|
|
for them. Switching the camera's setting sends that person's open sockets a
|
|
fresh snapshot.
|
|
|
|
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>}`. A microphone's chunk travels
|
|
the same way with a media type such as `audio/L16;rate=16000`. The message has
|
|
to be one of its `in` ports wired as `image` or `audio`, the media type of
|
|
that family, and the whole message at most `MAX_FRAME_BYTES`. The frame is
|
|
held in memory and published in one step, named after the message and credited
|
|
to its device node. A frame that does not
|
|
qualify is dropped, the reason logged once per socket, and the socket stays
|
|
open. A value of one of its `in` ports wired as `bool`, `int`, `float` or `str`
|
|
goes as the text message `{"type": "publish", "name": "<message>", "value":
|
|
...}`, taken in turn with the frames, so a microphone's `listening` false
|
|
arrives after its last chunk. 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, at the latest with its next frame.
|
|
|
|
### 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` |
|
|
|
|
A superuser may watch any device, anyone else a device whose camera is open
|
|
to members, and `start` is answered `bye` with `forbidden` otherwise. 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.
|