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#
- An API token with the role the call needs —
Issue a token for automation. Start with
viewer. - The router's address.
Step 1 — Prove the agent is alive#
R=https://<router>:8443
curl -sk "$R/health"{"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#
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#
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#
| Code | Means |
|---|---|
401 | No principal — a missing or wrong token, or a browser session not echoing X-Wheelhouse-CSRF |
402 | No usable licence, on a write |
403 | Your role is not enough for this route. Recorded in the audit log as denied <method> <path>. |
409 | A conflict — for example, restarting an app that is already stopped |
501 | Not implemented. POST /api/capture answers this and hands back the monitor traffic command to run by hand. |
502 | The agent could not reach the router. Check --api-url and the API key. |
Step 4 — Watch the live stream, if you want it#
# 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:
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#
- Stage and commit from a script — the write plane
- Issue a token for automation
- Endpoint index and Status codes
- Scrape the agent
Checked against agent/main.go ·
agent/security.go ·
agent/license.go ·
docs/deploy.md ·
SUPPORT.md