# Variables API

Service variables. 11 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.

## POST /environments/{environmentId}/env-template-preview

Preview the .env.example keys of a repository before creating its service

Like the service scan, for a repository that has no service yet: nothing is created or stored. The suggestions can reference the databases of the environment. Give a git connection and repository, or a repository URL (with `gitToken` for a private one); `serverId` is the server a read that needs an agent (Bitbucket, other hosts, a pasted token) runs on. The pasted token is used for this read only and never stored. A repository that cannot be read answers `status: unavailable` with the reason.

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

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

Request body (application/json, required): EnvTemplatePreviewInput
  - `branch` (string): Defaults to the repository's default branch
  - `buildContext` (string): Build context directory, read as well as the repository root
  - `connectionId` (string): Git connection the repository is read through
  - `gitToken` (string): Access token of a private repository, used for this read only and never stored
  - `gitUrl` (string): Clone URL of the repository (https, public or with gitToken)
  - `repo` (string): owner/name on the connection
  - `serverId` (string): The server a read that needs an agent runs on

Response 200: OK (EnvTemplateScan)
  - `branch` (string)
  - `commit` (string): Commit the files were read from (agent reads only)
  - `files` (EnvTemplateFile[] | null, required)
  - `items` (SuggestedVariable[] | null, required): One suggestion per key not set yet, with smart defaults (database references, generators, safe example values)
  - `message` (string, required)
  - `method` (enum: provider | agent | none, required): provider: read through the git provider's API; agent: read on the server
  - `skipped` (SkippedVariable[] | null, required)
  - `status` (enum: found | none | unavailable, required): found: files with keys exist; none: the repository has no template file; unavailable: it could not be read (see message)
Errors: 401 Unauthorized; 403 Forbidden; 404 Not Found; 422 Unprocessable Entity; 500 Internal Server Error

## GET /environments/{environmentId}/variable-usage

Which variables reference a variable

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

Parameters:
- `environmentId` (path, string, required)
- `ns` (query, string): Namespace: shared (default), team or a service slug
- `key` (query, string, required)

Response 200: OK (UsageOutputBody)
  - `items` (VariableUsage[] | null, required)
Errors: 401 Unauthorized; 403 Forbidden; 404 Not Found; 422 Unprocessable Entity; 500 Internal Server Error

## POST /services/{serviceId}/env-template-scan

Read a git service's .env.example files again

Reads `.env.example`, `.env.sample`, `.env.template` and `.env.dist` (never `.env` or `.env.local`) from the repository root and the build context at the tracked branch: through the git provider's API when the service is linked to a connection or the repository is public on github.com or gitlab.com, otherwise by a scan-only command on the service's server. The files become the suggestion source of `GET /services/{serviceId}/suggested-variables`. Returns the keys not set yet with smart defaults; a repository that cannot be read answers `status: unavailable` with the reason, and nothing changes. Reading re-runs on every push to the tracked branch.

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

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

Response 200: OK (EnvTemplateScan)
  - `branch` (string)
  - `commit` (string): Commit the files were read from (agent reads only)
  - `files` (EnvTemplateFile[] | null, required)
  - `items` (SuggestedVariable[] | null, required): One suggestion per key not set yet, with smart defaults (database references, generators, safe example values)
  - `message` (string, required)
  - `method` (enum: provider | agent | none, required): provider: read through the git provider's API; agent: read on the server
  - `skipped` (SkippedVariable[] | null, required)
  - `status` (enum: found | none | unavailable, required): found: files with keys exist; none: the repository has no template file; unavailable: it could not be read (see message)
Errors: 401 Unauthorized; 403 Forbidden; 404 Not Found; 422 Unprocessable Entity; 500 Internal Server Error

## GET /services/{serviceId}/suggested-variables

Suggested variables

References to databases of the environment that nothing references yet, and the keys of the repository's `.env.example`-style files that are not set yet (read before the first deploy, see `POST /services/{serviceId}/env-template-scan`, and again after every push to the tracked branch and at every build). Accepting a suggestion is a normal staged write; send `source: "suggested"` for an `.env.example` key so a value left empty is reported by the deploy plan.

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

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

Response 200: OK (SuggestedVariableList)
  - `branch` (string): Branch the .env.example files were read from
  - `commit` (string): Commit the .env.example files were read from (empty when they were read through the git provider)
  - `items` (SuggestedVariable[] | null, required)
  - `skipped` (SkippedVariable[] | null, required): Keys of the example files left out on purpose (platform variables such as PORT)
Errors: 401 Unauthorized; 403 Forbidden; 404 Not Found; 422 Unprocessable Entity; 500 Internal Server Error

## GET /services/{serviceId}/variables

List a service's variables

Secret values are never returned: they appear as `{key, isSecret:true, set:true}`. `computedSecret` is also true for a variable that embeds a secret through a reference. `platform` lists the reserved variables NebulaCtrl provides; `exports` the virtual variables other services can reference.

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

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

Response 200: OK (VariableList)
  - `exports` (ExportVar[] | null, required)
  - `items` (Variable[] | null, required)
  - `platform` (PlatformVar[] | null, required)
  - `restaged` (RestagedServiceRef[] | null, required)
  - `warnings` (VariableWarning[] | null, required)
