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.
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
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"
# }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']}")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
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 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"])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
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
# }
# ]
# }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']}")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
POSTto/api/templateswith a JSON body containingnameandcontent(Jinja2 template string). Optional fields includedescription,device_types, andtags. - How do I render a template with variables?
- Send a
POSTto/api/templates/:id/renderwith avariablesobject 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
variablesarray 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.
Related Features
- 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.