Flows and dashboards are created by POST under an issued or superuser-pinned id; PUT only updates; deleted flows leave a tombstone with their key; the controller stores by id and runs by key, hooks live under /hooks/<flow id>; a key with state or queued work behind it is refused until cleared
This commit is contained in:
+55
-31
@@ -52,29 +52,47 @@ a token. Password recovery uses `POST /password-recovery/{email}` and
|
||||
|
||||
| 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}/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/` | 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 |
|
||||
| `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.
|
||||
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.
|
||||
|
||||
A flow document carries an `id` beside its `name`. The id never changes, and
|
||||
for now it equals the name, which is the `{name}` in these paths. A save may
|
||||
leave the id out, but cannot change the id or the name.
|
||||
`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. 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.
|
||||
|
||||
### Node source
|
||||
|
||||
```sh
|
||||
curl "$FLUKSIO/flows/house/nodes/decide/source" \
|
||||
curl "$FLUKSIO/flows/f_k3q7m2xa/nodes/decide/source" \
|
||||
-H "Authorization: Bearer $TOKEN"
|
||||
|
||||
curl -X PUT "$FLUKSIO/flows/house/nodes/decide/source" \
|
||||
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"}'
|
||||
@@ -87,7 +105,7 @@ Function and Command nodes, changing Command settings, or changing
|
||||
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;
|
||||
`POST /flows/{id}/nodes/{node}/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
|
||||
@@ -97,14 +115,18 @@ the published flow and the draft, and the shared source is deleted.
|
||||
|
||||
| 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 |
|
||||
| `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.
|
||||
@@ -192,13 +214,13 @@ reclaiming space sooner.
|
||||
|
||||
| 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` | publish or discard |
|
||||
| `POST` | `/dashboards/{name}/duplicate` | copy into an unpublished `{name}_copy` |
|
||||
| `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 |
|
||||
| `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 |
|
||||
@@ -210,8 +232,10 @@ reclaiming space sooner.
|
||||
| `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 document carries an `id` the way a flow does. Dashboards have ids
|
||||
of their own, so a flow and a dashboard may share one.
|
||||
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.
|
||||
|
||||
@@ -69,7 +69,7 @@ without an inbound port. Replace `portal.example.com` with your portal's API
|
||||
address in these examples:
|
||||
|
||||
- A webhook or a served WebSocket:
|
||||
`https://portal.example.com/i/<instance-id>/hooks/<flow>/<url>/<secret>`,
|
||||
`https://portal.example.com/i/<instance-id>/hooks/<flow-id>/<url>/<secret>`,
|
||||
with `wss://` for a socket.
|
||||
- An agent: `https://portal.example.com/i/<instance-id>/mcp`, see
|
||||
[Agents over MCP](../code/agents.md).
|
||||
|
||||
@@ -61,7 +61,9 @@ unwrapped only if it contains the port's name as a key.
|
||||
|
||||
The ports pick the direction: outputs only make a **listener**, inputs a
|
||||
**sender**. A listener is a **webhook**: the engine mounts a route at
|
||||
`/hooks/{flow}/{url}/{secret}` while the flow runs. A sender requests `url`
|
||||
`/hooks/{flow id}/{url}/{secret}` while the flow runs. The flow's id is in the
|
||||
URL rather than its key, so the URL stays the same when the key changes, and
|
||||
for a flow made before ids existed it reads as the key. A sender requests `url`
|
||||
and publishes a JSON object response on its outputs, one key per output port.
|
||||
Keys no port declares are dropped, and a response that is not an object
|
||||
publishes nothing.
|
||||
@@ -93,7 +95,8 @@ one), while a reverse proxy in front of the engine logs the path as it sees it.
|
||||
|
||||
The URL picks the role: `ws://…` or `wss://…` connects to a server and
|
||||
reconnects when it drops; a path such as `/ticker` is served at
|
||||
`/hooks/{flow}/{url}/{secret}` while the flow runs. The ports pick the
|
||||
`/hooks/{flow id}/{url}/{secret}` while the flow runs, under the flow's id like
|
||||
a webhook. The ports pick the
|
||||
direction: outputs only make a **listener**, inputs a **sender**. A served
|
||||
sender sends to every connected client.
|
||||
|
||||
|
||||
Reference in New Issue
Block a user