Config Backups API
EnterprisePull device configurations, read stored versions, compare versions, and run backup snapshots across many devices with the NetStacks Controller API.
Overview
Every config backup the Controller takes is stored as a numbered version per device. A snapshot is a backup run over a set of devices; it groups the versions it collected and tracks how many devices succeeded or failed. These are the backups shown on a device's Config Backups tab and under Config Automation → Snapshots.
All endpoints need devices.access. They are not in the Swagger UI. For the UI side, see Config snapshots.
Endpoints
Per-device backups
| Method | Path | Purpose | Permission |
|---|---|---|---|
POST | /api/config/devices/:device_id/pull | Back up one device now. Query: method = cli, structured, or both (default) | devices.access |
POST | /api/config/devices/bulk-pull | Back up several devices in the background. Body: {"device_ids":[…],"method":"both"} | devices.access |
GET | /api/config/devices/:device_id/configs | List stored versions (newest first, no config text). Query: limit, offset | devices.access |
GET | /api/config/devices/:device_id/configs/latest | The newest stored version, with config text | devices.access |
GET | /api/config/devices/:device_id/configs/:version | One stored version, with config text | devices.access |
PUT | /api/config/devices/:device_id/configs/:version/notes | Set operator notes on a version. Body: {"notes":"…"} | devices.access |
GET | /api/config/devices/:device_id/diff-versions | Unified diff of two stored versions. Query: old_version, new_version | devices.access |
Snapshots
| Method | Path | Purpose | Permission |
|---|---|---|---|
GET | /api/devices/snapshots | List snapshots: {"snapshots":[…],"total":n} | devices.access |
POST | /api/devices/snapshots | Create a snapshot definition | devices.access |
GET | /api/devices/snapshots/:id | Get a snapshot: {"snapshot":{…}} | devices.access |
DELETE | /api/devices/snapshots/:id | Delete a snapshot (204) | devices.access |
POST | /api/devices/snapshots/:id/collect | Collect configs from the matching devices | devices.access |
POST | /api/devices/snapshots/:id/recollect-failed | Collect again from only the devices that failed | devices.access |
GET | /api/devices/snapshots/:id/backups | Per-device results of the snapshot | devices.access |
GET | /api/devices/snapshots/backups/:config_id/log | The session transcript of one device backup: {"log":"…"} | devices.access |
Back up one device
POST /api/config/devices/:device_id/pull logs in with the device's connection profile and stores the running config. cli uses SSH; structured uses the profile's gNMI or NETCONF transport. With both, each method is tried and stored on its own.
curl -s --cacert netstacks-ca.pem -X POST \
"https://controller.example.com:3000/api/config/devices/8d1e.../pull?method=cli" \
-H "Authorization: Bearer $TOKEN"On success the response is 201:
{ "message": "Config pull complete", "structured": false, "cli": true }If every requested method fails, the response is 502. The failed attempts are still stored as versions with status: "failed" and an error_message, so you can see why on the device's Config Backups tab.
POST /api/config/devices/bulk-pull returns 202 right away with {"message", "snapshot_id", "devices_queued"}. The work runs in the background as a snapshot; follow it with GET /api/devices/snapshots/:snapshot_id.
Read stored versions
GET /api/config/devices/:device_id/configs returns {"configs":[…],"total":n}. Each item describes one version without the config text:
{
"id": "f0c1...",
"device_id": "8d1e...",
"version": 14,
"config_format": "cli",
"config_hash": "9b2f...",
"source": "pull",
"pulled_via": "ssh",
"status": "success",
"error_message": null,
"snapshot_id": null,
"notes": "Before WAN cutover",
"notes_by": "jdoe",
"created_at": "2026-10-12T02:00:07Z"
}Fetch the text with /configs/latest or /configs/:version. Those responses add config_text.
Compare versions
curl -s --cacert netstacks-ca.pem \
"https://controller.example.com:3000/api/config/devices/8d1e.../diff-versions?old_version=13&new_version=14" \
-H "Authorization: Bearer $TOKEN"{
"old_version": 13,
"new_version": 14,
"old_format": "cli",
"new_format": "cli",
"identical": false,
"diff": " hostname core-sw-01\n-ntp server 10.0.0.5\n+ntp server 10.0.0.6\n ...",
"additions": 1,
"deletions": 1
}diff lists the config line by line. Removed lines start with -, added lines with +, and unchanged lines with a space. A version that doesn't exist returns 404.
Snapshots (many devices)
A snapshot is created first, then collected:
# 1. Create the snapshot (optionally limited to a site and/or device type)
SNAP=$(curl -s --cacert netstacks-ca.pem -X POST https://controller.example.com:3000/api/devices/snapshots \
-H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" \
-d '{"name":"Pre-change HQ","device_filter":{"site_id":"c07b...","device_type":"cisco_ios"}}' \
| jq -r .snapshot.id)
# 2. Collect configs from the matching devices (runs in the background)
curl -s --cacert netstacks-ca.pem -X POST https://controller.example.com:3000/api/devices/snapshots/$SNAP/collect \
-H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" -d '{}'
# 3. Check progress and per-device results
curl -s --cacert netstacks-ca.pem https://controller.example.com:3000/api/devices/snapshots/$SNAP \
-H "Authorization: Bearer $TOKEN" | jq '.snapshot | {status, total_devices, successful_devices, failed_devices}'
curl -s --cacert netstacks-ca.pem https://controller.example.com:3000/api/devices/snapshots/$SNAP/backups \
-H "Authorization: Bearer $TOKEN"device_filteracceptssite_idanddevice_type. Leave it out (or send{}) for all devices.collectaccepts an optionaltimeout_secondsand returns202withdevices_queued. If no devices match, it returns400“No devices match the filter”.- Each entry in
/backupshasdevice_name,version,size_bytes,line_count,backup_method,status, anderror_message.
The snapshot endpoints return error messages as plain text (for example Snapshot not found), not as a JSON object. See Errors.