The admin API
| Method and path | Permission | Notes |
|---|---|---|
GET /health |
none | Liveness. The kubelet carries no token, and auth on this route would restart-loop the pod forever. |
GET /ready |
none | Readiness, reported separately, so a runtime that failed to start leaves the Service without the kubelet killing the pod. |
POST /auth/token |
none | Log in. A 429 with Retry-After while locked out. |
POST /auth/revoke |
none | Log out. |
GET /auth/mfa, POST /auth/mfa/setup, /confirm, /disable |
a signed-in user | Your own two-factor sign-in. Setup returns the key, the otpauth:// link and the QR code. Confirm and disable take {"code": ...}. |
POST /auth/mfa/reset |
auth.admin |
Turns off somebody else’s two-factor sign-in: {"username": ...}. |
GET /metrics |
none | Prometheus. Counts and node ids only, never message contents or configuration. |
GET /settings |
settings.read |
|
GET /nodes |
nodes.read |
The registry, which is what drives the editor’s palette and its dialogs. |
GET /flows |
flows.read |
|
GET /flows/export |
flows.read |
The flow file exactly as it sits on disk, for git. |
POST /flows |
flows.write |
Deploy. Takes an optional note for the deployment log. |
GET /deployments |
flows.read |
The deployment log, newest first, without the payloads. ?limit=N bounds it. |
GET /audit |
audit.read |
Who did what from where, newest first. ?event=, ?user=, ?since=, ?limit=. |
GET /deployments/{seq} |
flows.read |
One record, with its flows parsed. Never its credentials. |
GET /deployments/{seq}/flows |
flows.read |
That deployment’s flow file, byte for byte. |
GET /deployments/{from}/diff/{to} |
flows.read |
Node-by-node diff between two deployments, structured and as text. |
POST /flows/diff |
flows.read |
The diff from what’s live to the document in the body. Deploys nothing. |
POST /deployments/{seq}/rollback |
flows.write |
Deploys that record’s flows and credentials again as a new deployment. Takes the rev header and an optional {"note": ...}. |
GET /tests |
flows.read |
The flow test suite kept beside the flow file, its file name, and whether deploys are gated on it. |
PUT /tests |
flows.write |
Replaces the suite. The body is the YAML; one that doesn’t parse is a 400 with the reason. |
POST /tests/run |
flows.read |
Runs the suite, or "tests" from the body, against "flows" from the body or what’s running. Returns the report. Deploys nothing. |
POST /tests/from-debug |
flows.read |
Drafts a test from {"node", "msgId"}, a message a Debug node showed. Saves nothing. |
POST /tests/cases |
flows.write |
Adds the tests in the body, a test file of their own, to the end of the suite. |
GET /traces |
flows.read |
The traces kept, newest first, with what the buffer holds and its limits. ?limit=N. 404 with trace.enabled: false. |
GET /traces/find |
flows.read |
The trace a message was in, from ?msgId= and ?node=, the id and Debug node the sidebar shows. Returns every hop and the path to the one asked about. |
GET /traces/{id} |
flows.read |
One trace, every hop with its timing and the message as the node was handed it. |
POST /traces/{id}/test |
flows.read |
Drafts a flow test from the trace. Saves nothing. |
POST /traces/{id}/replay |
flows.read |
Puts the message that started the trace into "flows" from the body, or what’s running, in a child process, and returns where it ends up against where it ended up. Deploys nothing. |
GET /runtime/stats |
status.read |
|
POST /inject/{id} |
inject.write |
Fire an Inject node. |
GET /comms |
status.read |
The editor’s status and debug websocket. Send the token as the subprotocol hotloop-flow.bearer.<token>, offered beside hotloop-flow. That is what the editor does. ?access_token=<token> in the URL still works for scripts that already use it, and logs a warning for every connection that does, because a URL ends up in proxy and access logs. |
/metrics and /health being unauthenticated is a decision rather than an
oversight. A Prometheus scraper carries no bearer token, so requiring one means
either handing a credential to your monitoring stack or having no monitoring, and
I have watched people pick the second one.
A user with two-factor sign-in on sends "code" with the username and password
to POST /auth/token. Without it the answer is a 401 with "mfa": "required".
Permissions are "*" for everything, an exact string such as flows.read, or a
prefix grant such as flows.*. A read-only account for a dashboard that just
wants to render flow status is ["flows.read", "status.read"] and nothing more.
Deploys take an optional HotLoop-Flow-Deployment-Rev header. Send the revision you
last read and a deploy racing another editor is rejected with a 409 instead of
silently overwriting somebody’s work, which is the failure mode you only find out
about from whoever lost their afternoon.
A deploy restarts only what you changed. A node stays up when nothing it leans on moved: its settings, its tab, its group, its environment, its credentials, and every config node it names. Dragging it across the canvas or rewiring it doesn’t count, because new wires go onto the running node. So fixing a typo in one function node no longer drops every MQTT session on the box, and a Delay node sitting on ten minutes of readings keeps sitting on them. That’s the whole reason it exists: when every save bounces the plant, operators stop saving during a shift, and the fix waits for Saturday.
Pick how much restarts with a HotLoop-Flow-Deployment-Type header. Node-RED’s
Node-RED-Deployment-Type works too, so a deploy script you already have gets
the restart it asks for.
| Type | What restarts |
|---|---|
nodes (the default) |
The nodes you changed, plus anything that names a config node you changed. |
flows |
Every node on any flow that has a change in it. |
full |
Everything. Same as restarting the process. |
The response says what it did: type, started, restarted, stopped and
unchanged. Nothing in flight gets lost on the way. Work already queued at a
node being replaced finishes on the old instance, anything that shows up during
the swap waits for the new one, and a message headed for a node you deleted is
counted and shows up as a drop instead of vanishing.