Restore
POST /api/bng/snapshots/{id}/restore admin_only
| Field | Default | Meaning |
|---|---|---|
confirm |
— | Must be exactly true unless dry_run is set |
dry_run |
false |
Plan only: no lock taken, no writes performed |
mode |
"merge" |
merge applies the snapshot's items; replace first removes current items absent from the snapshot |
Concurrency is enforced with an exclusive on-disk lock. A second restore
attempt while one is in progress returns 409 with the holder's process ID and
start time, both in the body and in a response header. This is the guard that
makes restore safe to expose to automation.
Always dry-run first. A dry run short-circuits before the lock is taken and
before anything is written, returning the same step structure with would-
prefixed statuses. Its one honest limitation is that the controller
configuration step cannot be verified without performing it, and reports as
would-ok with a note to that effect.
A real restore proceeds in ordered steps, and the order is chosen so that the
riskiest write happens first, while nothing else has been touched:
vpp_startup_conf— the only hard abort. A failure here stops the restore
with nothing else modified.vpp_restart— performed only if the startup configuration actually
changed.controller_replace_deletes— replace mode only, removing items in reverse
dependency order.controller_config— applied in the controller's natural dependency order:
DNS, then pools, then plans, then RADIUS, then captive portal, then interface
enablement, then uplinks. Uplinks are last because sub-interface creation
requires the parent access port to be up.qt_settings— written atomically.grub_and_sysctl— each gated on an actual text change, with the GRUB
command line surgically substituted rather than the file rewritten.
{
"ok": false,
"result": "partial",
"steps": [
{ "name": "vpp_startup_conf", "status": "ok", "detail": "" },
{ "name": "vpp_restart", "status": "skipped", "detail": "startup.conf unchanged" },
{ "name": "controller_config", "status": "ok", "detail": "9 blocks applied" },
{ "name": "qt_settings", "status": "ok", "detail": "" },
{ "name": "grub_and_sysctl", "status": "failed", "detail": "update-grub rc=1: …" }
],
"requires_reboot": true,
"reboot_reasons": ["grub_cmdline_changed"],
"extras_deleted": [],
"mode": "merge"
}
result is ok, partial, failed, or needs_deep_reset. Status codes map
accordingly: 200 for success, 207 Multi-Status for partial, 409 for a
held lock or a wedged pipeline, 400 for a missing confirmation, 404 for an
unknown snapshot.
The restore is not transactional and there is no automatic rollback. This is
stated plainly because it drives integration design: a partial result is a real
outcome an automation must handle, and the per-step report exists precisely so it
can. The recommended pattern is dry-run, then restore, then diff to confirm
convergence.
In replace mode, the subscriber and captive pools are hardcoded as
non-deletable, and a delete returning "not found" is treated as success so the
operation is idempotent.
If the controller's state machine is found wedged, the result is
needs_deep_reset with a hint naming the recovery action. A deep reset is never
triggered automatically.
Identity and Key Administration
GET /api/bng/api-keys?include_sessions=<bool> admin_only
POST /api/bng/api-keys admin_only
PATCH /api/bng/api-keys/{id} admin_only
DELETE /api/bng/api-keys/{id} admin_only
POST /api/bng/api-keys/{id}/rotate admin_only
Key administration is itself an API, which is what makes credential lifecycle
automatable — provisioning a new monitoring system, rotating a credential on a
schedule, or revoking one during an incident are all scripted operations rather
than console work.
The listing returns metadata only:
{
"keys": [
{ "id": "nms-poller", "label": "Solarwinds collector", "roles": ["read_only"],
"revoked": false, "created_at": 1782700000.0,
"fingerprint": "sha256:9f8b2c146e3a4a7d", "imported": false }
]
}
Raw key material is never returned here, or anywhere except at creation. The
fingerprint is a truncated digest suitable for identifying a key in the UI or
in a log without disclosing it. Browser session records are stored alongside API
keys and are filtered out unless include_sessions is set.
Creation takes an id, a role (or a roles list), an optional label, and the
three optional restrictions described in Access Model. The response is the one
and only disclosure of the key:
{ "id": "nms-poller", "raw": "quantum_9f8b2c146e3a4a7d1c0e4d5a6b7c8d9e", "fingerprint": "sha256:9f8b2c146e3a4a7d" }
Store it at that moment; it cannot be recovered.
Revocation is a PATCH setting revoked, returning 204. Deletion removes the
record entirely, also 204. Rotation revokes the existing key and mints a
replacement under the same identifier, preserving the role and label and
returning the new raw material.
Every mutation triggers a controller reload so the controller's own view of
valid credentials converges immediately. A key revoked through this API stops
working on both planes within one reload cycle.
One operational caution for rotation. Rotation replaces the credential
in-place with no overlap window — the moment it returns, the old key is dead.
For a scraper or a poller that cannot tolerate a gap, the safer pattern is a
two-identity rotation: create a new key under a new identifier, deploy it to
the consumer, confirm traffic on the new credential, then revoke the old one.
This is the pattern the platform's own scrape-credential rotation uses.