# client.deployments

> client.deployments — the deployments operations.

`client.deployments` — the `deployments` operations.

On `AsyncKindgi` every method is the same, awaited.

## `client.deployments.list()`

```python signature
list(
    *,
    limit: int | None = None,
    cursor: str | None = None,
    image_ref_prefix: str | None = None,
    signer_key_id: str | UUID | None = None,
    project_id: str | UUID | None = None,
    pack_id: str | UUID | None = None,
    timeout: float | None = None,
) -> DeploymentCollectionPage
```

List signed deployments. `GET /v1/deployments`

Cursor-paginated. Sort order: `activatedAt` descending (binding-defined tiebreak on `deploymentId`). Only the deployments in projects the caller reads are listed (a deployment naming no project is the default project's), so a page can hold fewer than `limit` with `hasMore` true: page by `nextCursor`, not by count. Optional `?imageRefPrefix=` narrows to deployments whose `imageRef` starts with the prefix; `?signerKeyId=` narrows to a specific signing key (useful for revocation audits); `?projectId=` to one environment (the default also finds those naming no project); `?packId=` to one pack.

## `client.deployments.register()`

```python signature
register(
    body: DeploymentRegistrationBody | Mapping[str, Any] | None = None,
    /,
    *,
    idempotency_key: str | None = None,
    timeout: float | None = None,
    **fields: Any,
) -> DeploymentRecord
```

Register a signed deployment. `POST /v1/deployments`

Atomic transaction: check the signing key against the workspace's trust list (the `signingKeyRegistry` binding), verify the Ed25519 signature over the canonical envelope, read the image's `/app/index.json` and verify it hashes to the signed `indexHash` and names the signed `artifactVersion` (`ImageRegistryBinding`), validate every tool / guardrail / agent / flow / data block it declares (the image's index is what registers; the request carries none), upsert into the corresponding registries (in the body's `projectId`, one project per client, else the workspace's default project), then record the deployment. The pack's data blocks (the index's `blocks`) are published first, as `POST /v1/blocks` publishes a version: a version the runtime holds with the same content is kept, and the agents' block ranges pin over the runtime's versions and the pack's, the newest in range. Its tools are checked the same way before anything is written: a tool version the runtime holds is kept only with the same definition (`409 tool-already-registered`). A runtime that does this lists `deploy-publishes-pack-blocks` in `info["x-kindgi-capabilities"]` of `/v1/openapi.json`. Where each tool's secrets are read is recorded on its version (`RecordedSecretScope`); one set nowhere yet is read where this deployment's secrets sync writes (`POST /v1/deployments/{deploymentId}/secrets`), the scope this route needs `admin` on. All-or-nothing rollback on any failure. Idempotency: same `imageDigest` re-submitted returns 200 with the existing record (no new version bump). `Idempotency-Key` also applies at the HTTP layer.

## `client.deployments.check()`

```python signature
check(
    body: DeployCheckBody | Mapping[str, Any] | None = None,
    /,
    *,
    timeout: float | None = None,
    **fields: Any,
) -> DeployCheckResult
```

Check a deploy's blocks against what runs. `POST /v1/deployments/check`

What `POST /v1/deployments` answers about the pack's data blocks against what runs, with nothing written: `kindgi deploy` asks it before it builds the image. For each block, the newest version that runs in a project of one of `agents` (a project's own version, or a segment pin inside one; every project, whoever asks) is compared with the body's: a newer one stops the deploy unless `replace` names the block. Equal passes, a newer one in the files passes, and a block that runs nowhere passes. It takes the deploy's own right: admin on `projectId`, else on the workspace.

## `client.deployments.get()`

```python signature
get(deployment_id: str | UUID, /, *, timeout: float | None = None) -> DeploymentRecord
```

Fetch a signed deployment record. `GET /v1/deployments/{deploymentId}`

## `client.deployments.pack_env()`

```python signature
pack_env(deployment_id: str | UUID, /, *, timeout: float | None = None) -> PackEnvNames
```

Show where each name a deployment's pack declares is set. `GET /v1/deployments/{deploymentId}/pack-env`

On a server that runs a pack service per deployment and gives it the env its pack declares: each declared name, where it's set (the deployment's project, then its org, then the workspace, at the env the server serves) and why it can't be given, if it can't. Never a value. A name set only as a secret can't be given into the pack service's environment yet: a tool reads it per call (`ctx.secrets`).

## `client.deployments.sync_secrets()`

```python signature
sync_secrets(
    deployment_id: str | UUID,
    body: DeploymentSecretsSyncRequest | Mapping[str, Any] | None = None,
    /,
    *,
    idempotency_key: str | None = None,
    timeout: float | None = None,
    **fields: Any,
) -> DeploymentSecretsSyncResponse
```

Sync secrets against a deployment. `POST /v1/deployments/{deploymentId}/secrets`

Deployment-scoped bulk secret sync. Body carries `{ envName, secrets: Array<{ name, ref } | { name, value }> }`. `{ name, ref }` entries validate against `SecretBinding.get` — no bytes cross the wire; missing → 404 `secret-not-found`. `{ name, value }` entries write via `SecretBinding.set` with `writeMode: add-version` + `tags.deployment = deploymentId` and return the created reference. The write scope is derived from the deployment record itself (tenant scope, or project scope when the deployment carries a `projectId`) and CANNOT be overridden on the wire. A secret the tools of this deployment name that was set nowhere when a deploy or a promotion published them is read at that scope, when whoever published them administers it: a deploy needs `admin` there, while a promotion into the default project needs it only on that project. Requires the `secrets:write` capability. Idempotency-Key applies.

## `client.deployments.promote()`

```python signature
promote(
    deployment_id: str | UUID,
    body: DeploymentPromotionBody | Mapping[str, Any] | None = None,
    /,
    *,
    timeout: float | None = None,
    **fields: Any,
) -> DeploymentPromotion
```

Promote a deployment to the next environment. `POST /v1/deployments/{deploymentId}/promotions`

Moves the exact build a deployment runs into the next environment of its environment order: a new deployment of the target project with the same image, signature, signer and contents, and `promotedFromDeploymentId`. Nothing is built again and no registry is written. The edits of the build's agents and flows (versions made from a version it ships) move with it (`moved.agents[].carried`); edits made from a version it doesn't ship stay (`leftBehind`). Needs `read` on the deployment's project and `admin` on the target. Refused, with nothing written, when the target isn't the next environment, the deployment (or the target's copy) no longer serves, its signer is no longer trusted or its signature doesn't verify, or a value the build needs isn't ready in the target. The target having this build already answers `200` with its deployment, and records the edits made since. With `pin`, the build's blocks (`contents.blocks`) are checked first against what runs in the target, as a deploy's are against what runs: a newer version of one running there stops the promotion with nothing written, unless `replace` names the block, and `blockCheck` answers what the check found. Then the move commits; then each carried agent is pinned through its target scope's release gate, and each carried flow is pinned. `pin` is the ask to take a target project that follows Latest off it: such a pin gets no `409 pin-stops-latest`, and records `stoppedFollowingLatest`. A release gate that refuses or holds an agent isn't an error: its `pinned` entry has `pinned: false` and the gate's `status`. A refusal after the move leaves the move in place, and lists the pins already made in `details.pinned` and `details.pinnedFlows`.

## `client.deployments.promote_check()`

```python signature
promote_check(
    deployment_id: str | UUID,
    body: DeploymentPromotionCheckBody | Mapping[str, Any] | None = None,
    /,
    *,
    timeout: float | None = None,
    **fields: Any,
) -> DeploymentPromotionCheck
```

Check a promotion without making it. `POST /v1/deployments/{deploymentId}/promotions/check`

Runs every check a promotion makes and writes nothing: what would move, the next environment, and each value with a problem in the target (where a promotion refuses with `promotion-values-not-ready`). Refused as the promotion is for every other check. Needs `read` on the deployment's project and `admin` on the target.
