NetStacksNetStacks

Template Versioning

How NetStacks snapshots document and template history on every save, and how to list, view, and restore previous versions via the /api/docs version routes.

Overview

NetStacks keeps a history of your documents — including templates, outputs, backups, and notes. Every time you update a document, NetStacks snapshots the previous content into a version record before writing the new content. Those snapshots are never overwritten, so you always have a trail you can inspect and restore from.

This gives you three capabilities:

  • History — list every saved snapshot of a document, newest first, each with its own timestamp.
  • Inspection — fetch the full content of any single version to see exactly what the template looked like at that point.
  • Restore — copy a previous version's content back into the live document, while snapshotting the current content first so nothing is lost.

For network configuration templates, where a small change can cause an outage, this lets you trace a problem back to the exact saved revision and revert to a known-good version.

Available in the free Terminal

Document and version history ship in the free NetStacks Terminal. The endpoints below are local to your running NetStacks instance under /api/docs. NetStacks Terminal is single-user, so versions are not attributed to individual accounts — each snapshot carries only a timestamp.

How It Works

Documents live in the documents table; their prior revisions live in a separate document_versions table. A document belongs to a category — such as outputs, templates, notes, backups, or history — and a Jinja template is simply a document in the templates category with a content_type of jinja.

The Version Record

Each version is a snapshot of the document's content at the moment it was replaced. There is no integer version number and no per-version author — versions are ordered by timestamp. A version record has exactly these fields:

  • id — the unique ID of this version snapshot (a UUID).
  • document_id — the ID of the document this snapshot belongs to.
  • content — the full document content captured in this snapshot. (Returned only when you fetch a single version; the list endpoint omits it.)
  • created_at — the timestamp when the snapshot was taken.

The live document itself carries the usual fields — id, name, category, content_type, content, parent_folder, session_id, created_at, and updated_at. The document does not store a version counter; the history is derived entirely from the snapshot rows.

Secure Notes are encrypted at rest

Documents in the notes category are encrypted with your vault. Their version snapshots are stored encrypted too, so listing versions works while locked, but fetching or restoring an encrypted version requires the vault to be unlocked. Templates are not encrypted, so this does not apply to them.

What Happens on Save

When you update a document with PUT /api/docs/:id, NetStacks, in order:

  1. Reads the existing document row.
  2. Inserts the current (about to be replaced) content into document_versions as a new snapshot with the current timestamp.
  3. Writes the new content onto the document and updates its updated_at timestamp.

So the snapshot always captures the content as it existed before the edit. The newest content is the live document; the most recent snapshot is the revision immediately preceding it. Snapshots are never deleted by an update.

How Restore Works

Restoring is non-destructive. When you call POST /api/docs/:id/restore/:version_id, NetStacks:

  1. Snapshots the document's current content into document_versions (just like a normal save), so you can always undo the restore.
  2. Copies the selected version's content back onto the live document and bumps updated_at.

Because there are no version numbers, restoring simply produces a new live content state plus one more timestamped snapshot of whatever you replaced. No history is ever removed.

Restore does not redeploy

Restoring a template only changes the stored template content. It does not re-render or re-push configuration anywhere. After restoring, re-render the template and run your normal deployment workflow to apply the change to devices.

Version API Endpoints

All version routes are nested under /api/docs. The list endpoint returns metadata only (no content); fetch a single version to get its full content.

Method & PathPurpose
GET /api/docs/:id/versionsList all snapshots for a document (newest first, metadata only).
GET /api/docs/versions/:version_idFetch one version snapshot including its full content.
POST /api/docs/:id/restore/:version_idRestore a version into the live document (snapshots current first).
PUT /api/docs/:idUpdate a document; snapshots the prior content automatically.
POST /api/docs/:id/renderRender a Jinja template with variables (verify output after restore).

The version list is returned in created_at descending order, so the first entry is the most recent snapshot.

Code Examples

List version history (metadata only)

list-versions.shbash
curl http://localhost:8080/api/docs/7f3a2b1c-9d2e-4a51-b0c8-1e2f3a4b5c6d/versions

The list response contains metadata only — no content field:

versions-list.jsonjson
[
  {
    "id": "c1d2e3f4-5a6b-7c8d-9e0f-1a2b3c4d5e6f",
    "document_id": "7f3a2b1c-9d2e-4a51-b0c8-1e2f3a4b5c6d",
    "created_at": "2026-06-12T14:15:00Z"
  },
  {
    "id": "b0c9d8e7-6f5a-4b3c-2d1e-0f9a8b7c6d5e",
    "document_id": "7f3a2b1c-9d2e-4a51-b0c8-1e2f3a4b5c6d",
    "created_at": "2026-06-05T11:00:00Z"
  },
  {
    "id": "a9b8c7d6-5e4f-3a2b-1c0d-9e8f7a6b5c4d",
    "document_id": "7f3a2b1c-9d2e-4a51-b0c8-1e2f3a4b5c6d",
    "created_at": "2026-05-28T09:30:00Z"
  }
]

