Stacks & Deployments API
EnterpriseCreate config stacks, preview what they render per device, save instances, run deployments, follow their logs, and roll back through the NetStacks Controller API.
Overview
A stack is an ordered list of services. Each service points at one template and can carry default variable values. A deployment renders the stack for chosen devices and pushes the result. An instance is a saved, ready-to-run deployment (devices and values filled in) that you can deploy later or on a schedule.
All endpoints are under /api/config and need devices.access. They are not in the Swagger UI. For the UI workflow, see Creating stacks.
Endpoints
Stacks
| Method | Path | Purpose | Permission |
|---|---|---|---|
GET | /api/config/stacks | List stacks (array). Query: limit, offset | devices.access |
POST | /api/config/stacks | Create a stack | devices.access |
GET | /api/config/stacks/:id | Get a stack | devices.access |
PUT | /api/config/stacks/:id | Update a stack (only the fields you send) | devices.access |
DELETE | /api/config/stacks/:id | Delete a stack (204) | devices.access |
POST | /api/config/stacks/:id/render | Render every service for its devices without deploying | devices.access |
POST | /api/config/stacks/:id/resolve-variables | Fetch variable values that are mapped to API resources | devices.access |
Deployments
| Method | Path | Purpose | Permission |
|---|---|---|---|
POST | /api/config/deployments | Create and start a deployment | devices.access |
GET | /api/config/deployments | List deployments (array). Query: stack_id, limit, offset | devices.access |
GET | /api/config/deployments/:id | A deployment with its per-device results | devices.access |
GET | /api/config/deployments/:id/logs | Deployment log lines. Query: device_id, limit (default 100, max 1000), offset | devices.access |
POST | /api/config/deployments/:id/rollback | Restore the pre-deployment backups | devices.access |
Instances
| Method | Path | Purpose | Permission |
|---|---|---|---|
GET | /api/config/instances | List saved instances | devices.access |
POST | /api/config/instances | Create an instance | devices.access |
GET / PUT / DELETE | /api/config/instances/:id | Get, update, or delete an instance | devices.access |
POST | /api/config/instances/:id/deploy | Start a deployment from the instance | devices.access |
Create a stack
{
"name": "branch-baseline",
"description": "Hostname, SNMP, NTP for new branch routers",
"atomic": true,
"services": [
{ "template_id": "a1f0...", "order": 1, "variables": {} },
{ "template_id": "7c90...", "order": 2, "variables": { "community": "n0c-ro" } }
]
}| Field | Type | Notes |
|---|---|---|
name | string | Required. |
services | array | Required. Each item: template_id (string UUID), order (integer), optional variables (default values) and api_variables. |
atomic | boolean | Default true. |
shared | boolean | Default false. |
description | string | Optional. |
variable_config | object | Optional per-variable settings used by the stack editor (scope shared or per_device, API-resource lookups). |
The response is 201 with the stack.
The variable_values object
Rendering, deployments, and instances all use a variable_values object that says, per service, which devices it runs on and which values to use. Keys are the service's position in the services array as a string ("0", "1", …):
{
"0": {
"devices": ["8d1e...", "a41f..."],
"shared_vars": { "domain": "corp.example.com" },
"device_vars": {
"8d1e...": { "hostname": "br-rtr-01" },
"a41f...": { "hostname": "br-rtr-02" }
}
},
"1": {
"devices": ["8d1e...", "a41f..."],
"shared_vars": { "location": "Branch 12" },
"device_vars": {}
}
}For each device, values merge in this order, later winning: the service's default variables, then shared_vars, then that device's device_vars. A service with no devices is skipped.
Preview the rendered config
POST /api/config/stacks/:id/render with {"variable_values": {…}} renders every service for its devices and returns the results. Nothing is pushed.
{
"jobs": [
{
"device_id": "8d1e...",
"device_name": "br-rtr-01",
"template_id": "a1f0...",
"template_name": "hostname",
"service_order": 1,
"rendered_config": "{ \"hostname\": \"br-rtr-01\" }",
"target_path": "/openconfig-system:system/config",
"operation": "merge",
"config_format": "json"
}
]
}A missing template, unknown device, or render error returns 400 with {"error":"Stack resolution failed: …"}.
Run a deployment
POST /api/config/deployments creates the deployment and starts it in the background. The call returns right away with 201 and the deployment in pending status.
curl -s --cacert netstacks-ca.pem -X POST https://controller.example.com:3000/api/config/deployments \
-H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" \
-d @deploy.json{
"stack_id": "5b3e...",
"name": "Branch 12 baseline",
"target_devices": [ { "device_id": "8d1e..." }, { "device_id": "a41f..." } ],
"variable_values": {
"0": { "devices": ["8d1e...", "a41f..."], "shared_vars": {}, "device_vars": {} }
}
}| Field | Type | Notes |
|---|---|---|
stack_id | string (UUID) | Required. |
name | string | Required. Shown in deployment history. |
target_devices | array | Required. [{"device_id": "…"}]. |
variable_values | object | Per-service devices and values (see above). |
Before changing a device, the deployment takes a backup of its config. That backup is what rollback restores.
Deployments log in with each device's connection profile. The profile needs an automation transport (gNMI or NETCONF) that matches the template's platform.
Follow a deployment
Poll GET /api/config/deployments/:id until status is completed or failed (it is in_progress while running). The response is the deployment plus a devices array with one entry per device and service:
{
"id": "d77a...",
"stack_id": "5b3e...",
"name": "Branch 12 baseline",
"status": "completed",
"total_devices": 2,
"succeeded_count": 2,
"failed_count": 0,
"started_at": "2026-10-12T14:00:02Z",
"completed_at": "2026-10-12T14:00:19Z",
"devices": [
{
"device_id": "8d1e...",
"service_name": "hostname",
"status": "completed",
"rendered_config": "...",
"backup_config_id": "f0c1...",
"error_message": null
}
]
}For step-by-step detail, read GET /api/config/deployments/:id/logs. Each line has level, message, device_id, and created_at. The fields shown above are trimmed. The full deployment also includes target_devices, variable_values, and audit fields.
Roll back
POST /api/config/deployments/:id/rollback pushes each device's pre-deployment backup back to it. It only works for deployments in completed or failed status that have at least one device backup. Otherwise it returns 400. While it runs, the deployment status is rolling_back, then rolled_back.
Instances
An instance stores everything a deployment needs so it can be run again. Create one with POST /api/config/instances:
{
"stack_id": "5b3e...",
"name": "Branch 12 baseline",
"target_devices": [ { "device_id": "8d1e..." } ],
"variable_values": { "0": { "devices": ["8d1e..."], "shared_vars": {}, "device_vars": {} } }
}POST /api/config/instances/:id/deploy (no body) starts a deployment from the instance and returns it with 201, just like POST /api/config/deployments. Instances can also be deployed by a scheduled task of type deployment.