# Templates API

8 operations under /api/v1. Errors are RFC 9457 problem+json with a stable `code` field. The complete schemas are in https://nebulactrl.com/api/v1/openapi.json.

## GET /catalog/templates

List the one-click template catalog

Built-in, reviewed templates. `q` matches every word in the name, summary, tags, licence and database engines (best match first); `category` and `license` filter. Source-available templates carry `licenseClass: source-available`.

Authentication: session cookie or `Authorization: Bearer nct_…` token.

Parameters:
- `q` (query, string): Search terms (name, summary, tags)
- `category` (query, enum: homelab | media | storage | games | devtools | ai | data | web | analytics | automation | productivity | security): Restrict to a category
- `license` (query, string): Restrict to a licence class: permissive, copyleft, source-available or proprietary

Response 200: OK (TemplateCatalog)
  - `categories` (TemplateCategory[] | null, required)
  - `items` (TemplateSummary[] | null, required)
Errors: 401 Unauthorized; 403 Forbidden; 422 Unprocessable Entity; 500 Internal Server Error

## GET /catalog/templates/{templateId}

Get a template

Authentication: session cookie or `Authorization: Bearer nct_…` token.

Parameters:
- `templateId` (path, string, required)

Response 200: OK (CatalogTemplate)
  - `category` (string, required)
  - `description` (string)
  - `group` (string)
  - `heavy` (boolean)
  - `icon` (string)
  - `id` (string, required)
  - `inputs` (TemplateInput[] | null, required)
  - `links` (TemplateLinks, required)
  - `name` (string, required)
  - `notes` (string[] | null, required)
  - `origin` (TemplateOrigin)
  - `requires` (TemplateRequires, required)
  - `requiresUnrestricted` (boolean)
  - `revision` (integer (int64), required)
  - `schema` (integer (int64), required)
  - `services` (TemplateService[] | null, required)
  - `summary` (string, required)
  - `tags` (string[] | null, required)
  - `upstream` (TemplateUpstream, required)
Errors: 401 Unauthorized; 403 Forbidden; 404 Not Found; 422 Unprocessable Entity; 500 Internal Server Error

## GET /environments/{environmentId}/template-instances

List the template stacks installed in an environment

Authentication: session cookie or `Authorization: Bearer nct_…` token.

Parameters:
- `environmentId` (path, string, required)

Response 200: OK (TemplateInstance[] | null)
  - `createdAt` (string (date-time), required)
  - `currentRevision` (integer (int64), required)
  - `currentVersion` (string, required)
  - `environmentId` (string, required)
  - `groupId` (string, required)
  - `id` (string, required)
  - `inputs` (map of string, required)
  - `name` (string, required)
  - `services` (TemplateInstanceService[] | null, required)
  - `templateId` (string, required)
  - `templateRevision` (integer (int64), required)
  - `templateVersion` (string, required)
  - `updateAvailable` (boolean, required)
  - `updateBlocked` (string)
  - `updatedAt` (string (date-time))
Errors: 401 Unauthorized; 403 Forbidden; 404 Not Found; 422 Unprocessable Entity; 500 Internal Server Error

## POST /environments/{environmentId}/templates/{templateId}/instances

Install a template

Creates the template's services, volumes, files, variables (secrets generated once), domain, published ports, links and a canvas group as staged changes. With `deploy: true` the new services are deployed in dependency order (needs `deployments:write`); templates that publish TCP/UDP ports also need `proxy:write`.

Authentication: session cookie or `Authorization: Bearer nct_…` token.

Parameters:
- `Idempotency-Key` (header, string): Client-chosen key (<=255 chars). A retry with the same key replays the original response for 24 hours.
- `environmentId` (path, string, required)
- `templateId` (path, string, required)

Request body (application/json, required): TemplateInstallRequest
  - `deploy` (boolean)
  - `inputs` (map of string)
  - `name` (string)
  - `serverId` (string, required)

Response 201: Created (TemplateInstallResult)
  - `deployments` (DeploymentView[] | null, required)
  - `instance` (TemplateInstance, required)
  - `services` (ServiceView[] | null, required)
  - `warnings` (string[] | null, required)
Errors: 401 Unauthorized; 403 Forbidden; 404 Not Found; 409 Conflict; 422 Unprocessable Entity; 500 Internal Server Error

