Wheelhouse docs

Stage and commit from a script#

You will end up with a script that changes the router's configuration the same way a person does: stage the operations, look at what is staged, commit with a confirm window, and confirm once the router still answers.

The staging area is shared state. A browser tab, this script and the reconcile loop all put operations in the same working set, and one Commit Bar commits all of it. That is worth designing around rather than around.

Before you start#

Step 1 — Stage the operations#

POST /api/stage takes a JSON array of operations.

bash
R=https://<router>:8443
T=wh_...
curl -sk -X POST -H "Authorization: Bearer $T" -H 'Content-Type: application/json' \
  -d '[
    {"op":"set","path":["service","dns","forwarding","cache-size","20000"]},
    {"op":"set","path":["system","host-name","edge"]}
  ]' "$R/api/stage"

The response is the whole working set, not just what you added:

json
{
  "staged": 2,
  "ops": [ … ],
  "commands": ["set service dns forwarding cache-size 20000", "set system host-name edge"]
}

That is deliberate: the queue is shared, so a client that only appended locally could un-stage by an index that meant a different operation on the agent.

The agent refuses two things here, while the request can still say why:

  • An op that is not set or delete. The Commit Bar renders exactly those; anything else would render dishonestly.
  • A path with no elements, or an empty element in it. description '' would otherwise fail later at commit, with an error that does not name this request.

Step 2 — Read what is staged#

bash
curl -sk -H "Authorization: Bearer $T" "$R/api/staged"

Both the operation list and the rendered CLI form come back. Log the commands array — that is the human-readable record of what your script is about to do.

To remove one:

bash
curl -sk -X POST -H "Authorization: Bearer $T" -H 'Content-Type: application/json' \
  -d '{"index":1}' "$R/api/stage/remove"

To clear it all:

bash
curl -sk -X POST -H "Authorization: Bearer $T" "$R/api/discard"

Step 3 — Commit with a confirm window#

bash
curl -sk -X POST -H "Authorization: Bearer $T" -H 'Content-Type: application/json' \
  -d '{"confirm_minutes":2}' "$R/api/commit"

This is VyOS' native commit-confirm. If no confirmation arrives inside the window, the router reboots into the previous configuration — that reboot is how VyOS implements the rollback.

Use it for any change that can cut off the caller: uplink addressing, firewall rules, NAT, the SSH or API services. In a script, use it for everything; the cost is two minutes of patience and the benefit is that a bad change undoes itself.

Step 4 — Confirm, after checking#

bash
# something that proves the router is still doing its job
curl -sfk -H "Authorization: Bearer $T" "$R/api/system" >/dev/null || exit 1

curl -sk -X POST -H "Authorization: Bearer $T" "$R/api/commit/confirm"

Do the check through the path the change could have broken. Confirming from a shell on the router itself proves nothing about whether the LAN can still reach it.

A whole script#

stage-and-commit.sh
#!/bin/sh
set -eu
R=${R:?router URL}
T=${T:?token}
api() { curl -sfk -H "Authorization: Bearer $T" "$@"; }

# Refuse to run if somebody else has something staged.
staged=$(api "$R/api/staged" | python3 -c 'import json,sys; print(json.load(sys.stdin).get("staged",0))')
[ "$staged" = "0" ] || { echo "working set is not empty ($staged ops); refusing" >&2; exit 1; }

api -X POST -H 'Content-Type: application/json' \
    -d @ops.json "$R/api/stage" | python3 -m json.tool

api -X POST -H 'Content-Type: application/json' \
    -d '{"confirm_minutes":2}' "$R/api/commit"

sleep 5
api "$R/api/system" > /dev/null
api -X POST "$R/api/commit/confirm"
echo "committed and confirmed"

The other write endpoints#

EndpointDoes
POST /api/configureDirect configure, bypassing staging. No diff, no review. Use it only where you have already decided, and prefer staging where a person might want to look.
POST /api/rollbackRestore an archived revision. {"revision":1} undoes the last commit — Undo the last commit.
POST /api/config/saveWrite the running configuration to the boot configuration
POST /api/config/loadLoad a configuration file that is already on the router. Commits immediately, with no preview — Restore a configuration.
POST /api/reconcileDiff a desired-state document; apply:true stages the difference — Config as code

Check it worked#

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

Every commit is audited with the actor, the source address, the role and the operations. A script's actor is the token's label, which is the reason to issue one token per consumer.

And confirm the change is really there:

bash
curl -sk -H "Authorization: Bearer $T" "$R/api/config/commands" | grep cache-size

Undoing it#

bash
curl -sk -X POST -H "Authorization: Bearer $T" -H 'Content-Type: application/json' \
  -d '{"revision":1}' "$R/api/rollback"

See also#


Checked against agent/main.go · agent/staging.go · docs/deploy.md · docs/api-cookbook.md

Updated 2026-09-02 api staging commit automation