Create a deployment
Makes a release live on its targets by recording a deployment, atomically: on success every target serves the release and the record exists; on failure nothing changes and no record is written.
Name the release by exactly one of release_id or content_hash; it
must already exist in the project. targets is primary or preview
URLs. The deployment freezes the project's variable values for its kind,
with no fallback between primary and preview values.
A deployment to primary changes nothing when the project's newest
primary deployment has the same release and frozen values and covers
exactly the current primary origins: the call answers 200 with that
deployment. Any other request writes a new deployment and answers 201;
for preview URLs that record renews their previews.
Failures:
400 dep.invalid: both or neither ofrelease_idandcontent_hash; a primary origin named exactly;primarymixed with preview URLs;primaryon a project with no primary origin;ttlwithprimary;ttlabove 365 days (P365D).400 dep.variables_missing: a referenced variable has no value for the kind;detailsnames the kind and every missing variable.403 proj.origin_not_allowed: a preview URL matches no preview pattern, or is a loopback URL.404 rel.not_found: the release id or digest names no release here.409 dep.conflict:expected_deployment_idis not the deployment this write replaces;details.deploymentis that deployment.409 rel.revoked: the release is revoked.
AuthorizationBearer <token>Scope: deployment.write
project_id*stringThe unique identifier of the project
^[a-zA-Z0-9_-]+$application/json- body
The release to make live, named by exactly one of release_id or
content_hash, and the targets to make it live on.
release_id?release-idThe release to make live.
^rel_[a-zA-Z0-9_-]+$content_hash?ReleaseContentHashThe content digest of the release to make live, in full with its
sha256: prefix.
^sha256:[0-9a-f]{64}$71 <= length <= 71targets*array<>Where the release goes live: either primary, which resolves to every
current primary origin, or preview URLs in Origin form, each matching one
of the project's preview patterns. Primary origins cannot be named one by
one, and the two forms cannot be mixed.
1 <= items <= 50ttl?PreviewTTLPreview URLs only. How long each preview stays live from this
deployment; omitted, 7 days (P7D).
durationreason?DeploymentReasonWhy this deployment happens. Omitted, the server records deploy.
"deploy""rollback"message?stringA short free-form summary of why this deployment happened, recorded on its metadata.
length <= 1024expected_deployment_id?deployment-idDeploy only if the deployment this write replaces — the project's
newest primary deployment, or the newest deployment to any of the
preview URLs — is this one. Otherwise the request fails with
409 dep.conflict and nothing changes. Not stored.
^dep_[a-zA-Z0-9_-]+$Nothing changed. The body is the newest primary deployment, with its
own reason, message and deployed_at.
application/json- response
An immutable record of a release made live on a set of targets. Deploying and rolling back each append one, so the history is the audit trail.
All primary origins serve the project's newest primary deployment; one
added after it serves nothing until the next deployment to primary. A
preview URL serves the newest deployment to it while its preview is live.
Every field is fixed at creation except status, which is read as of now.
id*deployment-idThe opaque, immutable resource id, assigned at creation.
^dep_[a-zA-Z0-9_-]+$project_id*project-idThe project this deployment belongs to.
^[a-zA-Z0-9_-]+$release_id*release-idThe release this deployment made live. Always the release id, also
when the request named the release by content_hash.
^rel_[a-zA-Z0-9_-]+$kind*DeploymentKindprimary for the project's primary origins, preview for preview URLs.
"primary""preview"targets*array<>The resolved origins, sorted. For primary, the project's primary
origins at creation. The live view lists only the targets still served.
1 <= itemsstatus*DeploymentStatusWhether the deployment serves its release now.
live— at least one of its targets serves it now.inactive— none does: a later deployment replaced it, or its previews expired or were removed.
"live""inactive"deployed_at*stringWhen the deployment was created.
date-timemetadata*Why the release went live and who made it happen.
release?The release named by release_id, present only when the request asked
for it with expand: ["release"] — the same representation
GET /releases/{release_id} serves. Absent means it was not requested.
import { configureZitadel, getApi } from "@zitadel/api/config";import { setApiCsrfToken } from "@zitadel/api/runtime/auth";// In the browser, through the SDK proxy: the session cookie authenticates the call.const client = getApi(configureZitadel({ projectId: "proj_01KWH1P4MYS7F0N9WD3P2E4YM5" }));// A write the session cookie authenticates carries the session's CSRF token.setApiCsrfToken((await client.getMySessionCsrfToken()).csrf_token);const deployment = await client.createDeployment( { release_id: "rel_01KX3RG8A7F0N9WD3P2E4YM5C1", targets: ["primary"], message: "add phone_number to human-user", expected_deployment_id: "dep_01KX4SB9C8E1F0N9WD3P2E4YM5", }, { project_id: "proj_01KWH1P4MYS7F0N9WD3P2E4YM5" },);{ "id": "dep_01KX4SB9C8E1F0N9WD3P2E4YM5", "project_id": "proj_01KWH1P4MYS7F0N9WD3P2E4YM5", "release_id": "rel_01KX3RG8A7F0N9WD3P2E4YM5C1", "kind": "primary", "targets": [ { "origin": "https://app.acme.com" }, { "origin": "https://www.acme.com" } ], "status": "live", "deployed_at": "2026-10-01T09:30:00Z", "metadata": { "reason": "deploy", "message": "initial import", "rollback_to": null, "deployed_by": "user_01KWH3B72K7M7F0N9WD3P2E4YM", "deployed_by_type": "human" }}Was this helpful?