NetStacksNetStacks

Templates API

Create, manage, and render Jinja2 configuration templates via the API with version tracking and variable extraction.

Overview

The Templates API lets you manage Jinja2 configuration templates programmatically. Templates define reusable network configurations with variables that are filled in at render time. Use the API to create, list, update, render, and version templates.

All template endpoints are under /api/devices scope (managed by the Controller) and require authentication. See API Authentication for token setup.

Template Variables

NetStacks automatically extracts Jinja2 variables from your template content. Variables like {{ hostname }} and {{ vlan_id }} are detected and listed so you know exactly what values to provide at render time.

How It Works

Templates are stored as Jinja2 content with metadata including name, description, associated device types, and tags. When you create or update a template, NetStacks parses the Jinja2 content and extracts all variable references automatically.

Template Rendering

The render endpoint accepts a set of variable values and returns the fully rendered configuration. This lets you preview configs before deploying them. Rendering validates that all required variables are provided and that the Jinja2 syntax is correct.

Version Management

Each template tracks versions. When you update a template, a new version is created. You can retrieve any previous version and compare versions to see what changed.

Step-by-Step Guide

1. Authenticate

Obtain a JWT token from the Authentication API.

2. Create a Template

Send a POST with the template name, Jinja2 content, and optional metadata (description, device types, tags).

3. List and Search Templates

Use GET /api/templates to browse available templates with optional filtering.

4. Render with Variables

Call the render endpoint with variable values to preview the generated configuration before deployment.

5. Update and Version

Update template content with PUT. Each update creates a new version that can be referenced later.

Code Examples

Create a Template

create-template.shbash
curl -X POST https://netstacks.example.net/api/templates \
  -H "Authorization: Bearer eyJhbGciOiJIUzI1NiIs..." \
  -H "Content-Type: application/json" \
  -d '{
    "name": "VLAN Configuration",
    "description": "Standard VLAN interface config for Cisco IOS",
    "content": "interface Vlan{{ vlan_id }}\n description {{ description }}\n ip address {{ ip_address }} {{ subnet_mask }}\n no shutdown",
    "device_types": ["cisco_ios"],
    "tags": ["vlan", "l2", "dc1"]
  }'

# Response (201 Created):
# {
#   "id": "b2c3d4e5-f6a7-8901-bcde-f23456789012",
#   "name": "VLAN Configuration",
#   "description": "Standard VLAN interface config for Cisco IOS",
#   "content": "interface Vlan{{ vlan_id }}\n ...",
#   "device_types": ["cisco_ios"],
#   "variables": ["vlan_id", "description", "ip_address", "subnet_mask"],
#   "version": 1,
#   "created_at": "2026-03-10T15:00:00Z"
# }
create-template.pypython
import requests

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

# Create a VLAN configuration template
template_content = """interface Vlan{{ vlan_id }}
 description {{ description }}
 ip address {{ ip_address }} {{ subnet_mask }}
 no shutdown"""

resp = requests.post(f"{base_url}/templates", headers=headers, json={
    "name": "VLAN Configuration",
    "description": "Standard VLAN interface config for Cisco IOS",
    "content": template_content,
    "device_types": ["cisco_ios"],
    "tags": ["vlan", "l2", "dc1"]
})
template = resp.json()
print(f"Created template: {template['id']}")
print(f"Detected variables: {template['variables']}")
create-template.tstypescript
const BASE_URL = "https://netstacks.example.net/api";
const headers = {
  Authorization: "Bearer eyJhbGciOiJIUzI1NiIs...",
  "Content-Type": "application/json",
};

const templateContent = `interface Vlan{{ vlan_id }}
 description {{ description }}
 ip address {{ ip_address }} {{ subnet_mask }}
 no shutdown`;

const resp = await fetch(`${BASE_URL}/templates`, {
  method: "POST",
  headers,
  body: JSON.stringify({
    name: "VLAN Configuration",
    description: "Standard VLAN interface config for Cisco IOS",
    content: templateContent,
    device_types: ["cisco_ios"],
    tags: ["vlan", "l2", "dc1"],
  }),
});
const template = await resp.json();
console.log(`Created template: ${template.id}`);
console.log(`Detected variables: ${template.variables.join(", ")}`);

Render a Template

render-template.shbash
curl -X POST https://netstacks.example.net/api/templates/b2c3d4e5-f6a7-8901-bcde-f23456789012/render \
  -H "Authorization: Bearer eyJhbGciOiJIUzI1NiIs..." \
  -H "Content-Type: application/json" \
  -d '{
    "variables": {
      "vlan_id": "100",
      "description": "Server-Farm-DC1",
      "ip_address": "10.100.0.1",
      "subnet_mask": "255.255.255.0"
    }
  }'

