Docs

Create a deployment

POST
/deployments

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 of release_id and content_hash; a primary origin named exactly; primary mixed with preview URLs; primary on a project with no primary origin; ttl with primary; ttl above 365 days (P365D).
  • 400 dep.variables_missing: a referenced variable has no value for the kind; details names 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_id is not the deployment this write replaces; details.deployment is that deployment.
  • 409 rel.revoked: the release is revoked.

Authorization

headerAuthorizationBearer <token>

Scope: deployment.write

Query Parameters

project_id*string

The unique identifier of the project

Match^[a-zA-Z0-9_-]+$

Request Body

application/json
  1. 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-id

The release to make live.

Match^rel_[a-zA-Z0-9_-]+$
content_hash?ReleaseContentHash

The content digest of the release to make live, in full with its sha256: prefix.

Match^sha256:[0-9a-f]{64}$
Length71 <= length <= 71
targets*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.

Items1 <= items <= 50
ttl?PreviewTTL

Preview URLs only. How long each preview stays live from this deployment; omitted, 7 days (P7D).

Formatduration
reason?DeploymentReason

Why this deployment happens. Omitted, the server records deploy.

Value in"deploy""rollback"
message?string

A short free-form summary of why this deployment happened, recorded on its metadata.

Lengthlength <= 1024
expected_deployment_id?deployment-id

Deploy 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.

Match^dep_[a-zA-Z0-9_-]+$

Response Body

Nothing changed. The body is the newest primary deployment, with its own reason, message and deployed_at.

application/json
  1. 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-id

The opaque, immutable resource id, assigned at creation.

Match^dep_[a-zA-Z0-9_-]+$
project_id*project-id

The project this deployment belongs to.

Match^[a-zA-Z0-9_-]+$
release_id*release-id

The release this deployment made live. Always the release id, also when the request named the release by content_hash.

Match^rel_[a-zA-Z0-9_-]+$
kind*DeploymentKind

primary for the project's primary origins, preview for preview URLs.

Value in"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.

Items1 <= items
status*DeploymentStatus

Whether 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.
Value in"live""inactive"
deployed_at*string

When the deployment was created.

Formatdate-time
metadata*

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" },);
Example Response
{  "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?