## POST /environments/{environmentId}/templates/{templateId}/plan

Preflight a template install

Returns the services that would be created, the secrets that would be generated (names only) and checks against the chosen server (architecture, memory, proxy, domain, published ports). Nothing is written.

Authentication: session cookie or `Authorization: Bearer nct_…` token.

Parameters:
- `environmentId` (path, string, required)
- `templateId` (path, string, required)

Request body (application/json, required): TemplatePlanRequest
  - `inputs` (map of string)
  - `serverId` (string)

Response 200: OK (TemplatePlan)
  - `checks` (TemplatePlanCheck[] | null, required)
  - `inputs` (TemplatePlanInput[] | null, required)
  - `installable` (boolean, required)
  - `proxyRestart` (boolean, required)
  - `secrets` (string[] | null, required)
  - `services` (TemplatePlanService[] | null, required)
  - `template` (TemplateSummary, required)
  - `warnings` (string[] | null, required)
Errors: 401 Unauthorized; 403 Forbidden; 404 Not Found; 422 Unprocessable Entity; 500 Internal Server Error

## DELETE /template-instances/{instanceId}

Delete a template stack

Removes every service of the stack. Requires `confirm=<template id>`. Volumes are kept unless `deleteVolumes=true`.

Authentication: session cookie or `Authorization: Bearer nct_…` token.

Parameters:
- `instanceId` (path, string, required)
- `confirm` (query, string): Must equal the template id
- `deleteVolumes` (query, boolean): Also remove the stack's Docker volumes (data is lost)

Response 204: No Content
Errors: 401 Unauthorized; 403 Forbidden; 404 Not Found; 409 Conflict; 422 Unprocessable Entity; 500 Internal Server Error

## GET /template-instances/{instanceId}/update

Diff an installed stack against the template's current revision

Lists what changed between the template as installed and the catalog's current revision: `apply` changes the update would stage, `kept` ones that differ because you changed the service, `manual` ones (new or removed services, database upgrades, published ports) that an update never does. Nothing is written. `blocked` is set for stacks installed before updates existed.

Authentication: session cookie or `Authorization: Bearer nct_…` token.

Parameters:
- `instanceId` (path, string, required)

Response 200: OK (TemplateUpdate)
  - `applied` (integer (int64), required)
  - `apply` (integer (int64), required)
  - `available` (boolean, required)
  - `blocked` (string)
  - `changes` (TemplateUpdateChange[] | null, required)
  - `currentRevision` (integer (int64), required)
  - `currentVersion` (string, required)
  - `inputs` (TemplatePlanInput[] | null, required)
  - `installedRevision` (integer (int64), required)
  - `installedVersion` (string, required)
  - `instanceId` (string, required)
  - `kept` (integer (int64), required)
  - `manual` (integer (int64), required)
  - `name` (string, required)
  - `notes` (string[] | null, required)
  - `templateId` (string, required)
Errors: 401 Unauthorized; 403 Forbidden; 404 Not Found; 422 Unprocessable Entity; 500 Internal Server Error

## POST /template-instances/{instanceId}/update

Stage a template update

Stages the `apply` changes of the diff as ordinary edits of the stack's services (image, configuration, variables, files, volumes); nothing is deployed, the staged bar and Deploy do that. Fields you changed are kept, secrets are never overwritten, and databases, routes and services are never touched. `revision` must be the revision you reviewed (409 `template_changed` otherwise). A retry after a failure is safe.

Authentication: session cookie or `Authorization: Bearer nct_…` token.

Parameters:
- `Idempotency-Key` (header, string): Client-chosen key (<=255 chars). A retry with the same key replays the original response for 24 hours.
- `instanceId` (path, string, required)

Request body (application/json, required): TemplateUpdateRequest
  - `inputs` (map of string)
  - `revision` (integer (int64), required)

Response 200: OK (TemplateUpdateResult)
  - `changes` (TemplateUpdateChange[] | null, required)
  - `instance` (TemplateInstance, required)
  - `services` (string[] | null, required)
  - `staged` (integer (int64), required)
  - `warnings` (string[] | null, required)
Errors: 401 Unauthorized; 403 Forbidden; 404 Not Found; 409 Conflict (`template_changed`); 422 Unprocessable Entity; 500 Internal Server Error
