NetStacksNetStacks

Stacks API

Combine config templates into deployable stacks, create instances with per-device overrides, deploy to devices, and roll back via the REST API.

Overview

Controller feature

Config stacks, instances, and deployments are part of the NetStacks Controller. All endpoints below are served by the Controller API under the /api/config prefix and require an authenticated operator token.

The Stacks API lets you combine multiple configuration templates into a single deployable unit called a stack. A stack lists one or more services — each service binds a config template to a render order and a set of variables. You then create a deployment (directly, or from a saved instance) to render every service per device and push the result to your network.

Use stacks to deploy consistent configuration across your network: a “DC1 Edge Router” stack might combine VLAN, BGP, and ACL templates and deploy them to every edge router with device-specific values for router IDs, neighbor IPs, and ASNs.

All examples below assume a base URL of https://netstacks.example.net and a bearer token from the Authentication API.

Resource Model

There are three related resources:

Stack — /api/config/stacks
A reusable blueprint. It has a name, description, an atomic flag, and a services array. Each service references one config template by template_id with a render order and per-service variables.
Instance — /api/config/instances
A saved, not-yet-deployed binding of a stack to target devices and variable values. Instances are a top-level resource (not nested under a stack); the stack is referenced by stack_id in the body. Deploying an instance creates a deployment.
Deployment — /api/config/deployments
An execution record. It tracks overall status, per-device results, logs, and config backups, and is the resource you poll for progress and call to roll back.

The services array

A stack’s services entries each have these fields:

FieldTypeDescription
template_idstring (UUID)The config template this service renders.
orderintegerRender/apply order within the stack (ascending).
variablesobjectDefault variable values for this service’s template.
api_variablesobjectOptional. Variables resolved from an API resource at deploy time (resource_id, method, path, json_path).

The atomic flag

atomic defaults to true. When set, the deployment treats the stack as a unit during execution; see your deployment results and logs for per-device outcomes.

Endpoint Reference

All paths are relative to the Controller’s API root.

Stacks

GET    /api/config/stacks                      # list stacks
POST   /api/config/stacks                      # create a stack
GET    /api/config/stacks/:id                  # get a stack
PUT    /api/config/stacks/:id                  # update a stack
DELETE /api/config/stacks/:id                  # delete a stack
POST   /api/config/stacks/:id/render           # dry-run render (no deploy)
POST   /api/config/stacks/:id/resolve-variables# resolve API variables

Instances

GET    /api/config/instances                   # list instances
POST   /api/config/instances                   # create an instance
GET    /api/config/instances/:id               # get an instance
PUT    /api/config/instances/:id               # update an instance
DELETE /api/config/instances/:id               # delete an instance
POST   /api/config/instances/:id/deploy        # deploy -> creates a deployment
POST   /api/config/instances/:id/deploy-with-mop  # deploy via a MOP plan

Deployments

POST   /api/config/deployments                 # create & execute a deployment
GET    /api/config/deployments                 # list (filter ?stack_id=)
GET    /api/config/deployments/:id             # detail incl. per-device results
GET    /api/config/deployments/:id/logs        # deployment logs (?device_id=)
POST   /api/config/deployments/:id/rollback    # roll back a deployment

Working with Stacks

Create a stack

A create request needs name and a services array. description is optional and atomic defaults to true.

create-stack.shbash
curl -X POST https://netstacks.example.net/api/config/stacks \
  -H "Authorization: Bearer eyJhbGciOiJIUzI1NiIs..." \
  -H "Content-Type: application/json" \
  -d '{
    "name": "DC1 Edge Router Config",
    "description": "Standard edge router config: VLANs + BGP + ACLs",
    "atomic": true,
    "services": [
      {
        "template_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
        "order": 0,
        "variables": { "site": "dc1" }
      },
      {
        "template_id": "b2c3d4e5-f6a7-8901-bcde-f23456789012",
        "order": 1,
        "variables": { "local_asn": "65001" }
      },
      {
        "template_id": "c3d4e5f6-a7b8-9012-cdef-345678901234",
        "order": 2,
        "variables": { "acl_name": "EDGE-INBOUND" }
      }
    ]
  }'

# Response (201 Created): the full ConfigStack, e.g.
# {
#   "id": "d4e5f6a7-b8c9-0123-defa-456789012345",
#   "name": "DC1 Edge Router Config",
#   "atomic": true,
#   "services": [ ... ],
#   "shared": false,
#   "created_at": "2026-03-10T16:00:00Z",
#   "updated_at": "2026-03-10T16:00:00Z"
# }
create-stack.pypython
import requests

BASE = "https://netstacks.example.net/api/config"
headers = {"Authorization": "Bearer eyJhbGciOiJIUzI1NiIs..."}

