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:

  1. vpp_startup_conf — the only hard abort. A failure here stops the restore
    with nothing else modified.
  2. vpp_restart — performed only if the startup configuration actually
    changed.
  3. controller_replace_deletes — replace mode only, removing items in reverse
    dependency order.
  4. 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.
  5. qt_settings — written atomically.
  6. 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.

Provisioning and Lab Surfaces