Fetch a single version (full content)

get-version.shbash
curl http://localhost:8080/api/docs/versions/b0c9d8e7-6f5a-4b3c-2d1e-0f9a8b7c6d5e
version.jsonjson
{
  "id": "b0c9d8e7-6f5a-4b3c-2d1e-0f9a8b7c6d5e",
  "document_id": "7f3a2b1c-9d2e-4a51-b0c8-1e2f3a4b5c6d",
  "content": "interface {{ interface_name }}\n description {{ description }}\n ip address {{ ip_address }} {{ subnet_mask }}\n no shutdown",
  "created_at": "2026-06-05T11:00:00Z"
}

Update a template (snapshots the prior content)

A normal update with PUT /api/docs/:id automatically snapshots the content that was there before, then writes the new content. You only need to send the fields you are changing.

update-template.shbash
curl -X PUT http://localhost:8080/api/docs/7f3a2b1c-9d2e-4a51-b0c8-1e2f3a4b5c6d \
  -H "Content-Type: application/json" \
  -d '{
    "content": "interface {{ interface_name }}\n description {{ description }}\n ip address {{ ip_address }} {{ subnet_mask }}\n ip ospf {{ ospf_process_id }} area {{ ospf_area }}\n no shutdown"
  }'

The response is the updated document. The content that existed before this call now appears as the newest entry when you list versions again.

Restore a previous version

Pick a version_id from the list, then restore it. The current content is snapshotted first, so the restore itself is reversible.

restore-version.shbash
curl -X POST \
  http://localhost:8080/api/docs/7f3a2b1c-9d2e-4a51-b0c8-1e2f3a4b5c6d/restore/b0c9d8e7-6f5a-4b3c-2d1e-0f9a8b7c6d5e

The response is the updated document, now carrying the restored content. Listing versions afterward shows one additional snapshot — the content that was live just before the restore.

Verify a restored template renders correctly

After restoring, render the template with test variables to confirm the output is what you expect before you deploy it.

render-restored.shbash
curl -X POST \
  http://localhost:8080/api/docs/7f3a2b1c-9d2e-4a51-b0c8-1e2f3a4b5c6d/render \
  -H "Content-Type: application/json" \
  -d '{
    "variables": {
      "interface_name": "GigabitEthernet0/1",
      "description": "Uplink to core",
      "ip_address": "10.0.0.1",
      "subnet_mask": "255.255.255.252"
    }
  }'
render-response.jsonjson
{
  "output": "interface GigabitEthernet0/1\n description Uplink to core\n ip address 10.0.0.1 255.255.255.252\n no shutdown",
  "success": true,
  "error": null
}

Questions & Answers

Q: Are template versions created automatically?
A: Yes. Every PUT /api/docs/:id update (and every restore) snapshots the content that was live just before the write into document_versions. You never have to create a version manually.
Q: Do versions have version numbers like v1, v2, v3?
A: No. Versions are identified by a UUID and ordered by their created_at timestamp (newest first). There is no sequential version counter on the document.
Q: How do I see the content of an old version?
A: List versions with GET /api/docs/:id/versions to get the snapshot IDs, then fetch one with GET /api/docs/versions/:version_id. The list endpoint returns metadata only; the single-version endpoint includes the full content.
Q: How do I roll back to a previous version?
A: Call POST /api/docs/:id/restore/:version_id. NetStacks snapshots the current content first, then copies the selected version's content back onto the live document. Nothing is deleted, and the restore can itself be undone by restoring the snapshot it created.
Q: Can I see who created each version?
A: No. NetStacks Terminal is single-user, so version snapshots are not attributed to a user. Each snapshot carries only an id, document_id, and created_at timestamp.
Q: Can I delete old versions?
A: There is no endpoint to delete an individual version — snapshots accumulate as an audit trail. Deleting the document itself removes its versions along with it.
Q: Does restoring a template redeploy it?
A: No. Restore only changes the stored template content. Re-render the template (POST /api/docs/:id/render) and run your deployment workflow to push the change to devices.

Troubleshooting

The version list shows no content

That is expected. GET /api/docs/:id/versions returns metadata only (id, document_id, created_at) to keep the payload small. Fetch a specific version with GET /api/docs/versions/:version_id to get its content.

"Vault is locked" when viewing or restoring a version

This only affects encrypted documents in the notes category. Their snapshots are stored encrypted, so you must unlock the vault before fetching or restoring them. Templates are not encrypted and are unaffected.

Restore didn't change my deployed devices

Restore changes the stored template content only. After restoring, re-render the template and trigger your deployment workflow to apply the configuration to devices.

Concurrent edits

NetStacks does not detect or merge conflicting edits. If two updates land on the same document, each one snapshots the content it replaced, so no data is lost — but the last write wins as the live content. Use the version list to recover any content that was replaced.

Restores are reversible

Because a restore snapshots the current content before overwriting it, you can always undo a restore by restoring the snapshot it just created. Check the version list immediately after a restore to find its ID.