Wheelhouse docs

What the licence gates#

A Wheelhouse licence unlocks configuration changes on a router, and nothing else. Without a usable licence the agent still signs you in and still serves every page, every counter, every log and the whole audit log. It refuses to add or apply changes, restore a revision, install an app or run the reconcile loop, saying so in one line with a link to the Licence page.

The gate is on the write plane, with one exception: the two fleet reads below, which read a different router.

Where the gate sits#

Every route in the write plane is gated twice — the operator role first, then the licence.

Needs a licenceDoes not
POST /api/stage, /api/stage/remove, /api/commit, /api/commit/confirm, /api/discardEvery GET about this router, including /api/config, /api/audit, /api/history and the telemetry stream
POST /api/configure, /api/rollback, /api/config/save, /api/config/load/api/auth/* — sign-in, sign-out, password change, TOTP enrolment, session listing
POST /api/reconcile, /api/captureGET /api/license itself, and GET /api/auth/status, which carries a licensed flag so the sign-in screen can say so
Every /api/apps/* writeGET /metrics
GET /api/fleet/{id}/config, GET /api/fleet/{id}/version — the two reads that are about a different router, and which also need the fleet feature
POST /api/diagnostics/traceroute and POST /api/ids/ips/plan — operator, but commands and plans rather than configuration changes
wheelhouse-agent plan, which reads and prints and changes nothing

The refusal is a 402 Payment Required whose body names the fix rather than just refusing:

json
{"error": "a licence is required to change configuration; enter one under Admin → Licence",
 "license": "unlicensed"}

When the licence exists but has a problem, the message is that problem — the licence expired on 2026-08-14 — followed by configuration changes are disabled.

The key, and why it works offline#

A key is WHL1.<kid>.<payload>.<signature>: an Ed25519 signature over a small JSON payload naming the licence id, the customer, the plan, the features, the router limit, the issue time, the expiry and the grace window. The agent verifies it against a public key compiled into its own binary, so a router with no route to the internet still knows what it holds.

kid identifies which public key signed it, so a rotation ships the new public key in a release and keeps the old one for existing tokens. A build carrying no key can verify nothing and refuses every licence — a build without keys is a build that cannot be licensed, not one licensed for free.

The states#

GET /api/license reports one of these, and the Licence page shows it:

StateWritesMeaning
unlicensedclosedNo key.
validopenInside the token's expiry.
graceopenPast expiry, inside the grace window. The page says when changes stop.
expiredclosedPast expiry and past grace.
revokedclosedThe server said so on a refresh.
invalidclosedThe stored key does not parse or does not verify — a bad signature, or a key id this build does not know.
clock-unverifiedopenThe router's clock reads earlier than the licence was issued.

The last state matters on a box with a bad clock. A clock reading before the issue time cannot be trusted to judge expiry either, and a forward jump from a broken time source would otherwise close the write plane — with set service ntp behind that same gate, so the fix would be locked behind the fault. The agent stays open and says why: this router's clock reads before the licence was issued; set the time (System → Settings → Time synchronisation) so expiry can be judged.

The grace window is a field in the token, in seconds, not a constant in the agent. The licence server issues seven days.

The daily refresh#

While the licence server is reachable, the agent refreshes: about 20 seconds after start, then every 24 hours, retrying hourly after a failure, and immediately after a new key is entered.

The request is a POST to /v1/refresh at the licence server — https://license.rhymelikedi.me by default, changeable with --license-server, and disabled entirely by --license-server ''. It carries four fields and nothing else: the key, a fingerprint of this installation, the router's host name and the agent version. The fingerprint is 16 hexadecimal characters — the first eight bytes of SHA-256 over /etc/machine-id plus a fixed string — so the server can count how many routers are using one licence without learning the machine id. Privacy sets out every field and what the connection itself reveals.

What the answers do:

AnswerEffect
A fresh tokenStored and used from then on; its expiry follows what has been paid for.
Network failureNothing changes. The current token stays good until its expiry plus grace.
403Revoked. The write plane closes at once, and the page says why.
402, 404, 409, 400Recorded, with the server's reason, so the page can show it. Whatever token is held keeps whatever validity it had.

Features within a plan#

Some capabilities are named in the token's features list rather than gated by expiry. Today that is fleet: /api/fleet/{id}/* is wrapped in requireFeature("fleet", …), which is requireLicense plus a check on the list, and answers 402 with the fleet feature is not part of this licence's plan. Fleet configuration loads regardless of the licence; the routes are what is gated.

Entering and removing a key#

  • In the UI: Admin → Licence, which is PUT /api/admin/license and needs the admin role. The key is verified before it is stored, so a typo is refused with the reason.
  • On the command line: --license-key-file /config/wheelhouse/license-key, a 0600 file. The agent refuses to read a secret file that is group- or world-readable. A key given by flag is also saved, so the next start without the flag still holds it.

Either way the key is stored in the agent's state file with the settings, and the settings endpoints never return it. Setting and removing a key are both audited.

What this gate is not#

The licence also does not touch the GPL boundary. It covers Wheelhouse's own agent and UI; the router underneath is GPL and its corresponding source is published — see Wheelhouse and VyOS.

See also#

Updated 2026-09-18 concepts licensing