Docs / docs (push) Successful in 3m11s
Playwright Tests / test-playwright (1, 2) (push) Successful in 11m42s
Playwright Tests / test-playwright (2, 2) (push) Successful in 2m22s
pre-commit / pre-commit (push) Successful in 3m12s
Test Backend / test-backend (3.12) (push) Successful in 6m19s
Test Backend / test-backend (3.14) (push) Successful in 5m59s
Compose Smoke Test / test-compose (push) Successful in 37s
Playwright Tests / merge-reports (push) Successful in 1m12s
448 lines
24 KiB
Markdown
448 lines
24 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; `?name=<key>` lists the one with that key |
|
|
| `POST` | `/flows/` | create a flow from a document; it starts as a draft |
|
|
| `GET` | `/flows/{id}` | read the draft when present, with status and issues |
|
|
| `PUT` | `/flows/{id}` | save a draft of an existing flow; send the current `version` |
|
|
| `POST` | `/flows/{id}/publish` | publish; send `{"version": <current-version>}` |
|
|
| `POST` | `/flows/{id}/discard-draft` | discard unpublished edits |
|
|
| `POST` | `/flows/{id}/duplicate` | copy into an unpublished `<key>_copy` with an id of its own; code nodes need a superuser |
|
|
| `DELETE` | `/flows/{id}` | delete; refused while a run is queued or running |
|
|
| `POST` | `/flows/{id}/rename/plan` | what renaming the key to `{"new_name": ...}` would change; superuser only |
|
|
| `POST` | `/flows/{id}/rename` | rename the key as planned; superuser only |
|
|
| `DELETE` | `/flows/{id}/rename/{request_id}` | call off a rename still waiting for work to finish |
|
|
| `GET` | `/flows/node-types` | list types and parameter schemas |
|
|
| `GET` | `/flows/graph` | read the graph shown on Home |
|
|
|
|
A flow has an `id` and a key, its `name`. The id says which flow it is and
|
|
never changes. Every path takes the id, never the key. The key is the
|
|
namespace of the flow's messages (`house.temperature`) and is unique among
|
|
flows. A flow created before ids existed has its key as its id.
|
|
|
|
`POST /flows/` takes the same document as a save, without `version`, and
|
|
answers 201 with the new flow. The server issues an opaque id such as
|
|
`f_k3q7m2xa`. A superuser may pin one instead by sending `id`, which the Demo
|
|
and restores do. A pinned id has to be free and cannot be a path of this API
|
|
like `graph`. If runs or metrics of a deleted flow are still filed under it,
|
|
the new flow takes that history back and has to use the key it was recorded
|
|
under. Another key is refused with 409, and `key` in the answer names the
|
|
recorded one. To bring it back under another key, restore it under the
|
|
recorded key and then rename it. Creating a flow with code nodes needs a
|
|
superuser, as saving one does.
|
|
A key that still has values or queued work from an earlier flow is refused
|
|
with 409 until that is gone.
|
|
|
|
`PUT /flows/{id}` only updates. It answers 404 for an id nobody holds, and 409
|
|
for a body whose `name` differs from the stored key. Its body may leave `id`
|
|
out.
|
|
|
|
Deleting a flow keeps its run history, which is shown under the key the flow
|
|
last had. Delete runs separately. The flow's values and queued work go with
|
|
it, so its key is free for a new flow, which starts with an empty history.
|
|
|
|
Answers that lead to a flow or dashboard carry its id next to the key. An
|
|
endpoint in `GET /flows/{id}` has `owner_id`, a neuron in `GET /flows/graph`
|
|
lists `flow_ids` in the order of its `flows`, a device binding has `flow_id`
|
|
and a search entry has `id`. Open things by these ids, not by the part of a
|
|
`<key>.<node>` before the dot.
|
|
|
|
### Renaming a key
|
|
|
|
A superuser renames a flow's key in two steps. `POST /flows/{id}/rename/plan`
|
|
with `{"new_name": "warmth"}` changes nothing. It lists the flows and
|
|
dashboards the rename would rewrite, counts the history, live state, queued
|
|
work and alert channels that would move, and gives warnings and `refusals`,
|
|
`allowed`, the flow's `name` and `version` and a `fingerprint`.
|
|
`POST /flows/{id}/rename` then sends the plan's `name` as `expected_name`, its
|
|
`version` as `expected_version`, `new_name` and `fingerprint`, plus a
|
|
`request_id` of your own of up to 64 characters. It answers with the new versions of the rewritten documents. Sending the same
|
|
`request_id` again gets the same answer.
|
|
|
|
The whole engine pauses for a flow rename. The request first waits for running
|
|
work to finish, however long that takes, and `GET /observability/summary` shows
|
|
the rename under `rename`, with what it waits for in `busy`. Until it starts
|
|
changing things, `DELETE /flows/{id}/rename/{request_id}` or closing the
|
|
connection calls it off, and the request answers 409 `aborted`. Meanwhile other
|
|
changes get 503 with code `renaming` and `Retry-After`, and so do the hooks of
|
|
the flows being rewritten. Reads keep working.
|
|
|
|
A refusal answers 409 with its `code` and the fresh `plan`, and so does
|
|
`plan_changed`, when something the rename would rewrite changed since the plan.
|
|
409 `stale` means the flow itself changed. A key that is no valid key answers
|
|
422.
|
|
|
|
`POST /dashboards/{id}/rename/plan` and `POST /dashboards/{id}/rename` work the
|
|
same way for a dashboard. Only its own documents change, and the engine keeps
|
|
running.
|
|
|
|
### Node source
|
|
|
|
```sh
|
|
curl "$FLUKSIO/flows/f_k3q7m2xa/nodes/decide/source" \
|
|
-H "Authorization: Bearer $TOKEN"
|
|
|
|
curl -X PUT "$FLUKSIO/flows/f_k3q7m2xa/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/{id}/nodes/{node}/share` promotes a source to the library;
|
|
`/unshare` makes it private again. `GET /flows/library` lists shared sources
|
|
and the `<key>.<node>` of every node using each.
|
|
`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/{id}/start`, `/flows/{id}/stop` | activate or stop a live flow |
|
|
| `POST` | `/flows/{id}/pause`, `/flows/{id}/resume` | hold or release incoming work |
|
|
| `POST` | `/flows/{id}/step` | release one held message |
|
|
| `POST` | `/flows/{id}/validate` | validate the current draft |
|
|
| `POST` | `/flows/{id}/run` | run the current draft once with `{"inputs": {...}}` |
|
|
| `POST` | `/flows/{id}/nodes/{node}/trigger` | feed a node with `{"values": {...}}` |
|
|
| `POST` | `/flows/{id}/nodes/{node}/cancel` | stop active node code |
|
|
| `POST` | `/flows/{id}/nodes/{node}/acknowledge` | clear its failure marker |
|
|
|
|
Values, states and node names in the answers are qualified with the key, as
|
|
in `house.temperature` and `house.decide`. Node ids in the paths are the
|
|
flow's own, unqualified.
|
|
|
|
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/{flow id}` | queue a run with `{"params": {...}, "seed": 7}` |
|
|
| `POST` | `/runs/flows/{flow id}/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={flow id}` | 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.
|
|
|
|
History is kept by flow id, so it stays with a flow whose key changes and
|
|
stays apart from a later flow that takes a deleted one's key. In a run, a
|
|
cascade, an engine event and a metrics rollup, `flow` is the flow's id and
|
|
`flow_name` its key, or the key it last had once it is deleted. A `flow`
|
|
filter, here and under `/observability`, takes the id and only the id. Nodes
|
|
are shown and taken as `<key>.<node>`, and metric and artifact names keep the
|
|
key too (`train.loss`).
|
|
|
|
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, with
|
|
the flow's id in `flow` and its key in `flow_name`.
|
|
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`. Each run's `flow` is the flow's id,
|
|
and its metric names are qualified with the key. 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; `?name=<key>` lists the one with that key |
|
|
| `POST` | `/dashboards/` | create an empty draft from `{"name": ..., "title": ...}` |
|
|
| `GET` | `/dashboards/{id}?draft=true` | read a draft; omit the query for published content |
|
|
| `POST` | `/dashboards/from-flow/{flow id}` | create and publish a batch flow's results dashboard, `<key>_results` |
|
|
| `PUT` | `/dashboards/{id}` | save a draft of an existing dashboard |
|
|
| `POST` | `/dashboards/{id}/publish`, `/dashboards/{id}/discard` | publish or discard |
|
|
| `POST` | `/dashboards/{id}/duplicate` | copy into an unpublished `<key>_copy` with an id of its own |
|
|
| `POST` | `/dashboards/{id}/rename/plan`, `/dashboards/{id}/rename` | plan or rename the key, see [Renaming a key](#renaming-a-key); superuser only |
|
|
| `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 |
|
|
|
|
A dashboard has an `id` and a key the way a flow does, and its paths take the
|
|
id. A new one gets an opaque id such as `d_k3q7m2xa`, or one a superuser pins
|
|
with `id`. Dashboards have ids of their own, so a flow and a dashboard may
|
|
share one. `PUT` only updates, as for a flow.
|
|
|
|
`/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 |
|
|
| `POST`, `GET`, `DELETE` | `/alerts/telegram/{channel}/link` | start, read or cancel connecting a chat to a telegram channel; the start returns the `t.me` links and never the bot token |
|
|
| `GET` | `/alerts/webpush/key` | the key a browser subscribes against; any account |
|
|
| `POST` | `/alerts/webpush/subscriptions`, `/alerts/webpush/unsubscribe` | add or remove the calling browser; any account |
|
|
|
|
Secrets, package changes and the alert configuration require a superuser. Any
|
|
account can subscribe its own browser. 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).
|
|
|
|
The history under `/observability` is kept by flow id as described under
|
|
[Runs](#runs): `flow` is the id, `flow_name` the key, and `node` a
|
|
`<key>.<node>`, which is also how `timeseries` takes its `node` filter. Dead
|
|
letters are work the engine gave up on and name the flow and node by key, as
|
|
the engine runs them.
|
|
|
|
## 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.
|
|
An event about a flow names it by key in `flow`, the way its nodes and
|
|
messages are named, and by id in `flow_id`, which is empty for the engine as
|
|
a whole. `dashboard_changed` names the dashboard by id, or nothing when a
|
|
device's own wiring moved.
|
|
|
|
A key rename sends `rename_waiting` with `busy` while it waits for work to
|
|
finish, and `renamed` with `kind`, `id`, `old` and `new` once it is done. A
|
|
client then reads again what it shows, at the same ids.
|
|
|
|
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, or a key rename in progress, retry after `Retry-After` |
|
|
|
|
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.
|