Endpoint index#
The agent registers 108 routes in one function, routes() in
agent/main.go, and the minimum role is written on each
registration rather than inside the handler. Two of the 108 are the same path —
GET /metrics is registered either authenticated or not, depending on
--metrics-public — so 107 distinct method-and-path pairs answer on a running agent,
plus GET / when --ui-dir is set. This page is the whole list.
Re-derive it for the version you are running:
grep -o 'mux.HandleFunc("[A-Z]* [^"]*"' agent/main.goThere is no OpenAPI document and nothing generates one, so this table is the contract. Request and response bodies are given where they are not obvious; for the rest, read the handler or drive the web UI with the browser's network tab open.
How to read the columns#
| Column | Meaning |
|---|---|
| Role | The minimum principal. — is unauthenticated. viewer < operator < admin. |
| Licence | yes means the route answers 402 without a usable licence. fleet means it additionally needs a licence whose plan carries the fleet feature. |
| Redacts | The role below which secret leaves are blanked in the response. See What a read hides. |
Unauthenticated#
| Method | Path | Returns |
|---|---|---|
| GET | /health | {"status":"ok"} and nothing else. Not the version — anonymous version disclosure was closed deliberately. install.sh health-checks with it. |
| GET | /api/auth/status | configured, tls, licensed, require_totp, and an oidc block with enabled, label, login and ready. No issuer URL. |
| GET | /api/oidc/status | enabled, label, login, ready, and on success redirect_uri; on failure a detail with the issuer's hostname masked out. |
| GET | /api/oidc/login | 302 to the identity provider. ?next=/path is remembered for after the login (paths only). 503 when single sign-on is not configured. |
| GET | /api/oidc/callback | The provider's return leg. No session exists until the ID token verifies. |
| POST | /api/auth/login | {"username","password","totp"} → a session cookie and a CSRF token. Behind all three rate limiters. |
Session self-service#
Every route here is viewer, because it acts on the caller's own account.
| Method | Path | Role | Returns |
|---|---|---|---|
| POST | /api/auth/logout | viewer | Drops the session and clears the cookie. Returns logout_url for a single sign-on session whose provider supports RP-initiated logout. |
| GET | /api/auth/me | viewer | The current principal: user, role, kind, capabilities, csrf, expires_at, totp_enabled, must_change_password, email, display_name, oidc_subject, sso_only, via. |
| POST | /api/auth/password | viewer | {"current_password","new_password"}. A wrong current password is 403, not 401, so the UI does not read it as a dead session. Every other session for that account is dropped. |
| POST | /api/auth/totp/begin | viewer | A fresh secret and its otpauth:// URI. Stored as pending: an enabled second factor keeps gating login until a code proves the new one. |
| POST | /api/auth/totp/confirm | viewer | {"code"} promotes the pending secret. |
| POST | /api/auth/totp/disable | viewer | Refused with 403 when the require_totp policy is on. |
| GET | /api/auth/sessions | viewer | Sessions. An admin sees every account's; everyone else sees only their own. Session ids are truncated to 12 characters. |
| DELETE | /api/auth/sessions/{id} | viewer | Revokes by id prefix — your own sessions, or anyone's if you are an admin. 404 when nothing matched. |
| POST | /api/auth/oidc/unlink | viewer | Detaches your own account from the identity provider. Refused if it would leave you with no way in. |
Read plane#
All viewer. None needs a licence — losing sight of a router because a card expired
would be a worse product than losing the ability to change it.
| Method | Path | Redacts | Returns |
|---|---|---|---|
| GET | /api/version | — | The router's show version, as the router's own data. 502 if it cannot be reached. |
| GET | /api/system | — | The Dashboard's single round trip: hostname, parsed version, uptime with three load averages, memory, storage, cpus, boot images, and the raw text of each. Seven reads, capped at the 20 s router budget and cancelled if the browser goes away. |
| GET | /api/config?path= | below admin | The configuration tree as JSON. path may be a slash-joined string or a JSON array — the array form is how a node whose key holds a slash (subnet 10.0.0.0/16) is named. |
| GET | /api/config/commands?path= | below admin | The same subtree rendered as set lines, filtered from op-mode show configuration commands. |
| GET | /api/config/raw?path= | admin only | The configuration in the router's native curly-brace format. This is the backup export, unredacted, which is why it is an admin route rather than a redacted read. |
| GET | /api/interfaces | — | interfaces configuration plus the show interfaces summary text. |
| GET | /api/interfaces/stats | — | show interfaces, as the router's own data. |
| GET | /api/interfaces/detail | — | Per-interface link state, MTU, addresses, qdisc and RX/TX counters. One router read per configured interface, under the 20 s budget. |
| GET | /api/interfaces/history?points= | — | The server-sampled throughput series: interval_ms and, per interface, up to points samples (default 120, 240 kept). |
| GET | /api/neighbors | — | The ARP/NDP table, parsed, plus the raw text. |
| GET | /api/routes | — | The kernel forwarding table and the declared static routes (IPv4). |
| GET | /api/wan | — | The multi-WAN model: every uplink ranked and joined with the live default routes, failover verdicts, load-balancer health, policy tables, per-uplink source NAT, and suggested free rule and table numbers. |
| GET | /api/vrrp | — | high-availability configuration, keepalived's per-group state from show vrrp, and service conntrack-sync. running:false when keepalived says data is not available. |
| GET | /api/lldp | — | service lldp configuration and what the neighbours announce. |
| GET | /api/vpn/ipsec | below admin | vpn ipsec configuration, strongSwan's security associations, and its connections. running:false when IPsec is not initialised, which is the honest state of a router without it. |
| GET | /api/vpn/openvpn | below admin | Every interfaces openvpn instance with the status text the router prints for each mode. |
| GET | /api/wireguard | below admin | Tunnels and peers from the configuration, plus the reported interfaces. |
| GET | /api/diagnostics/lookup?host= | — | show host lookup through the router's own resolver, with the addresses parsed out. The host must match [A-Za-z0-9.:_-]{1,253}. |
| GET | /api/dhcp/leases | — | Active leases, parsed. |
| GET | /api/dhcp/reservations | — | The service dhcp-server subtree — pools, options and static mappings. |
| GET | /api/dns | — | service dns configuration and the forwarder's cache statistics. |
| GET | /api/firewall/rules | — | The firewall subtree. |
| GET | /api/firewall/stats | — | show firewall statistics split into titled rulesets with parsed rows. |
| GET | /api/firewall/groups | — | Address, network, port and interface groups. |
| GET | /api/nat/rules | — | The nat subtree. |
| GET | /api/nat/stats | — | Per-rule destination and source NAT counters, parsed. |
| GET | /api/qos | — | The qos subtree. |
| GET | /api/qos/stats | — | Per shaped interface: the policy the configuration asks for beside the qdisc the kernel actually installed. |
| GET | /api/sessions | — | Connection-tracking flows with a top-talker aggregation. |
| GET | /api/conntrack/stats | — | Per-CPU connection-tracking health: insert failures and early drops. |
| GET | /api/routing | — | Whether BGP and OSPF are configured, and their configuration. |
| GET | /api/ids | — | Suricata configuration, engine state, alerts, and inline-IPS status. |
| GET | /api/drift | below admin | Desired-state enforcement: managed, file, mode, drifted, ops, checked_at, applied_at, error. mode:"off" when no file is being managed. |
| GET | /api/daemons | — | The configuration state of service upnp, service dns and service dhcp-server, each configured or not-configured. |
| GET | /api/apps | — | The router's containers: the declared set, the running set, the count and the raw table. |
| GET | /api/apps/catalog | — | The whole embedded catalogue. |
| GET | /api/apps/installed | — | Declared, running and health-probed, merged. |
| GET | /api/apps/hints/{page} | — | The integration offers installed apps make on one page. |
| GET | /api/apps/logs/{name} | — | One container's journal. name must match ^[-a-zA-Z0-9]+$ or the answer is 400. |
| GET | /api/containers | — | The same containers as /api/apps, shaped for the container view. |
| GET | /api/services | — | Every node under service, sorted, with its configuration. |
| GET | /api/processes | — | The router's process table, parsed. |
| GET | /api/pki | — | Certificate authorities and certificates with their expiry, from show pki and the pki subtree. |
| GET | /api/ntp | — | Time synchronisation configuration and peer state. |
| GET | /api/ddns | — | Dynamic DNS configuration and status. |
| GET | /api/log?lines= | — | The journal tail. Default 100 lines, capped at 5000 — this is a page, not a log export. |
| GET | /api/audit?limit= | — | The agent's own audit log, newest first. Default 300. |
| GET | /api/history | — | Commit revisions, parsed, plus the raw text. |
| GET | /api/history/diff?rev= | below admin | What one stored revision changed. rev must be a non-negative integer. |
| GET | /api/staged | below operator | The working set: count, the rendered commands, and the ops themselves. Redacted only below operator — an operator who staged a key typed it, and a Commit Bar that shows them [redacted] is not a preview. |
| GET | /api/stream | — | The telemetry WebSocket. See The telemetry stream. |
| GET | /api/license | — | Licence state, plan, features, expiry, grace, last refresh, server status and this router's fingerprint. |
| GET | /api/fleet | — | Every router in the fleet file, health-checked concurrently. Answers enabled:false with a note when fleet mode is off or the licence lacks the feature — this route itself never 402s. |
Read plane, licensed or operator-only#
| Method | Path | Role | Licence | Returns |
|---|---|---|---|---|
| GET | /api/reconcile/file?file= | operator | no | Diffs a desired-state document in the data directory against the router. file is a bare name (or an absolute path inside the data directory) ending .yaml, .yml or .json, and never state.*. Default agent.yaml. |
| GET | /api/fleet/{id}/config?path= | viewer | fleet | One fleet router's configuration. Redacted below admin. 404 for an unknown id. |
| GET | /api/fleet/{id}/version | viewer | fleet | One fleet router's show version. |
| GET | /api/admin/oidc | admin | no | Provider state in full — including the issuer URL — the group mapping, and every linked account. |
| GET | /metrics | viewer, or none with --metrics-public | no | Prometheus text exposition. See Prometheus metrics. |
Write plane#
Operator role and a usable licence, except the two rows marked otherwise. Every one of these writes an audit entry with the actor, the role, the source address and the outcome.
| Method | Path | Licence | What it does |
|---|---|---|---|
| POST | /api/stage | yes | Appends operations to the working set. The body is a bare array of {"op":"set"\|"delete","path":[…]}. Any other verb, an empty path, or an empty element in a path is 400 naming the index. Returns the whole working set, not just what you added. |
| POST | /api/stage/remove | yes | {"index":N} removes one operation. 400 when there is nothing at that index. |
| POST | /api/commit | yes | Commits the working set. {"confirm_minutes":N} arms the router's native commit-confirm. Returns committed counted from the payload actually sent. 400 when nothing is staged. |
| POST | /api/commit/confirm | yes | Confirms a pending commit-confirm, cancelling the reboot that would otherwise restore the previous configuration. |
| POST | /api/discard | yes | Empties the working set. |
| POST | /api/configure | yes | Applies an operation array directly, bypassing staging. Same body shape as /api/stage. |
| POST | /api/rollback | yes | {"revision":N}, default 1. Loads /config/archive/config.boot.<N>.gz and commits it — which is exactly what the CLI's rollback does, because the router's HTTP API has no rollback operation. Revision 0 is the running configuration. |
| POST | /api/config/save | yes | {"file":"…"} writes the running configuration to a file on the router; empty means the boot configuration. |
| POST | /api/config/load | yes | {"file":"…"} loads a configuration file that already exists on the router, and commits it. |
| POST | /api/reconcile | yes | {"desired_state":{…},"full":bool,"apply":bool}. Without apply it returns the operations and any warnings; with it, they are added to the working set. It never commits — the Commit Bar stays the single write point. 400 on an empty document. |
| POST | /api/capture | yes | Not implemented. Answers 501 with the monitor traffic command to run by hand. |
| POST | /api/diagnostics/traceroute | no | {"host":"…"} → the router's own traceroute output. A command, not a configuration change, so it needs the operator role and no licence. |
| POST | /api/ids/ips/plan | no | Renders the operations that would put the inline IPS engine in the forward path, with warnings, without applying them. |
| POST | /api/apps/plan | yes | Renders the operations an app install would stage, plus warnings, the image and whether it is already on the router. |
| POST | /api/apps/pull | yes | Pulls an image onto the router. The router refuses to commit a container whose image is not local, so this runs before the configuration change. Image references are validated against the OCI grammar. |
| POST | /api/apps/prepare | yes | Creates the host directories and files a container's bind mounts need. On-router it creates them; off-router it hands back the exact command instead of pretending. |
| POST | /api/apps/update | yes | Re-pulls an installed app's image and reports whether the image id changed. |
| POST | /api/apps/restart | yes | {"name":"…"}. Bounces a container as two commits — set container name <n> disable, then delete it. There is no control socket. 409 if it is already stopped. If the second commit fails the app is left down and the error says so in those words. |
| POST | /api/apps/feature | yes | Enables or disables a built-in feature module, such as the WireGuard page. |
| DELETE | /api/apps/image | yes | Removes an unused image from the router. |
| POST | /api/fleet/{id}/configure | fleet | Applies an operation array to one fleet router. |
Admin plane#
All admin. None needs a licence — an unlicensed router must still be able to manage
its own accounts.
| Method | Path | What it does |
|---|---|---|
| GET | /api/admin/users | Every account: name, role, TOTP, must-change-password, created, last login, and whether it is linked to the identity provider or has a password at all. |
| POST | /api/admin/users | {"name","password","role"}. Names match ^[A-Za-z0-9._-]{1,64}$; admin-token and anything starting token: are reserved. 409 if the name exists. New accounts are flagged must-change-password. |
| PATCH | /api/admin/users/{name} | role, password, oidc_subject, email — each optional. Demoting or deleting the last admin is 409. Any of these changes drops that account's sessions. Linking a subject another account claims is 409. |
| DELETE | /api/admin/users/{name} | 409 on your own account, and 409 on the last admin. |
| GET | /api/admin/tokens | Token metadata. Never the token. |
| POST | /api/admin/tokens | {"label","role","expires_days"}. The plaintext is returned exactly once; only its SHA-256 is stored. |
| DELETE | /api/admin/tokens/{id} | Revokes one token. 404 if there is no such id. |
| GET | /api/admin/settings | session_ttl_minutes, require_totp, audit_retention, commit_confirm_minutes. The stored licence key is blanked out of this response. |
| PUT | /api/admin/settings | The same four. Session TTL must be 5 minutes to 30 days; the commit-confirm window 1 to 60 minutes; audit retention below 100 is raised to 100. The stored licence key is preserved across the write. |
| POST | /api/system/power | {"action":"reboot"\|"poweroff"}. Not a configuration change, so not staged. Audited. |
| POST | /api/system/image | {"op":"add","url":…}, or {"op":"delete"\|"set_default","name":…}, or {"op":"show"}. add accepts http(s) URLs only and downloads and installs before answering; names must match ^[A-Za-z0-9][A-Za-z0-9._-]{0,63}$. Audited except for show. |
| PUT | /api/admin/license | {"key":"WHL1.…"}. Verified before it is stored, then refreshed against the licence server immediately so the page can show the answer. |
| DELETE | /api/admin/license | Removes the key. The write plane closes at once. |
The UI#
| Method | Path | Notes |
|---|---|---|
| GET | / | Registered only when --ui-dir is set. Serves the directory, falls back to index.html for any path that is not a real file, so the single-page app's own routes work on a hard refresh. |
See also#
- Role matrix — the same list, sorted by who may call it.
- Status codes — what each failure means.
- Authenticating — how to become a principal.
- What a read hides — the Redacts column, explained.
- The telemetry stream
- Audit entries — what a write leaves behind.
Checked against#
agent/main.go (routes and the handlers in it),
agent/opmode.go,
agent/apps.go,
agent/admin.go,
agent/authhttp.go,
agent/parity.go,
agent/gaps.go,
agent/ids.go,
agent/wan.go,
agent/fleet.go,
agent/desired.go,
agent/reconcile.go,
agent/license.go,
agent/endpoints_test.go,
docs/deploy.md "Agent endpoints".