stack = requests.post(f"{BASE}/stacks", headers=headers, json={
    "name": "DC1 Edge Router Config",
    "description": "Standard edge router config: VLANs + BGP + ACLs",
    "atomic": True,
    "services": [
        {"template_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890", "order": 0,
         "variables": {"site": "dc1"}},
        {"template_id": "b2c3d4e5-f6a7-8901-bcde-f23456789012", "order": 1,
         "variables": {"local_asn": "65001"}},
        {"template_id": "c3d4e5f6-a7b8-9012-cdef-345678901234", "order": 2,
         "variables": {"acl_name": "EDGE-INBOUND"}},
    ],
}).json()
print("Created stack:", stack["id"])

Preview rendering (dry run)

POST /api/config/stacks/:id/render resolves the stack against a per-service config and returns rendered config jobs without touching any device. The variable_values body uses a per-service shape keyed by service index, where each entry carries devices, shared_vars, and device_vars.

render-preview.shbash
curl -X POST https://netstacks.example.net/api/config/stacks/d4e5f6a7-.../render \
  -H "Authorization: Bearer eyJhbGciOiJIUzI1NiIs..." \
  -H "Content-Type: application/json" \
  -d '{
    "variable_values": {
      "0": {
        "devices": ["f47ac10b-58cc-4372-a567-0e02b2c3d479"],
        "shared_vars": { "site": "dc1" },
        "device_vars": {
          "f47ac10b-58cc-4372-a567-0e02b2c3d479": { "vlan_id": "100" }
        }
      }
    }
  }'

# Response: { "jobs": [
#   { "device_id": "...", "device_name": "core-rtr-01.dc1",
#     "template_id": "...", "template_name": "vlan-base",
#     "service_order": 0, "rendered_config": "vlan 100\n name ...",
#     "target_path": "...", "operation": "merge", "config_format": "..." }
# ] }
Resolve API variables

If a stack uses API-backed variables, call POST /api/config/stacks/:id/resolve-variables with a target_devices array (objects with device_id). The response returns resolved shared values and per_device values you can feed into a render or deployment.

Stack Instances

An instance saves a stack binding so you can review and deploy it later. Instances are created at the top-level /api/config/instances endpoint with stack_id in the body. The fields are stack_id, name, target_devices, variable_values, and device_overrides (the last three are JSON; they default to empty).

create-instance.shbash
curl -X POST https://netstacks.example.net/api/config/instances \
  -H "Authorization: Bearer eyJhbGciOiJIUzI1NiIs..." \
  -H "Content-Type: application/json" \
  -d '{
    "stack_id": "d4e5f6a7-b8c9-0123-defa-456789012345",
    "name": "dc1-edge-deploy-2026-03",
    "target_devices": [
      "f47ac10b-58cc-4372-a567-0e02b2c3d479",
      "e36bd20a-47bb-3261-9456-1d13a1b2c368"
    ],
    "variable_values": { "site": "dc1", "local_asn": "65001" },
    "device_overrides": {
      "f47ac10b-58cc-4372-a567-0e02b2c3d479": { "router_id": "10.0.1.1", "vlan_id": "100" },
      "e36bd20a-47bb-3261-9456-1d13a1b2c368": { "router_id": "10.0.2.1", "vlan_id": "200" }
    }
  }'

# Response (201 Created): the ConfigStackInstance with "id" and "state".

Deploy an instance

POST /api/config/instances/:id/deploy reads the saved instance and creates a deployment from it. The response is a ConfigDeployment (status 201 Created); execution then runs in the background.

deploy-instance.shbash
curl -X POST \
  https://netstacks.example.net/api/config/instances/INSTANCE_ID/deploy \
  -H "Authorization: Bearer eyJhbGciOiJIUzI1NiIs..."

# Response (201 Created):
# {
#   "id": "9f0e1d2c-...",      # deployment id
#   "stack_id": "d4e5f6a7-...",
#   "name": "dc1-edge-deploy-2026-03",
#   "status": "pending",
#   "total_devices": 2,
#   "succeeded_count": 0,
#   "failed_count": 0,
#   "created_at": "2026-03-10T16:05:00Z"
# }
Deploy with a MOP

POST /api/config/instances/:id/deploy-with-mop turns the stack’s deployment procedure into a Method of Procedure plan. The optional body accepts control_mode and name. Use this when you want gated, step-by-step execution instead of a direct push.

Deployments & Rollback

Create a deployment directly

You can skip instances and create a deployment in one call. The body is a CreateDeployment: stack_id, name, target_devices (objects with device_id and optional credential_id), plus optional variable_values and device_overrides.

create-deployment.shbash
curl -X POST https://netstacks.example.net/api/config/deployments \
  -H "Authorization: Bearer eyJhbGciOiJIUzI1NiIs..." \
  -H "Content-Type: application/json" \
  -d '{
    "stack_id": "d4e5f6a7-b8c9-0123-defa-456789012345",
    "name": "dc1-edge-deploy-2026-03",
    "target_devices": [
      { "device_id": "f47ac10b-58cc-4372-a567-0e02b2c3d479" },
      { "device_id": "e36bd20a-47bb-3261-9456-1d13a1b2c368", "credential_id": "cred-uuid" }
    ],
    "variable_values": {
      "0": {
        "devices": ["f47ac10b-58cc-4372-a567-0e02b2c3d479"],
        "shared_vars": { "site": "dc1" },
        "device_vars": {
          "f47ac10b-58cc-4372-a567-0e02b2c3d479": { "router_id": "10.0.1.1" }
        }
      }
    }
  }'

