Wheelhouse docs

Your first API call#

You will end up with a working curl against the router's API, and the four facts you need before writing anything larger.

Before you start#

Step 1 — Prove the agent is alive#

bash
R=https://<router>:8443
curl -sk "$R/health"
json
{"status":"ok","version":"0.5.1"}

That route is unauthenticated, because a load balancer has no credential, and it carries the agent's version — which is the only version answer available before a login, and the string --metrics-public would publish in wheelhouse_build_info anyway. Nothing else about the box is in it: no host name, no licence state, no router reachability. GET /api/system carries those, behind a principal.

Step 2 — Authenticate#

bash
T=wh_...
curl -sk -H "Authorization: Bearer $T" "$R/api/auth/me"

That returns your principal, its role and its capabilities, which is the fastest way to confirm a token has the role you thought.

Step 3 — Read something#

bash
curl -sk -H "Authorization: Bearer $T" "$R/api/system"
curl -sk -H "Authorization: Bearer $T" "$R/api/interfaces/detail"
curl -sk -H "Authorization: Bearer $T" "$R/api/config/commands"
curl -sk -H "Authorization: Bearer $T" "$R/api/audit?limit=20"

The four things worth knowing#

1. Use a token, not a cookie. Every mutating request from a browser session must also echo that session's CSRF token in X-Wheelhouse-CSRF; a cookie alone can never change configuration. Token requests skip that requirement, which is why scripts use tokens.

2. Reads never need a licence; writes answer 402 without one. Every mutating endpoint answers 402 with the reason on an unlicensed router, and every read keeps working. Build for that: a monitoring integration survives a lapsed subscription.

3. Configuration reads are redacted below the admin role. Private keys, pre-shared secrets and password hashes come back as [redacted]. Redaction matches secret leaf names, so a value that is a secret because of where it sits — a container's environment variable, an SNMP community string — is not matched. Assume a viewer can read those.

4. There is no OpenAPI document. The endpoint table in the reference is the contract. Request and response bodies are not specified there; read the handlers, or drive the UI with the browser's network tab open.

The status codes that mean something specific here#

CodeMeans
401No principal — a missing or wrong token, or a browser session not echoing X-Wheelhouse-CSRF
402No usable licence, on a write
403Your role is not enough for this route. Recorded in the audit log as denied <method> <path>.
409A conflict — for example, restarting an app that is already stopped
501Not implemented. POST /api/capture answers this and hands back the monitor traffic command to run by hand.
502The agent could not reach the router. Check --api-url and the API key.

Step 4 — Watch the live stream, if you want it#

bash
# a WebSocket, so a plain curl will not do it
wscat -c "wss://<router>:8443/api/stream" -H "Authorization: Bearer $T"

GET /api/stream is read uncached each tick. An unreadable router arrives as {"type":"error","error":…} rather than as silence, which is the difference between "the router is quiet" and "the router is gone".

WebSocket upgrades are rejected unless the Origin matches.

Check it worked#

Every command above returning JSON rather than an error. Then:

bash
curl -sk -H "Authorization: Bearer $T" "$R/api/audit?limit=5"

Your reads are not in the audit log — it records mutations — but a denied request is, which makes it the place to look when a call is refused and you cannot see why.

See also#


Checked against agent/main.go · agent/security.go · agent/license.go · docs/deploy.md · SUPPORT.md

Updated 2026-09-02 api automation curl