Skip to content
GitHub

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.