# Response (201 Created): a ConfigDeployment with status "pending".

Track progress

There is no separate status endpoint — poll the deployment detail at GET /api/config/deployments/:id. The response includes overall status, the counters total_devices / succeeded_count / failed_count, and a devices array with per-device results.

poll-deployment.pypython
import requests, time

BASE = "https://netstacks.example.net/api/config"
headers = {"Authorization": "Bearer eyJhbGciOiJIUzI1NiIs..."}

deployment_id = "9f0e1d2c-..."  # from the create/deploy response
terminal = {"completed", "failed", "rolled_back"}

while True:
    d = requests.get(f"{BASE}/deployments/{deployment_id}", headers=headers).json()
    print(f"{d['status']}: {d['succeeded_count']}/{d['total_devices']} ok, "
          f"{d['failed_count']} failed")
    if d["status"] in terminal:
        break
    time.sleep(5)

Deployment status moves through pending → in_progress → completed or failed (and rolled_back after a rollback). Each entry in the devices array has its own status (for example completed, failed, or rolled_back) and may carry an error_message and a backup_config_id.

Read logs

deployment-logs.shbash
# All logs for a deployment
curl "https://netstacks.example.net/api/config/deployments/DEPLOY_ID/logs" \
  -H "Authorization: Bearer eyJhbGciOiJIUzI1NiIs..."

# Filter to one device
curl "https://netstacks.example.net/api/config/deployments/DEPLOY_ID/logs?device_id=DEVICE_ID&limit=200" \
  -H "Authorization: Bearer eyJhbGciOiJIUzI1NiIs..."

# Each log entry: { "level": "info|error", "message": "...",
#                   "device_id": "...", "created_at": "..." }

Roll back

Rollback is first-class: POST /api/config/deployments/:id/rollback restores devices from the config backups captured before the deployment. The deployment must be in completed or failed status; otherwise the call returns 400 Bad Request.

rollback.shbash
curl -X POST \
  https://netstacks.example.net/api/config/deployments/DEPLOY_ID/rollback \
  -H "Authorization: Bearer eyJhbGciOiJIUzI1NiIs..."

# Response: the updated ConfigDeployment.
# On success its status becomes "rolled_back".

List deployment history

list-deployments.shbash
# All deployments
curl "https://netstacks.example.net/api/config/deployments" \
  -H "Authorization: Bearer eyJhbGciOiJIUzI1NiIs..."

# Filter to one stack's deployments
curl "https://netstacks.example.net/api/config/deployments?stack_id=STACK_ID&limit=50" \
  -H "Authorization: Bearer eyJhbGciOiJIUzI1NiIs..."

Questions & Answers

What is the correct base path for stacks?
Stacks live under the config router: /api/config/stacks. Instances and deployments are separate top-level resources at /api/config/instances and /api/config/deployments. There is no /api/stacks path.
How do I define which templates a stack includes?
Use the services array. Each service has a template_id, an order, and a variables object. There is no template_ids field.
How do I deploy a stack to multiple devices?
Either create a deployment directly at POST /api/config/deployments with a target_devices array, or save an instance and call POST /api/config/instances/:id/deploy. Both run the deployment in the background.
How do I set per-device variable overrides?
Provide a device_overrides object keyed by device UUID, or use the per-service device_vars map inside variable_values. Device-specific values take priority over shared stack values.
How do I check deployment progress?
Poll GET /api/config/deployments/:id for overall status, the counters (total_devices, succeeded_count, failed_count), and per-device results. For detail, read GET /api/config/deployments/:id/logs.
What are the deployment statuses?
pending → in_progress → completed or failed, plus rolled_back after a successful rollback.
Can I roll back a failed deployment?
Yes. Call POST /api/config/deployments/:id/rollback. NetStacks backs up each device before applying changes and restores from those backups. Only completed or failed deployments can be rolled back.
Can I preview the rendered config before deploying?
Yes. POST /api/config/stacks/:id/render returns rendered config jobs per device without pushing anything.

Troubleshooting

404 on /api/stacks

The resource is namespaced under config. Use /api/config/stacks, /api/config/instances, and /api/config/deployments.

Deployment stays in “pending” or fails fast

Execution runs as a background task after the 201 Created response. If it does not progress, read GET /api/config/deployments/:id/logs. Common causes are a template render error (a required variable missing from variables, variable_values, or device_overrides) or no resolvable target devices.

Device unreachable during deploy

The Controller cannot reach the device. Verify the device address, port, and credentials. Supply a credential_id on the target device if the stack should use a specific credential. See the Devices API.

Variable override not applied

Device-specific values (device_overrides / device_vars) take priority over shared values, which take priority over a service’s template defaults. Ensure the variable name matches the template variable exactly (case-sensitive).

Rollback rejected with 400

Rollback only applies to deployments in completed or failed status. A deployment still in_progress cannot be rolled back — wait for it to reach a terminal status first.