# Response:
# {
#   "rendered": "interface Vlan100\n description Server-Farm-DC1\n ip address 10.100.0.1 255.255.255.0\n no shutdown"
# }
render-template.pypython
# Render template with variable values
rendered = requests.post(
    f"{base_url}/templates/{template['id']}/render",
    headers=headers,
    json={
        "variables": {
            "vlan_id": "100",
            "description": "Server-Farm-DC1",
            "ip_address": "10.100.0.1",
            "subnet_mask": "255.255.255.0"
        }
    }
)
print("Rendered config:")
print(rendered.json()["rendered"])
render-template.tstypescript
const renderResp = await fetch(
  `${BASE_URL}/templates/${template.id}/render`,
  {
    method: "POST",
    headers,
    body: JSON.stringify({
      variables: {
        vlan_id: "100",
        description: "Server-Farm-DC1",
        ip_address: "10.100.0.1",
        subnet_mask: "255.255.255.0",
      },
    }),
  }
);
const result = await renderResp.json();
console.log("Rendered config:");
console.log(result.rendered);

List Templates

list-templates.shbash
curl "https://netstacks.example.net/api/templates" \
  -H "Authorization: Bearer eyJhbGciOiJIUzI1NiIs..."

# Response:
# {
#   "data": [
#     {
#       "id": "b2c3d4e5-f6a7-8901-bcde-f23456789012",
#       "name": "VLAN Configuration",
#       "description": "Standard VLAN interface config for Cisco IOS",
#       "device_types": ["cisco_ios"],
#       "version": 3
#     },
#     {
#       "id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
#       "name": "BGP Peering",
#       "description": "eBGP neighbor configuration",
#       "device_types": ["cisco_ios", "juniper_junos"],
#       "version": 1
#     }
#   ]
# }
list-templates.pypython
templates = requests.get(f"{base_url}/templates", headers=headers).json()
for t in templates["data"]:
    print(f"{t['name']} (v{t['version']}) - {t['device_types']}")
list-templates.tstypescript
const templatesResp = await fetch(`${BASE_URL}/templates`, {
  headers: { Authorization: "Bearer eyJhbGciOiJIUzI1NiIs..." },
});
const templates = await templatesResp.json();
templates.data.forEach((t: any) =>
  console.log(`${t.name} (v${t.version}) - ${t.device_types.join(", ")}`)
);

Questions & Answers

How do I create a template via the API?
Send a POST to /api/templates with a JSON body containing name and content (Jinja2 template string). Optional fields include description, device_types, and tags.
How do I render a template with variables?
Send a POST to /api/templates/:id/render with a variables object mapping variable names to values. The response contains the fully rendered configuration text.
How does template versioning work?
Each time you update a template's content, a new version is created. The version number increments automatically. You can retrieve previous versions to compare changes over time.
Can I extract variables automatically?
Yes. When you create or update a template, NetStacks parses the Jinja2 content and returns a variables array listing all detected variable names. This helps you know exactly what values to provide at render time.
What Jinja2 features are supported?
NetStacks supports standard Jinja2 syntax including variables ({{ var }}), conditionals ({% if %}), loops ({% for %}), filters ({{ var | upper }}), and template inheritance.
How do I create a BGP peering template?
Create a template with Jinja2 content for your BGP configuration, using variables for the neighbor IP, ASN, and other parameters. For example: router bgp {{ local_asn }}\n neighbor {{ neighbor_ip }} remote-as {{ remote_asn }}.

Troubleshooting

Jinja2 Syntax Error on Create

The template content contains invalid Jinja2 syntax. Common issues include unclosed {{ }} blocks, mismatched {% if %} / {% endif %} tags, and unsupported filters. Validate your template syntax before uploading.

Missing Variable on Render

The render request is missing a required variable. Check the template's variables array to see all required variable names and ensure they are all included in the render request's variables object.

Version Not Found

The requested version number does not exist for this template. Templates start at version 1 and increment with each update. Use the template detail endpoint to check the current version.

422 Validation Error

The request body is missing required fields or contains invalid values. Check that name and content are provided. See Error Codes for field-level details.

  • API Authentication — Obtain tokens for API access.
  • Stacks API — Combine templates into deployment stacks for multi-device rollouts.
  • Devices API — Manage the devices that templates are deployed to.
  • Error Codes — Reference for template rendering errors and validation failures.
  • Template Basics — UI guide for creating and managing templates.