Errors: 401 Unauthorized; 403 Forbidden; 404 Not Found; 422 Unprocessable Entity; 500 Internal Server Error

## PUT /services/{serviceId}/variables

Upsert variables (staged until deployed)

Bulk upsert; variables not listed are untouched. Omitting a value keeps the stored one. Values may reference other services (`${{service-slug.KEY}}`), the same service (`${{KEY}}`) and shared variables (`${{shared.KEY}}`), and may contain generators (`${{secret(32)}}`, `${{randomInt(1,6)}}`, `${{uuid()}}`, `${{randomBytes(32,hex)}}`) that run once: writing the same text again keeps the generated value. `$${{` writes a literal `${{`. Variables whose resolved value changes in other services are restaged (`restaged`). Platform variable names (`PORT`, `NEBULA_*`) are rejected with `reserved_variable`.

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

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

Request body (application/json, required): PutInputBody2
  - `variables` (VariableInput[] | null, required)

Response 200: OK (VariableList)
  - `exports` (ExportVar[] | null, required)
  - `items` (Variable[] | null, required)
  - `platform` (PlatformVar[] | null, required)
  - `restaged` (RestagedServiceRef[] | null, required)
  - `warnings` (VariableWarning[] | null, required)
Errors: 401 Unauthorized; 403 Forbidden; 404 Not Found; 422 Unprocessable Entity; 500 Internal Server Error

## DELETE /services/{serviceId}/variables/{key}

Delete a variable (staged until deployed)

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

Parameters:
- `serviceId` (path, string, required)
- `key` (path, string, required)

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

## GET /services/{serviceId}/variables/{key}/history

Version history of a variable

Newest first. Non-secret versions include their value; secrets carry only a fingerprint (equal fingerprints mean equal values).

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

Parameters:
- `serviceId` (path, string, required)
- `key` (path, string, required)

Response 200: OK (HistoryOutputBody)
  - `items` (VariableHistoryEntry[] | null, required)
Errors: 401 Unauthorized; 403 Forbidden; 404 Not Found; 422 Unprocessable Entity; 500 Internal Server Error

## POST /services/{serviceId}/variables/{key}/history/{version}/restore

Restore an old non-secret version as a new version

Secrets cannot be restored (`restore_unsupported`); use a release rollback or re-enter the value.

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

Parameters:
- `serviceId` (path, string, required)
- `key` (path, string, required)
- `version` (path, integer (int64), required)

Response 200: OK (VariableList)
  - `exports` (ExportVar[] | null, required)
  - `items` (Variable[] | null, required)
  - `platform` (PlatformVar[] | null, required)
  - `restaged` (RestagedServiceRef[] | null, required)
  - `warnings` (VariableWarning[] | null, required)
Errors: 401 Unauthorized; 403 Forbidden; 404 Not Found; 422 Unprocessable Entity; 500 Internal Server Error

## POST /services/{serviceId}/variables/{key}/rotate

Rotate a generated variable (plan or stage)

Re-runs the generators of a generated variable. `dryRun:true` returns the plan (affected services, deploy order, warnings, blockers) and changes nothing; `dryRun:false` stages the new value and restages the services that consume it. Nothing is deployed. Database credentials and volume-backed passwords are blocked with `rotation_unsupported` once deployed.

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

Parameters:
- `serviceId` (path, string, required)
- `key` (path, string, required)

Request body (application/json, required): VariableRotateBody
  - `dryRun` (boolean, required): true returns the plan and changes nothing; false stages the new value (nothing is deployed)

Response 200: OK (VariableRotationPlan)
  - `affected` (RotationAffected[] | null, required)
  - `blockers` (RotationBlocker[] | null, required)
  - `deployOrder` (string[] | null, required): Service slugs, providers first
  - `key` (string, required)
  - `kind` (enum: app-secret | engine-bound, required)
  - `staged` (boolean, required)
  - `warnings` (string[] | null, required)
Errors: 401 Unauthorized; 403 Forbidden; 404 Not Found; 422 Unprocessable Entity; 500 Internal Server Error

## POST /services/{serviceId}/variables/validate

Validate a draft value

Parses the draft, checks generator arguments and dry-run resolves its references in the service's environment. Nothing is written and no generated value is returned.

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

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

Request body (application/json, required): ValidateVariableInput
  - `isSecret` (boolean)
  - `key` (string): Name of the variable being edited (defaults to a placeholder)
  - `value` (string, required)

Response 200: OK (ValidateVariableResult)
  - `code` (string)
  - `containsSecret` (boolean, required): The value is or becomes secret (declared, a secret generator, or a secret reference)
  - `error` (SyntaxProblem)
  - `generators` (GeneratorCall[] | null, required)
  - `message` (string)
  - `ok` (boolean, required)
  - `preview` (string): The resolved value for non-secret drafts
  - `references` (RefCheck[] | null, required)
  - `warnings` (VariableWarning[] | null, required)
Errors: 401 Unauthorized; 403 Forbidden; 404 Not Found; 422 Unprocessable Entity; 500 Internal Server Error
