# Register a signed deployment

POST

/v1/deployments

```sh
curl --request POST \
  --url https://example.com/v1/deployments \
  --header 'Authorization: Bearer <token>' \
  --header 'Content-Type: application/json' \
  --data '{ "imageRef": "example", "artifactVersion": "example", "indexHash": "example", "signerKeyId": "example", "signerPublicKey": "example", "signature": "example", "publishedAt": "2026-04-15T12:00:00Z", "projectId": "2489E9AD-2EE2-8E00-8EC9-32D5F69181C0", "source": { "repo": "example", "path": "example", "commit": "example", "branch": "example", "dirty": true }, "replace": [ { "blockId": "example", "reason": "example" } ] }'
```

Atomic transaction: check the signing key against the tenant’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 tenant’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.

## Authorizations

- **[bearerAuth](https://docs.kindgi.com/v0.4.0/reference/api.md#bearerauth)**

## Parameters

### Header Parameters

**Idempotency-Key**

string

\>= 1 characters

Caller-supplied idempotency key, scoped to the caller: the same key from someone else in the tenant is their own request (from 0.1.5). Retries with the same key return the original response byte-identical (per `docs/API-ROUTE-CONVENTIONS.md` §3.1). A retry sent while the first request still runs is answered `409 idempotency-key-in-flight` with `Retry-After`, on a runtime whose store holds keys (from 0.1.5); retry after it, and you get the first request’s answer. An answer that carries a secret (a new API key or public run token, a session token, a generated signing secret) isn’t kept: a retry gets `409 idempotency-key-replay-withheld`, with the first request’s status and when it succeeded (from 0.1.5). A request whose key store can’t be read before it runs is answered `503 idempotency-store-unavailable` with `Retry-After`: nothing ran, so retry with the same key (from 0.3.0).

## Request Bodyrequired

application/json

object

**imageRef**

required

Full digest-pinned OCI reference: `<host>/<repo>@sha256:<hex>`.

string

**artifactVersion**

required

Issuer-generated `YYYYMMDD.N` — covered by the signature.

string

/^\d{8}\\.\d+$/

**indexHash**

required

Sha256 of the image’s `/app/index.json`, byte for byte — covered by the signature. The server reads the file from the image, checks it hashes to this, and registers what it declares.

string

/^sha256:\[0-9a-f]{64}$/

**signerKeyId**

required

string

\>= 1 characters

**signerPublicKey**

required

PEM-encoded Ed25519 public key (`-----BEGIN PUBLIC KEY-----\n…`). Parsed to raw bytes for verification.

string

**signature**

required

Base64 of the Ed25519 signature over canonicalise({ imageDigest, artifactVersion, indexHash, tenantId, publishedAt }) with sorted keys, no whitespace, UTF-8.

string

**publishedAt**

required

Issuer-supplied ISO-8601 timestamp — covered by the signature.

string format: date-time

**projectId**

The project to deploy into, one per client: its agents, flows, tools and guardrails are registered there, and its secret sync writes there. It takes admin on that project (a key limited to another project is refused). Absent: a tenant-wide deploy, into the tenant’s default project. An id that already lives in another project is `409` (`tool-project-mismatch` and the like): each client gets its own pack, whose ids carry its own pack id. Not covered by the signature, which attests the image.

string format: uuid

**source**

Where the pack lives in git, when the deploy runs inside a work tree. The deployment keeps it with each block’s file from the image’s index. A field it doesn’t know is ignored. Not covered by the signature.

object

**repo**

The `origin` remote as `host/owner/name` (a group path may stand for the owner), with no scheme, user, password, token or `.git`. Absent when the work tree has no `origin`, or one that is not a host and a path.

string

<= 1024 characters /^\[a-z0-9]\(?:\[a-z0-9.-]\*\[a-z0-9])?(?:/\[A-Za-z0-9\_.-]+){2,}$/

**path**

required

The pack’s folder relative to the repository’s root, `/`-separated: `.` when it is the root. Never an absolute path.

string

\>= 1 characters <= 1024 characters

**commit**

required

The commit checked out (`HEAD`), its full hex id.

string

/^(?:\[0-9a-f]{40}|\[0-9a-f]{64})$/

**branch**

The branch checked out. Absent on a detached `HEAD`.

string

\>= 1 characters <= 255 characters

**dirty**

required

The pack’s folder had changes the commit doesn’t hold, untracked files included.

boolean

**replace**

The blocks the deploy puts back to an older version than runs now, on purpose, each with why; a block named once. Without it, a newer version of one of the pack’s blocks running in a project of one of its agents stops the deploy (`409 deploy-would-put-back-older-blocks`). The deployment records each one put back (`replaced`). Not covered by the signature.

Array\<object>

object

**blockId**

required

string

\>= 1 characters

**reason**

required

Why the older version goes back: 1 to 500 characters, no control characters.

string

\>= 1 characters <= 500 characters

##### Examplegenerated

```json
{
  "imageRef": "example",
  "artifactVersion": "example",
  "indexHash": "example",
  "signerKeyId": "example",
  "signerPublicKey": "example",
  "signature": "example",
  "publishedAt": "2026-04-15T12:00:00Z",
  "projectId": "2489E9AD-2EE2-8E00-8EC9-32D5F69181C0",
  "source": {
    "repo": "example",
    "path": "example",
    "commit": "example",
    "branch": "example",
    "dirty": true
  },
  "replace": [
    {
      "blockId": "example",
      "reason": "example"
    }
  ]
}
```

## Responses

### 200

Digest replay — same imageDigest already recorded; returns existing record.

application/json

object

**deploymentId**

required

string

\>= 1 characters

**tenantId**

required

string format: uuid

**imageRef**

required

Full digest-pinned OCI reference: `<host>/<repo>@sha256:<hex>`.

string

**imageDigest**

required

Idempotency key. Extracted from `imageRef` after the `@`.

string

/^sha256:\[0-9a-f]{64}$/

**artifactVersion**

required

Issuer-supplied `YYYYMMDD.N` — matches what was signed.

string

/^\d{8}\\.\d+$/

**indexHash**

required

Sha256 of the canonicalised `/app/index.json` inside the image.

string

/^sha256:\[0-9a-f]{64}$/

**signerKeyId**

required

string

\>= 1 characters

**signerPublicKey**

required

Base64 of the raw Ed25519 public key bytes (32 bytes → 44 chars).

string

**signature**

required

Base64 of the raw Ed25519 signature (64 bytes → 88 chars).

string

**publishedAt**

required

Issuer-supplied timestamp inside the signed envelope.

string format: date-time

**activatedAt**

required

Server-side ledger timestamp — when the register call succeeded.

string format: date-time

**projectId**

The project the deploy named, which its primitives were registered into. Absent for a tenant-wide deploy (the tenant’s default project).

string format: uuid

**packEnv**

The names the pack service reads from its process environment, as the image’s `index.json` declares them (`env`), each list in name order. Names only, never a value. On a deployment: absent when it declares none, and from older servers.

object

**required**

required

Names the pack service needs, set and non-empty, to be ready.

Array\<string>

**optional**

required

Names the pack reads when present.

Array\<string>

**promotedFromDeploymentId**

When this deployment was made by promoting another into the next environment of its order: the deployment it was promoted from. Absent for one deployed directly.

string

\>= 1 characters

**packId**

The pack’s id, from the image’s index. Absent from a runtime that doesn’t keep it.

string

**endpoint**

Its pack service’s status, when the server routes each pack call to the deployment that shipped the tool (`KINDGI_PACK_ROUTING=per-deployment`). Absent otherwise, and for a deployment that ships nothing a pack service serves (no tool, and no guardrail check but built-in ones: a pack of agents and flows only), which needs none. `pending`: no pack service yet, and calls to its tools fail (`pack-endpoint-pending`) rather than go to another deployment. `ready`: calls go to its own pack service. `cleared`: its pack service was taken down, and calls fail (`pack-endpoint-cleared`); it carries `waiting` or `retired` to say whether it comes back. Carries no URL and no token.

object

**status**

required

string

Allowed values: pending ready cleared

**since**

required

When the status last changed.

string format: date-time

**problem**

While the deployment waits for a pack service (`pending`, or `cleared` and `waiting`): the last reason recorded while it waited. A `ready` endpoint has none, nor a `retired` one. A fixed code, never free text.

object

**code**

required

One of a fixed list; a later runtime may add codes, so treat an unknown one as a reason not yet named. The server refused the endpoint it was given: `endpoint-url-invalid` (not `https`, or with user info or a path), `endpoint-host-not-allowed` (the host isn’t on the operator’s allowlist), `endpoint-name-mismatch` (the host doesn’t name this deployment’s service), `endpoint-unreachable` (the pack service didn’t answer), `endpoint-info-mismatch` (it answered as another pack or version), `image-not-allowed` (the image is outside the tenant’s repository). The pack service couldn’t be created: `tenant-not-provisioned` (the tenant has nowhere to run pack services yet), `capacity-unavailable` (no room for another pack service just now), `service-start-failed` (the image was deployed but didn’t start). It can’t be given its declared environment (`GET /v1/deployments/{deploymentId}/pack-env` names the names): `pack-env-ambiguous`, `pack-env-secret-not-yet`, `pack-env-missing`, `pack-env-secret-unreadable` (a name’s secret can’t be read for a reason the platform fixes, not the owner; it’s tried again). It can’t be given its secrets: `pack-env-secrets-unaudited` (the runtime has nowhere to audit a secret it gives out; the platform fixes it, and it’s tried again), `pack-env-secret-too-large` (a secret’s value is over 32768 bytes, the most a pack service can be given), `pack-env-name-too-long` (a name set as a secret is too long for the secret it’s kept in, which is named after the pack service; shorten it in the pack), `pack-env-secret-capacity` (a name is set only as a secret, and there’s no room just now for a pack service that can be given one, apart from other projects’; it’s tried again).

string

**since**

required

When it was recorded.

string format: date-time

**waiting**

`cleared` only: a pack service is wanted for it again, as once a suspension that took its service down is lifted, and it lists as needing one; `problem` says why it isn’t back yet. A `cleared` endpoint carries this or `retired`, never both. Absent from older servers, which send neither.

boolean

**retired**

`cleared` only: why no pack service is wanted for it now. `superseded`: nothing a call can still ask for needs it: for each tool version, pack check, agent and flow version it serves, a newer deployment into its project serves it too, or it’s unregistered and not held (a check: no registered guardrail runs it in its project); and each tool it deployed without a version, a newer deployment into its project deploys without one too (from Kindgi 0.4.0; before, it meant newer deployments into its project shipped every tool version and check it does and deployed each tool it deployed without a version the same way, and was for good). One whose endpoint an earlier runtime made or last set is `superseded` as well, for good, once newer deployments into its project ship every tool version and check it does and deploy each tool it deployed without a version the same way. Otherwise it’s needed again once something it serves that no newer deployment into its project serves is wanted again: a tool, agent or flow version registered or held again, or a check a newly registered guardrail runs in its project. A tool call a step made on it still goes back to it when the step runs again, and fails closed (`pack-endpoint-cleared`). `tenant-closing`, `tenant-suspended`, `project-suspended` and `pack-suspended` last until the tenant, project or pack is restored. A later runtime may add reasons, so treat an unknown one as a reason not yet named. A `cleared` endpoint carries this or `waiting`, never both. Absent from older servers, which send neither.

string

**packEnvProblem**

`ready` only: the pack service runs on the values it was last given, because the newest can’t be given to it (a required one was removed, say, or a secret can’t be given), or didn’t start (`pack-env-update-failed`). Gone once they can, or do. For a name that can’t be given, `GET /v1/deployments/{deploymentId}/pack-env` names it. Absent from older servers.

object

**code**

required

Why a `ready` pack service runs on the values it was last given: a `PackEnvProblemCode`, so the newest can’t be given to it; `pack-env-update-failed`: they were given, and its new revision with them didn’t start, so the last ready one serves. That revision is tried again for an hour after it’s first recorded; setting the values again is a new revision, tried again too. The orchestrator couldn’t give it its secrets: `pack-env-secrets-unaudited` (the runtime has nowhere to audit a secret it gives out, so it gives none; the platform fixes it), `pack-env-secret-too-large` (a secret’s value is over 32768 bytes, the most a pack service can be given), `pack-env-name-too-long` (a name set as a secret is too long for the secret it’s kept in, which is named after the pack service; shorten it in the pack), `pack-env-secret-redeploy` (a name is now set only as a secret, and this pack service was made where no secret can be given to it; a new deployment’s can be, from a new build of the pack), `pack-env-cloud-secret-unsupported` (a name is set to a cloud secret, and this pack service’s host mounts none; removing it, or setting a value instead, moves the revision). A service held as `pack-env-secret-unreadable` or `pack-env-secrets-unaudited` is listed by `GET /v1/system/pack-env` every time, with its `held`, as the revision doesn’t move when the platform fixes it. A later runtime may add codes, so treat an unknown one as a reason not yet named.

string

Allowed values: pack-env-ambiguous pack-env-secret-not-yet pack-env-missing pack-env-secret-unreadable pack-env-update-failed pack-env-secrets-unaudited pack-env-secret-too-large pack-env-name-too-long pack-env-cloud-secret-unsupported pack-env-secret-redeploy

**since**

required

When it was recorded.

string format: date-time

**keptFor**

What keeps this build’s pack service, whatever its status, in kind, id, then version order. `[]` when nothing does: it’s retired (`superseded`), or retired next. A suspended build has `retired` and still lists what keeps it, since its service comes back when the suspension lifts. On a list page, the first 20 entries, with `keptForTotal`; a get of one deployment gives them all. Absent when builds aren’t counted (one service serves every build), and from a runtime before Kindgi 0.4.0.

Array\<object>

One thing a build’s pack service is kept for. To let the build go, unregister it: `kindgi tools|agents|flows unregister <id> <version>`, `kindgi guardrails unregister <id>`.

object

**kind**

required

`tool`: a tool version this build is the newest into its project to route, still registered or held; a tool deployed without a version has no `version` and neither flag, and is kept until a newer deployment into the project ships the tool. `agent`, `flow`: a version this build is the newest into its project to ship or carry, still registered or held. `guardrail`: a registered guardrail that runs a pack check this build is the newest into its project to route; it has no `version`. A later runtime may add kinds, so treat an unknown one as a kind not yet named.

string

**id**

required

The tool, agent, flow or guardrail id.

string

**version**

The version kept for. Absent for a guardrail, or a tool deployed without one.

string

**registered**

The version (or guardrail) is registered.

boolean

**held**

A pin or a run that hasn’t finished uses the version. Unregistering it then lets the build go only once the pin’s own version is unregistered or the run finishes. Sent beside `registered` when both are true.

boolean

**keptForTotal**

On a list page, with `keptFor`: how many entries there are, of which `keptFor` holds the first 20. A get of one deployment doesn’t send it.

integer

**primitives**

required

object

**tools**

required

integer

**guardrails**

required

integer

**agents**

required

integer

**flows**

required

integer

**contents**

required

object

**tools**

required

Array\<object>

object

**id**

required

string

**version**

Absent for guardrails, which have no version.

string

**guardrails**

required

Array\<object>

object

**id**

required

string

**version**

Absent for guardrails, which have no version.

string

**agents**

required

Array\<object>

object

**id**

required

string

**version**

required

The version the agent is registered as.

string

**authoredVersion**

The version the agent’s or flow’s definition names, present when it differs from `version`: that version was registered already with other pins or content, and versions never change, so the deploy registered the next free version in its line (or an earlier deploy did).

string

**reason**

Why `version` differs from `authoredVersion`: `pins-changed` (a tool or agent it uses has a new version), `unpinned` (`authoredVersion` was published before pins existed), `version-taken` (`authoredVersion` is registered with other content).

string

Allowed values: pins-changed unpinned version-taken

**newVersion**

`true`: this deploy registered `version`; `false`: an earlier deploy did.

boolean

**pinChanges**

For `pins-changed`: the pins that differ from `authoredVersion`’s.

Array\<object>

One pin that differs between two versions of an agent or a flow.

object

**kind**

required

string

Allowed values: tool prompt setting agent

**id**

required

string

**from**

The earlier version’s pin; absent when it had none.

string

**to**

The later version’s pin; absent when it has none.

string

**flows**

required

Array\<object>

object

**id**

required

string

**version**

required

The version the agent is registered as.

string

**authoredVersion**

The version the agent’s or flow’s definition names, present when it differs from `version`: that version was registered already with other pins or content, and versions never change, so the deploy registered the next free version in its line (or an earlier deploy did).

string

**reason**

Why `version` differs from `authoredVersion`: `pins-changed` (a tool or agent it uses has a new version), `unpinned` (`authoredVersion` was published before pins existed), `version-taken` (`authoredVersion` is registered with other content).

string

Allowed values: pins-changed unpinned version-taken

**newVersion**

`true`: this deploy registered `version`; `false`: an earlier deploy did.

boolean

**pinChanges**

For `pins-changed`: the pins that differ from `authoredVersion`’s.

Array\<object>

One pin that differs between two versions of an agent or a flow.

object

**kind**

required

string

Allowed values: tool prompt setting agent

**id**

required

string

**from**

The earlier version’s pin; absent when it had none.

string

**to**

The later version’s pin; absent when it has none.

string

**blocks**

The pack’s data blocks, each `{ id, version }`. Absent on a deployment recorded before deployments listed them, and from a runtime that doesn’t keep them.

Array\<object>

object

**id**

required

string

**version**

Absent for guardrails, which have no version.

string

**source**

Where a deployment’s pack lives in git, as `kindgi deploy` read it from the work tree it ran in. Absent for a deploy made outside git, one made before deploys said, and from a runtime that doesn’t keep it.

object

**repo**

The `origin` remote as `host/owner/name` (a group path may stand for the owner), with no scheme, user, password, token or `.git`. Absent when the work tree has no `origin`, or one that is not a host and a path.

string

<= 1024 characters /^\[a-z0-9]\(?:\[a-z0-9.-]\*\[a-z0-9])?(?:/\[A-Za-z0-9\_.-]+){2,}$/

**path**

required

The pack’s folder relative to the repository’s root, `/`-separated: `.` when it is the root. Never an absolute path.

string

\>= 1 characters <= 1024 characters

**commit**

required

The commit checked out (`HEAD`), its full hex id.

string

/^(?:\[0-9a-f]{40}|\[0-9a-f]{64})$/

**branch**

The branch checked out. Absent on a detached `HEAD`.

string

\>= 1 characters <= 255 characters

**dirty**

required

The pack’s folder had changes the commit doesn’t hold, untracked files included.

boolean

**blockFiles**

required

Each of the pack’s blocks by id: its file, relative to the pack’s folder, as the image’s index names it.

object

**_key_**

additional properties

string

**replaced**

The blocks the deploy put back to an older version than ran, on purpose (the body’s `replace`). A deployment a promotion made has its own: the blocks that promotion’s pins put back in its project, never the source deployment’s. Absent when it put back none, and from a runtime that doesn’t keep them.

Array\<object>

object

**blockId**

required

string

**from**

required

The newest version that ran in a project of one of the pack’s agents (a promotion: in its target).

string

**to**

required

The files’ older version, which the deploy shipped (a promotion: the build’s).

string

**reason**

required

Why, as the deploy or the promotion said.

string

**by**

required

Who pinned or unpinned: a user, or a service token.

object

**kind**

required

string

Allowed values: user service

**id**

required

string

##### Example

```json
{
  "endpoint": {
    "status": "pending",
    "waiting": true,
    "packEnvProblem": {
      "code": "pack-env-ambiguous"
    },
    "keptFor": [
      {
        "registered": true,
        "held": true
      }
    ]
  },
  "contents": {
    "agents": [
      {
        "reason": "pins-changed",
        "pinChanges": [
          {
            "kind": "tool"
          }
        ]
      }
    ],
    "flows": [
      {
        "reason": "pins-changed",
        "pinChanges": [
          {
            "kind": "tool"
          }
        ]
      }
    ]
  },
  "replaced": [
    {
      "by": {
        "kind": "user"
      }
    }
  ]
}
```

### 201

Deployment registered.

application/json

object

**deploymentId**

required

string

\>= 1 characters

**tenantId**

required

string format: uuid

**imageRef**

required

Full digest-pinned OCI reference: `<host>/<repo>@sha256:<hex>`.

string

**imageDigest**

required

Idempotency key. Extracted from `imageRef` after the `@`.

string

/^sha256:\[0-9a-f]{64}$/

**artifactVersion**

required

Issuer-supplied `YYYYMMDD.N` — matches what was signed.

string

/^\d{8}\\.\d+$/

**indexHash**

required

Sha256 of the canonicalised `/app/index.json` inside the image.

string

/^sha256:\[0-9a-f]{64}$/

**signerKeyId**

required

string

\>= 1 characters

**signerPublicKey**

required

Base64 of the raw Ed25519 public key bytes (32 bytes → 44 chars).

string

**signature**

required

Base64 of the raw Ed25519 signature (64 bytes → 88 chars).

string

**publishedAt**

required

Issuer-supplied timestamp inside the signed envelope.

string format: date-time

**activatedAt**

required

Server-side ledger timestamp — when the register call succeeded.

string format: date-time

**projectId**

The project the deploy named, which its primitives were registered into. Absent for a tenant-wide deploy (the tenant’s default project).

string format: uuid

**packEnv**

The names the pack service reads from its process environment, as the image’s `index.json` declares them (`env`), each list in name order. Names only, never a value. On a deployment: absent when it declares none, and from older servers.

object

**required**

required

Names the pack service needs, set and non-empty, to be ready.

Array\<string>

**optional**

required

Names the pack reads when present.

Array\<string>

**promotedFromDeploymentId**

When this deployment was made by promoting another into the next environment of its order: the deployment it was promoted from. Absent for one deployed directly.

string

\>= 1 characters

**packId**

The pack’s id, from the image’s index. Absent from a runtime that doesn’t keep it.

string

**endpoint**

Its pack service’s status, when the server routes each pack call to the deployment that shipped the tool (`KINDGI_PACK_ROUTING=per-deployment`). Absent otherwise, and for a deployment that ships nothing a pack service serves (no tool, and no guardrail check but built-in ones: a pack of agents and flows only), which needs none. `pending`: no pack service yet, and calls to its tools fail (`pack-endpoint-pending`) rather than go to another deployment. `ready`: calls go to its own pack service. `cleared`: its pack service was taken down, and calls fail (`pack-endpoint-cleared`); it carries `waiting` or `retired` to say whether it comes back. Carries no URL and no token.

object

**status**

required

string

Allowed values: pending ready cleared

**since**

required

When the status last changed.

string format: date-time

**problem**

While the deployment waits for a pack service (`pending`, or `cleared` and `waiting`): the last reason recorded while it waited. A `ready` endpoint has none, nor a `retired` one. A fixed code, never free text.

object

**code**

required

One of a fixed list; a later runtime may add codes, so treat an unknown one as a reason not yet named. The server refused the endpoint it was given: `endpoint-url-invalid` (not `https`, or with user info or a path), `endpoint-host-not-allowed` (the host isn’t on the operator’s allowlist), `endpoint-name-mismatch` (the host doesn’t name this deployment’s service), `endpoint-unreachable` (the pack service didn’t answer), `endpoint-info-mismatch` (it answered as another pack or version), `image-not-allowed` (the image is outside the tenant’s repository). The pack service couldn’t be created: `tenant-not-provisioned` (the tenant has nowhere to run pack services yet), `capacity-unavailable` (no room for another pack service just now), `service-start-failed` (the image was deployed but didn’t start). It can’t be given its declared environment (`GET /v1/deployments/{deploymentId}/pack-env` names the names): `pack-env-ambiguous`, `pack-env-secret-not-yet`, `pack-env-missing`, `pack-env-secret-unreadable` (a name’s secret can’t be read for a reason the platform fixes, not the owner; it’s tried again). It can’t be given its secrets: `pack-env-secrets-unaudited` (the runtime has nowhere to audit a secret it gives out; the platform fixes it, and it’s tried again), `pack-env-secret-too-large` (a secret’s value is over 32768 bytes, the most a pack service can be given), `pack-env-name-too-long` (a name set as a secret is too long for the secret it’s kept in, which is named after the pack service; shorten it in the pack), `pack-env-secret-capacity` (a name is set only as a secret, and there’s no room just now for a pack service that can be given one, apart from other projects’; it’s tried again).

string

**since**

required

When it was recorded.

string format: date-time

**waiting**

`cleared` only: a pack service is wanted for it again, as once a suspension that took its service down is lifted, and it lists as needing one; `problem` says why it isn’t back yet. A `cleared` endpoint carries this or `retired`, never both. Absent from older servers, which send neither.

boolean

**retired**

`cleared` only: why no pack service is wanted for it now. `superseded`: nothing a call can still ask for needs it: for each tool version, pack check, agent and flow version it serves, a newer deployment into its project serves it too, or it’s unregistered and not held (a check: no registered guardrail runs it in its project); and each tool it deployed without a version, a newer deployment into its project deploys without one too (from Kindgi 0.4.0; before, it meant newer deployments into its project shipped every tool version and check it does and deployed each tool it deployed without a version the same way, and was for good). One whose endpoint an earlier runtime made or last set is `superseded` as well, for good, once newer deployments into its project ship every tool version and check it does and deploy each tool it deployed without a version the same way. Otherwise it’s needed again once something it serves that no newer deployment into its project serves is wanted again: a tool, agent or flow version registered or held again, or a check a newly registered guardrail runs in its project. A tool call a step made on it still goes back to it when the step runs again, and fails closed (`pack-endpoint-cleared`). `tenant-closing`, `tenant-suspended`, `project-suspended` and `pack-suspended` last until the tenant, project or pack is restored. A later runtime may add reasons, so treat an unknown one as a reason not yet named. A `cleared` endpoint carries this or `waiting`, never both. Absent from older servers, which send neither.

string

**packEnvProblem**

`ready` only: the pack service runs on the values it was last given, because the newest can’t be given to it (a required one was removed, say, or a secret can’t be given), or didn’t start (`pack-env-update-failed`). Gone once they can, or do. For a name that can’t be given, `GET /v1/deployments/{deploymentId}/pack-env` names it. Absent from older servers.

object

**code**

required

Why a `ready` pack service runs on the values it was last given: a `PackEnvProblemCode`, so the newest can’t be given to it; `pack-env-update-failed`: they were given, and its new revision with them didn’t start, so the last ready one serves. That revision is tried again for an hour after it’s first recorded; setting the values again is a new revision, tried again too. The orchestrator couldn’t give it its secrets: `pack-env-secrets-unaudited` (the runtime has nowhere to audit a secret it gives out, so it gives none; the platform fixes it), `pack-env-secret-too-large` (a secret’s value is over 32768 bytes, the most a pack service can be given), `pack-env-name-too-long` (a name set as a secret is too long for the secret it’s kept in, which is named after the pack service; shorten it in the pack), `pack-env-secret-redeploy` (a name is now set only as a secret, and this pack service was made where no secret can be given to it; a new deployment’s can be, from a new build of the pack), `pack-env-cloud-secret-unsupported` (a name is set to a cloud secret, and this pack service’s host mounts none; removing it, or setting a value instead, moves the revision). A service held as `pack-env-secret-unreadable` or `pack-env-secrets-unaudited` is listed by `GET /v1/system/pack-env` every time, with its `held`, as the revision doesn’t move when the platform fixes it. A later runtime may add codes, so treat an unknown one as a reason not yet named.

string

Allowed values: pack-env-ambiguous pack-env-secret-not-yet pack-env-missing pack-env-secret-unreadable pack-env-update-failed pack-env-secrets-unaudited pack-env-secret-too-large pack-env-name-too-long pack-env-cloud-secret-unsupported pack-env-secret-redeploy

**since**

required

When it was recorded.

string format: date-time

**keptFor**

What keeps this build’s pack service, whatever its status, in kind, id, then version order. `[]` when nothing does: it’s retired (`superseded`), or retired next. A suspended build has `retired` and still lists what keeps it, since its service comes back when the suspension lifts. On a list page, the first 20 entries, with `keptForTotal`; a get of one deployment gives them all. Absent when builds aren’t counted (one service serves every build), and from a runtime before Kindgi 0.4.0.

Array\<object>

One thing a build’s pack service is kept for. To let the build go, unregister it: `kindgi tools|agents|flows unregister <id> <version>`, `kindgi guardrails unregister <id>`.

object

**kind**

required

`tool`: a tool version this build is the newest into its project to route, still registered or held; a tool deployed without a version has no `version` and neither flag, and is kept until a newer deployment into the project ships the tool. `agent`, `flow`: a version this build is the newest into its project to ship or carry, still registered or held. `guardrail`: a registered guardrail that runs a pack check this build is the newest into its project to route; it has no `version`. A later runtime may add kinds, so treat an unknown one as a kind not yet named.

string

**id**

required

The tool, agent, flow or guardrail id.

string

**version**

The version kept for. Absent for a guardrail, or a tool deployed without one.

string

**registered**

The version (or guardrail) is registered.

boolean

**held**

A pin or a run that hasn’t finished uses the version. Unregistering it then lets the build go only once the pin’s own version is unregistered or the run finishes. Sent beside `registered` when both are true.

boolean

**keptForTotal**

On a list page, with `keptFor`: how many entries there are, of which `keptFor` holds the first 20. A get of one deployment doesn’t send it.

integer

**primitives**

required

object

**tools**

required

integer

**guardrails**

required

integer

**agents**

required

integer

**flows**

required

integer

**contents**

required

object

**tools**

required

Array\<object>

object

**id**

required

string

**version**

Absent for guardrails, which have no version.

string

**guardrails**

required

Array\<object>

object

**id**

required

string

**version**

Absent for guardrails, which have no version.

string

**agents**

required

Array\<object>

object

**id**

required

string

**version**

required

The version the agent is registered as.

string

**authoredVersion**

The version the agent’s or flow’s definition names, present when it differs from `version`: that version was registered already with other pins or content, and versions never change, so the deploy registered the next free version in its line (or an earlier deploy did).

string

**reason**

Why `version` differs from `authoredVersion`: `pins-changed` (a tool or agent it uses has a new version), `unpinned` (`authoredVersion` was published before pins existed), `version-taken` (`authoredVersion` is registered with other content).

string

Allowed values: pins-changed unpinned version-taken

**newVersion**

`true`: this deploy registered `version`; `false`: an earlier deploy did.

boolean

**pinChanges**

For `pins-changed`: the pins that differ from `authoredVersion`’s.

Array\<object>

One pin that differs between two versions of an agent or a flow.

object

**kind**

required

string

Allowed values: tool prompt setting agent

**id**

required

string

**from**

The earlier version’s pin; absent when it had none.

string

**to**

The later version’s pin; absent when it has none.

string

**flows**

required

Array\<object>

object

**id**

required

string

**version**

required

The version the agent is registered as.

string

**authoredVersion**

The version the agent’s or flow’s definition names, present when it differs from `version`: that version was registered already with other pins or content, and versions never change, so the deploy registered the next free version in its line (or an earlier deploy did).

string

**reason**

Why `version` differs from `authoredVersion`: `pins-changed` (a tool or agent it uses has a new version), `unpinned` (`authoredVersion` was published before pins existed), `version-taken` (`authoredVersion` is registered with other content).

string

Allowed values: pins-changed unpinned version-taken

**newVersion**

`true`: this deploy registered `version`; `false`: an earlier deploy did.

boolean

**pinChanges**

For `pins-changed`: the pins that differ from `authoredVersion`’s.

Array\<object>

One pin that differs between two versions of an agent or a flow.

object

**kind**

required

string

Allowed values: tool prompt setting agent

**id**

required

string

**from**

The earlier version’s pin; absent when it had none.

string

**to**

The later version’s pin; absent when it has none.

string

**blocks**

The pack’s data blocks, each `{ id, version }`. Absent on a deployment recorded before deployments listed them, and from a runtime that doesn’t keep them.

Array\<object>

object

**id**

required

string

**version**

Absent for guardrails, which have no version.

string

**source**

Where a deployment’s pack lives in git, as `kindgi deploy` read it from the work tree it ran in. Absent for a deploy made outside git, one made before deploys said, and from a runtime that doesn’t keep it.

object

**repo**

The `origin` remote as `host/owner/name` (a group path may stand for the owner), with no scheme, user, password, token or `.git`. Absent when the work tree has no `origin`, or one that is not a host and a path.

string

<= 1024 characters /^\[a-z0-9]\(?:\[a-z0-9.-]\*\[a-z0-9])?(?:/\[A-Za-z0-9\_.-]+){2,}$/

**path**

required

The pack’s folder relative to the repository’s root, `/`-separated: `.` when it is the root. Never an absolute path.

string

\>= 1 characters <= 1024 characters

**commit**

required

The commit checked out (`HEAD`), its full hex id.

string

/^(?:\[0-9a-f]{40}|\[0-9a-f]{64})$/

**branch**

The branch checked out. Absent on a detached `HEAD`.

string

\>= 1 characters <= 255 characters

**dirty**

required

The pack’s folder had changes the commit doesn’t hold, untracked files included.

boolean

**blockFiles**

required

Each of the pack’s blocks by id: its file, relative to the pack’s folder, as the image’s index names it.

object

**_key_**

additional properties

string

**replaced**

The blocks the deploy put back to an older version than ran, on purpose (the body’s `replace`). A deployment a promotion made has its own: the blocks that promotion’s pins put back in its project, never the source deployment’s. Absent when it put back none, and from a runtime that doesn’t keep them.

Array\<object>

object

**blockId**

required

string

**from**

required

The newest version that ran in a project of one of the pack’s agents (a promotion: in its target).

string

**to**

required

The files’ older version, which the deploy shipped (a promotion: the build’s).

string

**reason**

required

Why, as the deploy or the promotion said.

string

**by**

required

Who pinned or unpinned: a user, or a service token.

object

**kind**

required

string

Allowed values: user service

**id**

required

string

##### Example

```json
{
  "endpoint": {
    "status": "pending",
    "waiting": true,
    "packEnvProblem": {
      "code": "pack-env-ambiguous"
    },
    "keptFor": [
      {
        "registered": true,
        "held": true
      }
    ]
  },
  "contents": {
    "agents": [
      {
        "reason": "pins-changed",
        "pinChanges": [
          {
            "kind": "tool"
          }
        ]
      }
    ],
    "flows": [
      {
        "reason": "pins-changed",
        "pinChanges": [
          {
            "kind": "tool"
          }
        ]
      }
    ]
  },
  "replaced": [
    {
      "by": {
        "kind": "user"
      }
    }
  ]
}
```

### 400

Signature invalid, image unverifiable, or deployment-validation-failed with per-primitive `details[]`. When `image-unverifiable` comes from a failed registry read, `details.cause` names why: `image-not-found`, `image-file-not-found`, `image-registry-unreachable`, `image-registry-unauthorized`, `image-unreadable` (a layer that isn’t a readable archive), or `image-limit-exceeded`, with the bound in `details.limit` (`manifest`, `file`, `layer`, `read` or `unpack`: the image’s own, so the same image is refused again). The tenant’s hour of image reads passes, so it answers `429`. A tool that declares or sends a model provider’s key, or a secret under the reserved `kindgi.` prefix, is one such issue (`path` `/secrets/<name>`): a tool never gets a model provider’s key or one of Kindgi’s own secrets. An agent’s `output.schema`, or a flow’s loop, fanout branch or subgraph `outputSchema` or its `output.schema`, that doesn’t compile is one too (`path` where it sits, such as `/output/schema` or `/nodes/<i>/branches/<k>/outputSchema`), as `defineAgent` and the flow loader refuse it: every turn or run would. A tool whose `metadata` has a key under `kindgi.` (the runtime’s own) is a `validation-failed` detail at `/metadata/<key>`. Or `reference-out-of-scope`: an agent or flow names a tool, agent, flow, guardrail or data block that neither the deployment brings nor its project reaches (`details.issues`, at `/agents/<i>/…` or `/flows/<i>/…`); nothing is registered.

application/json

object

**error**

required

object

**code**

required

Stable machine-readable discriminant. Values match domain error codes (see `docs/API-ROUTE-CONVENTIONS.md` §4.3).

string

**message**

required

string

**details**

Optional, kind-specific.

object

**_key_**

additional properties

any

**requestId**

required

Server-assigned request id; also echoed via `X-Request-Id` header.

string

##### Examplegenerated

```json
{
  "error": {
    "code": "example",
    "message": "example",
    "details": {},
    "requestId": "example"
  }
}
```

### 401

Missing / malformed / expired / revoked bearer token.

application/json

object

**error**

required

object

**code**

required

Stable machine-readable discriminant. Values match domain error codes (see `docs/API-ROUTE-CONVENTIONS.md` §4.3).

string

**message**

required

string

**details**

Optional, kind-specific.

object

**_key_**

additional properties

any

**requestId**

required

Server-assigned request id; also echoed via `X-Request-Id` header.

string

##### Examplegenerated

```json
{
  "error": {
    "code": "example",
    "message": "example",
    "details": {},
    "requestId": "example"
  }
}
```

### 403

Signer key not on the tenant’s trust list. Or, with `projectId`, no admin on that project (`forbidden`), or a key limited to another project (`key-project-mismatch`); without it, no admin on the tenant.

application/json

object

**error**

required

object

**code**

required

Stable machine-readable discriminant. Values match domain error codes (see `docs/API-ROUTE-CONVENTIONS.md` §4.3).

string

**message**

required

string

**details**

Optional, kind-specific.

object

**_key_**

additional properties

any

**requestId**

required

Server-assigned request id; also echoed via `X-Request-Id` header.

string

##### Examplegenerated

```json
{
  "error": {
    "code": "example",
    "message": "example",
    "details": {},
    "requestId": "example"
  }
}
```

### 404

The body’s `projectId` names no project of this tenant (`project-not-found`).

application/json

object

**error**

required

object

**code**

required

Stable machine-readable discriminant. Values match domain error codes (see `docs/API-ROUTE-CONVENTIONS.md` §4.3).

string

**message**

required

string

**details**

Optional, kind-specific.

object

**_key_**

additional properties

any

**requestId**

required

Server-assigned request id; also echoed via `X-Request-Id` header.

string

##### Examplegenerated

```json
{
  "error": {
    "code": "example",
    "message": "example",
    "details": {},
    "requestId": "example"
  }
}
```

### 409

Idempotency-Key was reused with a different body, or resource-state conflict. Or, on a runtime serving many tenants (`KINDGI_TENANCY=multi`), where each tenant deploys only its own images: `image-not-allowed`, the image is outside the tenant’s repository (`details.imagePrefix`, its own), or `image-prefix-unset`, the tenant has no repository set yet; both are checked before any registry read, and never on a runtime serving one tenant. Or `tool-project-mismatch`, `agent-project-mismatch`, `flow-project-mismatch` or `block-project-mismatch`: one of the image’s tools, agents, flows or data blocks belongs to another project (`details.primitive`, `details.id`; the message doesn’t name the project); nothing was deployed, even when it was unchanged. Or `block-already-registered`: the runtime holds one of the pack’s data block versions with other content, and a version never changes; or holds versions of the pack’s blocks unregistered, by hand or by the rollback of a deploy that failed after publishing them, and a deploy never reinstates one (`details.primitive` is `block`, `details.id` is `<id>@<version>`, the first such block); the message says to give it a new version, or names each unregistered one with the command that reinstates it; nothing was deployed. Or `tool-already-registered`: the runtime holds one of the pack’s tool versions, active or unregistered, with another definition, and a version never changes (`details.primitive` is `tool`, `details.id` is `<id>@<version>`, `details.fields` the fields of its definition that differ: the manifest’s top-level fields, and `modulePath` for where its code lives in the pack). A new image of the same tool isn’t another definition: its image and artifact version aren’t compared. The message says to give the tool a new version; nothing was deployed. Or `tool-kind-mismatch`: one of the image’s tools has the id of an MCP endpoint’s tool (`details.endpointId`; `details.primitive`, `details.id`), whose active versions are all the endpoint’s; nothing was deployed. Or `project-suspended`: the project the deploy goes into (the named one, or the default for a tenant-wide deploy) is suspended, and nothing deploys into it until it’s restored. Or `environment-takes-promotions`: the project the deploy goes into is a later environment of an environment order, which takes builds only by promotion from the one before it; deploy into the order’s first environment and promote the build from there (`details.firstProjectId`, when you can read that project; `details.position`, the project’s place from 0). Nothing is written. Or `registry-read-only`: this registry takes no writes (under `kindgi dev`, the pack’s files are the source); the message says what to do instead. Or `deploy-would-put-back-older-blocks`: a newer version of one of the pack’s data blocks than its files’ runs now in a project of one of its agents (a project’s own version, or a segment pin inside one), and the body’s `replace` doesn’t name it. `details.blocks` lists each (`DeployCheckStop`): the files’ version and the newest that runs, where, who made it run there and when, and the pull request opened for it; in a project the caller can’t read, the version only. Every project counts, whoever deploys. Nothing was deployed. A runtime that doesn’t read the agents’ pins and Latest doesn’t check.

application/json

object

**error**

required

object

**code**

required

Stable machine-readable discriminant. Values match domain error codes (see `docs/API-ROUTE-CONVENTIONS.md` §4.3).

string

**message**

required

string

**details**

Optional, kind-specific.

object

**_key_**

additional properties

any

**requestId**

required

Server-assigned request id; also echoed via `X-Request-Id` header.

string

##### Examplegenerated

```json
{
  "error": {
    "code": "example",
    "message": "example",
    "details": {},
    "requestId": "example"
  }
}
```

### 413

The request body is over the API’s limit (`request-too-large`; `details.maxBytes` says how much).

application/json

object

**error**

required

object

**code**

required

Stable machine-readable discriminant. Values match domain error codes (see `docs/API-ROUTE-CONVENTIONS.md` §4.3).

string

**message**

required

string

**details**

Optional, kind-specific.

object

**_key_**

additional properties

any

**requestId**

required

Server-assigned request id; also echoed via `X-Request-Id` header.

string

##### Examplegenerated

```json
{
  "error": {
    "code": "example",
    "message": "example",
    "details": {},
    "requestId": "example"
  }
}
```

### 429

`rate-limit-exceeded` with `details.limit: "schema-compile-time"`, on a server with a compile budget (Kindgi Cloud): this tenant’s schemas spent `details.budgetMs` compiling in the last 60 seconds. Nothing was stored; `Retry-After` says when to send it again. Or `details.limit: "tenant-unpack-hour"`, on a server that bounds each tenant’s image reads (Kindgi Cloud): this tenant’s image reads unpacked as much as they may in an hour. Nothing was deployed; `Retry-After` says when there’s room again.

application/json

object

**error**

required

object

**code**

required

Stable machine-readable discriminant. Values match domain error codes (see `docs/API-ROUTE-CONVENTIONS.md` §4.3).

string

**message**

required

string

**details**

Optional, kind-specific.

object

**_key_**

additional properties

any

**requestId**

required

Server-assigned request id; also echoed via `X-Request-Id` header.

string

##### Examplegenerated

```json
{
  "error": {
    "code": "example",
    "message": "example",
    "details": {},
    "requestId": "example"
  }
}
```

### 500

Server error (unmapped domain code or framework crash).

application/json

object

**error**

required

object

**code**

required

Stable machine-readable discriminant. Values match domain error codes (see `docs/API-ROUTE-CONVENTIONS.md` §4.3).

string

**message**

required

string

**details**

Optional, kind-specific.

object

**_key_**

additional properties

any

**requestId**

required

Server-assigned request id; also echoed via `X-Request-Id` header.

string

##### Examplegenerated

```json
{
  "error": {
    "code": "example",
    "message": "example",
    "details": {},
    "requestId": "example"
  }
}
```

### 503

The `Idempotency-Key` store couldn’t be read before the request ran (`idempotency-store-unavailable`), so nothing was changed; `Retry-After` (whole seconds) says when to try again with the same key.

application/json

object

**error**

required

object

**code**

required

Stable machine-readable discriminant. Values match domain error codes (see `docs/API-ROUTE-CONVENTIONS.md` §4.3).

string

**message**

required

string

**details**

Optional, kind-specific.

object

**_key_**

additional properties

any

**requestId**

required

Server-assigned request id; also echoed via `X-Request-Id` header.

string

##### Examplegenerated

```json
{
  "error": {
    "code": "example",
    "message": "example",
    "details": {},
    "requestId": "example"
  }
}
```

## See also

- [`GET /v1/deployments`: List signed deployments](https://docs.kindgi.com/v0.4.0/reference/api/operations/deploymentslist.md)
- [Ship a pack from CI](https://docs.kindgi.com/v0.4.0/deploy/ship-from-ci.md)
- [Kindgi 0.1.9](https://docs.kindgi.com/v0.4.0/releases/0.1.9.md)
- [Kindgi 0.4.0](https://docs.kindgi.com/v0.4.0/releases/0.4.0.md)
- [Error code `agent-project-mismatch`](https://docs.kindgi.com/v0.4.0/reference/errors.md#agent-project-mismatch)
- [Error code `block-already-registered`](https://docs.kindgi.com/v0.4.0/reference/errors.md#block-already-registered)
- [Error code `block-project-mismatch`](https://docs.kindgi.com/v0.4.0/reference/errors.md#block-project-mismatch)
- [Error code `deploy-would-put-back-older-blocks`](https://docs.kindgi.com/v0.4.0/reference/errors.md#deploy-would-put-back-older-blocks)
- [Error code `environment-takes-promotions`](https://docs.kindgi.com/v0.4.0/reference/errors.md#environment-takes-promotions)
- [Error code `flow-project-mismatch`](https://docs.kindgi.com/v0.4.0/reference/errors.md#flow-project-mismatch)
- [Error code `idempotency-store-unavailable`](https://docs.kindgi.com/v0.4.0/reference/errors.md#idempotency-store-unavailable)
- [Error code `image-not-allowed`](https://docs.kindgi.com/v0.4.0/reference/errors.md#image-not-allowed)
- [Error code `image-prefix-unset`](https://docs.kindgi.com/v0.4.0/reference/errors.md#image-prefix-unset)
- [Error code `image-unverifiable`](https://docs.kindgi.com/v0.4.0/reference/errors.md#image-unverifiable)
- [Error code `key-project-mismatch`](https://docs.kindgi.com/v0.4.0/reference/errors.md#key-project-mismatch)
- [Error code `project-not-found`](https://docs.kindgi.com/v0.4.0/reference/errors.md#project-not-found)
- [Error code `project-suspended`](https://docs.kindgi.com/v0.4.0/reference/errors.md#project-suspended)
- [Error code `rate-limit-exceeded`](https://docs.kindgi.com/v0.4.0/reference/errors.md#rate-limit-exceeded)
- [Error code `reference-out-of-scope`](https://docs.kindgi.com/v0.4.0/reference/errors.md#reference-out-of-scope)
- [Error code `registry-read-only`](https://docs.kindgi.com/v0.4.0/reference/errors.md#registry-read-only)
- [Error code `request-too-large`](https://docs.kindgi.com/v0.4.0/reference/errors.md#request-too-large)
- [Error code `tool-already-registered`](https://docs.kindgi.com/v0.4.0/reference/errors.md#tool-already-registered)
- [Error code `tool-kind-mismatch`](https://docs.kindgi.com/v0.4.0/reference/errors.md#tool-kind-mismatch)
- [Error code `tool-project-mismatch`](https://docs.kindgi.com/v0.4.0/reference/errors.md#tool-project-mismatch)
- [Error code `validation-failed`](https://docs.kindgi.com/v0.4.0/reference/errors.md#validation-failed)
- [Every error code](https://docs.kindgi.com/v0.4.0/reference/errors.md)
