Skip to main content

Deploy: Deployments API

Enterprise

Everything the Deploy page does is also served by the public REST API, so a template can be deployed from a CI pipeline or a script. Requests authenticate with an API token (Authorization: Bearer <token>) and need the tenant's REST API access; see REST API Access.

A deployment run through the API is recorded exactly as one run from the console, and shows in the Deploy list with the source API and the token as the one who started it.

All paths are under /api/v1:

Method and pathDoes
POST /deployments/validateParse and check a template, write nothing.
POST /deployments/what-ifWhat a deployment would change, write nothing.
POST /deploymentsRun a deployment (?wait=true to wait for it).
GET /deploymentsList, newest first.
GET /deployments/{id}One deployment with its operations.
POST /deployments/{id}/cancelStop a running deployment before its next operation.
POST /deployments/exportWrite existing resources as a template.
POST /deployments/templateWrite resources not saved yet (a create wizard's draft) as a template.
GET /deployments/resource-typesThe resource types a template can name.

The Swagger reference at /api/v1/swagger documents them under the Deployment tag.

The template language is described in Templates.


Permissions​

RouteNeeds
validate, what-if, list, get, export, template, resource-typesDEPLOYMENT_READ
deploy, cancelDEPLOYMENT_CREATE

For an API token, the token's role decides.

DEPLOYMENT_CREATE does not stand in for a resource's own permissions. Before anything is written, every resource of the template is checked: create and update of its type (which one runs is only known at lookup), read for an existing resource, plus each type's edition features. A missing permission refuses the whole deployment with 403 DEPLOYMENT_PERMISSION_DENIED, listing what is missing. Export checks each type's read permission the same way, and so does a what-if, which answers the missing ones as VM403 diagnostics.

A type may ask for less when the template asks for less: a pipeline or child with review: true does not need PIPELINE_COMMIT_MERGE, and a staging sync needs PIPELINE_DELETE only with endStaging: true. The narrower need applies when the resource's properties are written out in the template; a property read from another resource leaves the type's full list in force.

Reading a deployment is per type. DEPLOYMENT_READ opens the organization's deployments; each resource type's own read permission opens its details. See Restricted Details.


Validate​

POST /deployments/validate takes {template, parameters?}. Parameters are optional; without them, a missing required parameter is not an error.

{
"valid": false,
"diagnostics": [ { "code": "BCP018", "message": "...", "severity": "error",
"range": { "startLine": 3, "startColumn": 5, "endLine": 3, "endColumn": 9 } } ],
"parameters": [ { "name": "deviceName", "type": "string", "defaultValue": "syslog-udp", "hasDefault": true,
"allowedValues": null, "description": "", "secure": false, "minValue": null,
"maxValue": null, "minLength": null, "maxLength": null, "metadata": null } ],
"resources": [ { "symbolicName": "syslog", "type": "VirtualMetric/devices", "apiVersion": "2026-09-27",
"existing": false, "conditional": false, "loop": false, "kind": "resource" } ],
"outputs": [ { "name": "deviceId", "type": "string", "description": "" } ]
}

Lists are [], never null.

curl -sS -X POST "$VM_URL/api/v1/deployments/validate" \
-H "Authorization: Bearer $VM_TOKEN" -H "Content-Type: application/json" \
--data-binary @- <<'JSON'
{ "template": "param deviceName string = 'syslog-udp'\nresource syslog 'VirtualMetric/devices@2026-09-27' = {\n name: deviceName\n properties: { type: 'syslog', properties: { protocol: 'udp', port: 514 } }\n}\n" }
JSON

What-If​

POST /deployments/what-if takes {template, parameters} and answers {diagnostics, changes}. Each change names the resource and what would happen to it (create, modify, noChange, read for an existing resource, action, ignore for a false condition), with a property delta:

{ "symbolicName": "syslog", "type": "VirtualMetric/devices", "apiVersion": "2026-09-27",
"name": "syslog-udp", "changeType": "modify", "resourceId": "7318...",
"delta": [ { "path": "properties.port", "before": 514, "after": 1514 } ] }

A preview needs the read permission of every type it names: without one it answers only VM403 diagnostics, one per missing permission. A lookup that fails shows on its change as unknown, with the error. A change a resource cannot take (a director's mode, a device's type) stays modify, with a VM043 error diagnostic at the property: the deployment would refuse it.


Deploy​

POST /deployments takes {name?, template, parameters}.

  • name defaults to deploy-YYYYMMDD-HHMMSS (UTC). It is at most 128 characters, without control characters, invisible formatting characters (bidi overrides, zero-width characters) or line separators.
  • The template is at most 1 MiB. The template and the parameters are stored with the deployment, so a NUL character or bytes that are not UTF-8 in either are refused (400 DEPLOYMENT_TEMPLATE_INVALID).

The answer comes at once: 202 with the deployment running and every planned operation pending, so the whole list can be drawn from the first poll. The run continues in the background.

Before the first write, the run looks up every resource it can already name. A lookup that fails fails the deployment with nothing written, that operation failed and every other skipped, and so does a change a resource it found cannot take (the what-if's VM043), with DEPLOYMENT_TEMPLATE_INVALID. A run lasts at most 30 minutes: past that, the rest is skipped and the deployment fails with DEPLOYMENT_TIMED_OUT.

?wait=true waits for the deployment to end, up to 90 seconds, and answers 200 with the final state, or 202 with the state so far if it is still running (keep polling).

# Deploy and wait
curl -sS -X POST "$VM_URL/api/v1/deployments?wait=true" \
-H "Authorization: Bearer $VM_TOKEN" -H "Content-Type: application/json" \
--data-binary @deploy.json -w '\nHTTP %{http_code}\n'

# deploy.json
# { "name": "ci-syslog", "template": "<bicep text>", "parameters": { "deviceName": "syslog-tcp" } }

One deployment runs at a time per organization: a second answers 409 DEPLOYMENT_IN_PROGRESS.

Poll​

ID=7318146275443761152
while :; do
STATUS=$(curl -sS "$VM_URL/api/v1/deployments/$ID" -H "Authorization: Bearer $VM_TOKEN" | jq -r .status)
echo "$STATUS"
[ "$STATUS" != "running" ] && break
sleep 5
done

The Deployment​

Deploy, get and cancel answer the deployment itself, not wrapped:

{
"id": "7318146275443761152", // ids are strings
"name": "deploy-20260927-142233",
"status": "running", // running | succeeded | failed | canceled | interrupted
"source": "api", // ui | api
"createdBy": "API Token: ci", // or the user's email
"startedAt": 1790000000, // epoch seconds
"completedAt": null,
"resourceCount": 2,
"error": null, // { "code", "message" } when it did not succeed
"restricted": false, // always present
"missingPermissions": [], // always present; [] when none
"operations": [
{ "seq": 1, "symbolicName": "syslog", "type": "VirtualMetric/devices", "apiVersion": "2026-09-27",
"name": "syslog-udp", "resourceId": "7318...", "operation": "update", // create | update | noChange | read | action | skip
"status": "succeeded", // pending | running | succeeded | failed | skipped
"error": null, "delta": [ { "path": "properties.port", "before": 514, "after": 1514 } ],
"warnings": [], "startedAt": 1790000001, "completedAt": 1790000002,
"restricted": false } // always present
],
"outputs": { "deviceId": "7318..." }, // the template's outputs, secure values "***"
"template": "...", // as deployed
"parameters": { "deviceName": "syslog-udp", "adminPassword": "***" }
}

operations are in seq order. A pending resource that is neither existing nor an action has an empty operation until its lookup decides between create and update. A secure value inside a plain parameter's value is *** there too.

Restricted Details​

An operation of a resource type whose read permission the caller lacks is restricted:

  • restricted: true, name: "", resourceId: "", delta: null, warnings: null, and no outputs;
  • its error, when it has one, keeps its code, with the message Details are hidden: reading <type> needs <KEY>.

When any operation is restricted, so is the deployment: restricted: true, missingPermissions lists the read permissions the caller lacks for its types (sorted), template is "", parameters and outputs are null, and its error, when it has one, keeps its code with the same kind of message. A caller who can read every type sees restricted: false and missingPermissions: [].

An operation's error never quotes a storage error: a database or network failure beneath a resource reads <operation> failed: a storage error occurred.

One-Time Outputs​

Some writes return a value once and never again: a new director's API key and install script. An operation carries them in outputs:

  • only for the caller who started the deployment (the same user, or the same API token);
  • until 15 minutes after the deployment ends;
  • never stored in the database, logged or audited.

They appear as each operation that returns one completes, so a poll during the run already sees them. Read them from the ?wait=true answer, or poll: after 15 minutes they are gone for good. A director's API key can be regenerated from its page; nothing else can bring an expired output back.


Status Model​

StatusMeaning
runningOperations are running.
succeededEvery operation succeeded (or changed nothing).
failedAn operation failed, or the run passed its 30 minutes (DEPLOYMENT_TIMED_OUT). The operations before were applied; the rest are skipped. error says why.
canceledA cancel stopped it before an operation. The operations before were applied; the rest are skipped.
interruptedThe server running it stopped (a restart, a lost database) and it stopped reporting progress for two minutes. The operation it was running is marked failed (it may or may not have been applied) and the rest skipped. Check the resources, then deploy again.

A deployment stops at its first failure. Resources are never deleted. After a run that may have written anything, the tenant's director and cluster configurations are redeployed once, whatever the outcome.


Cancel​

POST /deployments/{id}/cancel asks a running deployment to stop before its next operation; the operation already running completes. A finished deployment is answered as it is.

List​

GET /deployments?search=&status=&pageNumber=&itemCount= answers {deployments, total}, newest first.

  • search matches the name and who started it.
  • status takes one status, or several separated by commas.
  • itemCount defaults to 20, at most 100.

Rows are the summary: id, name, status, source, createdBy, startedAt, completedAt, resourceCount, error, restricted. A row is restricted when the deployment touched a type whose read permission the caller lacks. An organization keeps its newest 500 deployments.

Export and Draft Templates​

POST /deployments/export takes {resources: [{type, id}]} (at most 100) and answers {template, warnings}. References to other resources become existing resources named by parameters defaulted to the current names, and withheld secrets become @secure() parameters whose empty default keeps the stored value. An unknown type answers 400 DEPLOYMENT_RESOURCE_TYPE_UNKNOWN listing the known ones.

POST /deployments/template takes {resources: [{type, apiVersion?, name, properties, parentType?, parentId?}]} and answers {template, warnings}: a create wizard's body, not saved yet, written as a template.

Resource Types​

GET /deployments/resource-types answers {resourceTypes: [{type, apiVersions, kind, parentType, description, properties: [{name, type, required, description, allowed?}]}]}.


Errors​

Errors answer {code, message, traceId}, with the detail beside it (diagnostics, missingPermissions, missingFeatures, omitted when empty).

CodeStatusWhen
INVALID_REQUEST400Malformed body, empty or oversized template, bad name, bad id or status filter.
DEPLOYMENT_TEMPLATE_INVALID400The template does not parse or check, or its parameters do not bind. The message names the first error and where it is; diagnostics carries all of them with ranges. Also a template or parameter holding a NUL or bytes that are not UTF-8.
DEPLOYMENT_RESOURCE_TYPE_UNKNOWN400An export or a draft named a type the engine does not know.
DEPLOYMENT_PERMISSION_DENIED403The template's (or the export's) resources need permissions the caller does not hold or features the organization lacks. Nothing was written.
PERMISSION_DENIED403The caller lacks the route's own DEPLOYMENT_* permission.
DEPLOYMENT_NOT_FOUND404No such deployment in the organization.
DEPLOYMENT_IN_PROGRESS409Another deployment is running for the organization.
UNKNOWN_ERROR500A permission check could not be completed, or the server failed otherwise. The message is generic; the detail is logged with the trace id.
// 400
{ "code": "DEPLOYMENT_TEMPLATE_INVALID",
"message": "The template is invalid: Expected the \"=\" character (line 3, column 5).",
"traceId": "...",
"diagnostics": [ { "code": "BCP018", "message": "...", "severity": "error", "range": { ... } } ] }

// 403
{ "code": "DEPLOYMENT_PERMISSION_DENIED",
"message": "The template needs permissions you do not hold: DEVICE_CREATE.",
"traceId": "...", "diagnostics": [ ... ], "missingPermissions": [ "DEVICE_CREATE" ] }

Audit​

Every deployment is audited under Organization > Audit, with the object type Deployment; the object is the deployment's name. See Audit Logs.

ActionWritten
DeploymentStartedWhen the deployment is requested, before the run starts.
DeploymentSucceeded, DeploymentFailed, DeploymentCanceledWhen the run ends, attributed to whoever started it.
DeploymentInterruptedWhen a deployment that stopped reporting progress is marked interrupted.
DeploymentCancelRequestedWhen a cancel is requested, naming who asked.
TemplateExportedWhen resources are exported.
PermissionDeniedWhen a deployment, an export or a what-if is refused for missing permissions, one row per missing permission.

No row carries the template's text (its SHA-256 is recorded instead), a secure parameter's value or a one-time output. The resources a deployment writes keep their own rows (DeviceCreated, TargetUpdated and so on), tagged with the deployment. Validate, draft templates, the resource-type list and a what-if that is not refused write nothing.