# Pin a version for a scope

POST

/v1/agents/{agentId}/pinned/pin

```sh
curl --request POST \
  --url https://example.com/v1/agents/example/pinned/pin \
  --header 'Authorization: Bearer <token>' \
  --header 'Content-Type: application/json' \
  --data '{ "version": "example", "scope": { "kind": "tenant" }, "reason": "example", "testRunId": "example", "stopFollowingLatest": true }'
```

Pins `version` for `scope`: runs in that scope that don’t name a version use it, from the next run. Open conversations keep their version. The version must be registered and active. Every pin is recorded, with who asked and why. Needs `pin` on the agent.

The pin checks the version in each project whose running version it changes and that has active cases, on the project’s agent test set (`testRuns`): with the run `testRunId` names when that run completed there on the test set as it stands, whose outcome changes the corrections’ statuses at once (`fixed`, `reopened`); otherwise with a run it starts, correlation id `pin:<changeId>`, whose outcome changes them when it ends. A pin that waits for approval starts its runs at once, and its statuses change once it’s approved.

A pin that would take a project that follows Latest now off it (a project or segment pin there, or a workspace or org pin over it) is refused with `409 pin-stops-latest` before any gate, whether or not its version changes, unless the body has `stopFollowingLatest: true`. The pin then records `stoppedFollowingLatest`, and the project runs the pinned version from then on, not each new Latest.

With a release gate for the scope (`GET …/release-gate`), the pin is checked first against the comparison named by `testRunId`: `201` pinned; `202` the gate passed and wants a reviewer’s approval (a HITL approval, subject `agent-pin`; the pinned version moves once it’s approved, if nothing changed meanwhile); `422 gate-failed` with `details.changeId`, `details.releaseGate` and every check in `details.checks`. A refused pin is recorded too.

A pin in place skips the gate: when the scope has no pinned version of its own and already serves exactly `version` (from a scope above, or as the latest), pinning it there changes nothing any run gets, so it is pinned (`201`) with one passing `pinInPlace` check, no comparison and no approval, its reason starting `pin-in-place`. It is re-checked as it is written; if the scope moved meanwhile, `409 pin-superseded`: check again.

Under `kindgi dev`, nothing is pinned, since the pack’s files are what runs: pinning a version writes the data blocks it changes into the pack’s own files (each block’s new version and content, and a prompt’s template into its template file), and the dev server reloads them. Only for the whole workspace (the `tenant` scope), which the pack’s files serve. If one of those files changed since the version was made (the changed block’s file holds another version than the one its suggested change changed, or other content than its suggested change was drafted from, even at that version, or another block’s file a version not older), or the block left the pack, `409 pin-superseded`, and the message names the file; an approval’s decision settles it `superseded` the same way. Once the dev server reloads the files, the pinned version is what runs, and the pin’s test runs check it in the agent’s project (the default one) and change the corrections’ statuses as anywhere else.

## Authorizations

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

## Parameters

### Path Parameters

**agentId**

required

string

AgentId — dotted namespace (`acme.drafting`).

### Header Parameters

**Idempotency-Key**

string

\>= 1 characters

Caller-supplied idempotency key, scoped to the caller: the same key from someone else in the workspace 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

**version**

required

The agent version to pin (registered, active).

string

**scope**

required

One of: discriminator: kind

- **object**

  The whole workspace.

  object

  **kind**

  required

  string

  Allowed values: tenant

- **object**

  An org’s projects.

  object

  **kind**

  required

  string

  Allowed values: org

  **orgId**

  required

  string format: uuid

- **object**

  One project.

  object

  **kind**

  required

  string

  Allowed values: project

  **projectId**

  required

  string format: uuid

- **object**

  A segment path within a project: it covers every run whose path starts with it.

  object

  **kind**

  required

  string

  Allowed values: segment

  **projectId**

  required

  string format: uuid

  **path**

  required

  Array\<object>

  \>= 1 items <= 8 items

  object

  **key**

  required

  Lowercase, like an identifier: `company`, `contact-role`.

  string

  /^\[a-z]\[a-z0-9\_-]{0,63}$/

  **value**

  required

  string

  \>= 1 characters <= 256 characters

**reason**

string

<= 2000 characters

**testRunId**

The comparison behind this pin.

string

**stopFollowingLatest**

Pin even when it takes a project that follows Latest now off it: the project then runs the pinned version, not each new Latest. Without it, such a pin is refused (`409 pin-stops-latest`). `POST …/pinned/check` ignores it.

boolean

## Responses

### 201

Pinned, with the test runs that check it (`testRuns`), and the corrections a reused run changed.

application/json

A pin, and the test runs that check it.

object

**pin**

required

One change of a scope’s pinned version, kept for good: what was pinned before, what is after, who asked and why.

object

**id**

required

string format: uuid

**agentId**

required

string

**scope**

required

One of: discriminator: kind

- **object**

  The whole workspace.

  object

  **kind**

  required

  string

  Allowed values: tenant

- **object**

  An org’s projects.

  object

  **kind**

  required

  string

  Allowed values: org

  **orgId**

  required

  string format: uuid

- **object**

  One project.

  object

  **kind**

  required

  string

  Allowed values: project

  **projectId**

  required

  string format: uuid

- **object**

  A segment path within a project: it covers every run whose path starts with it.

  object

  **kind**

  required

  string

  Allowed values: segment

  **projectId**

  required

  string format: uuid

  **path**

  required

  Array\<object>

  \>= 1 items <= 8 items

  object

  **key**

  required

  Lowercase, like an identifier: `company`, `contact-role`.

  string

  /^\[a-z]\[a-z0-9\_-]{0,63}$/

  **value**

  required

  string

  \>= 1 characters <= 256 characters

**action**

required

string

Allowed values: pin unpin

**fromVersion**

required

The scope’s own pin before; null when it had none.

string | null

**toVersion**

required

The scope’s own pin after; null after an unpin.

string | null

**requestedBy**

required

object

**kind**

required

string

Allowed values: user service

**id**

required

string

**reason**

string

**testRunId**

The comparison behind the change.

string

**createdAt**

required

string format: date-time

**status**

A `pin` row’s state: it changes once, from `pending-approval` to its final state. Absent on unpin rows, and on pins made before gates (`pinned`).

string

Allowed values: pinned pending-approval refused superseded rejected expired

**releaseGate**

One of:

- **object**

  object

  **id**

  required

  string

  **version**

  required

  string

- **null**

  null

**checks**

Array\<object>

One of a gate’s checks, as the pin records it.

object

**name**

required

Stable: `comparison.required`, `comparison.status`, `comparison.freshness`, `comparison.testSet`, `sameContents`, `baselineIsPinned`, `scopeCovered`, `evidence.<metric>.minCases|minWeight|baselineMinCases|baselineMinWeight`, `metric.<metric>.k|minCandidate|maxDrop`, `replay.maxDiverged|maxErrors|maxRefusedWrites|maxStopped`, `cases.minPassing|noNewFailures`, `groupWeights.expertsOnly`, and `approval.recheck`, which an approval adds to a pin it supersedes, to say what changed since the check.

string

**passed**

required

boolean

**message**

required

A plain sentence: what was found against what the gate asks.

string

**value**

number | string

**threshold**

number | string

**caseIds**

The cases it names: for `cases.noNewFailures`, the ones that passed before and fail now (empty when none does). Absent when it could not tell, and on other checks.

Array\<string>

**approvalId**

The approval a `pending-approval` pin waits on (kept once decided).

string

**resolvedAt**

When a `pending-approval` pin reached its final state.

string format: date-time

**stoppedFollowingLatest**

A `pin`’s: `true` when it took a project that followed Latest off it, as its request asked (`stopFollowingLatest`) or as a promotion’s pin does. Absent otherwise.

boolean

**testRuns**

required

One per project whose running version the pin changes and that has active cases: a project pin has one, a workspace or org pin one per such project, a segment pin none. While the pin waits for approval, its runs are already started.

Array\<object>

A test run a pin checks a project with: the one the pin named, reused, or one it started, with correlation id `pin:<changeId>`.

object

**projectId**

required

string format: uuid

**testRunId**

required

string

**fixed**

required

The corrections a reused run made `fixed` at pin time. Empty when the runs were started: their outcome changes the statuses when each ends.

Array\<string>

**reopened**

required

The corrections a reused run reopened at pin time, as `fixed`’s.

Array\<string>

##### Example

```json
{
  "pin": {
    "scope": {
      "kind": "tenant"
    },
    "action": "pin",
    "requestedBy": {
      "kind": "user"
    },
    "status": "pinned"
  }
}
```

### 202

The gate passed; the pin waits for an approval (`pin.approvalId`). Its test runs are started, and `fixed` and `reopened` are empty.

application/json

A pin, and the test runs that check it.

object

**pin**

required

One change of a scope’s pinned version, kept for good: what was pinned before, what is after, who asked and why.

object

**id**

required

string format: uuid

**agentId**

required

string

**scope**

required

One of: discriminator: kind

- **object**

  The whole workspace.

  object

  **kind**

  required

  string

  Allowed values: tenant

- **object**

  An org’s projects.

  object

  **kind**

  required

  string

  Allowed values: org

  **orgId**

  required

  string format: uuid

- **object**

  One project.

  object

  **kind**

  required

  string

  Allowed values: project

  **projectId**

  required

  string format: uuid

- **object**

  A segment path within a project: it covers every run whose path starts with it.

  object

  **kind**

  required

  string

  Allowed values: segment

  **projectId**

  required

  string format: uuid

  **path**

  required

  Array\<object>

  \>= 1 items <= 8 items

  object

  **key**

  required

  Lowercase, like an identifier: `company`, `contact-role`.

  string

  /^\[a-z]\[a-z0-9\_-]{0,63}$/

  **value**

  required

  string

  \>= 1 characters <= 256 characters

**action**

required

string

Allowed values: pin unpin

**fromVersion**

required

The scope’s own pin before; null when it had none.

string | null

**toVersion**

required

The scope’s own pin after; null after an unpin.

string | null

**requestedBy**

required

object

**kind**

required

string

Allowed values: user service

**id**

required

string

**reason**

string

**testRunId**

The comparison behind the change.

string

**createdAt**

required

string format: date-time

**status**

A `pin` row’s state: it changes once, from `pending-approval` to its final state. Absent on unpin rows, and on pins made before gates (`pinned`).

string

Allowed values: pinned pending-approval refused superseded rejected expired

**releaseGate**

One of:

- **object**

  object

  **id**

  required

  string

  **version**

  required

  string

- **null**

  null

**checks**

Array\<object>

One of a gate’s checks, as the pin records it.

object

**name**

required

Stable: `comparison.required`, `comparison.status`, `comparison.freshness`, `comparison.testSet`, `sameContents`, `baselineIsPinned`, `scopeCovered`, `evidence.<metric>.minCases|minWeight|baselineMinCases|baselineMinWeight`, `metric.<metric>.k|minCandidate|maxDrop`, `replay.maxDiverged|maxErrors|maxRefusedWrites|maxStopped`, `cases.minPassing|noNewFailures`, `groupWeights.expertsOnly`, and `approval.recheck`, which an approval adds to a pin it supersedes, to say what changed since the check.

string

**passed**

required

boolean

**message**

required

A plain sentence: what was found against what the gate asks.

string

**value**

number | string

**threshold**

number | string

**caseIds**

The cases it names: for `cases.noNewFailures`, the ones that passed before and fail now (empty when none does). Absent when it could not tell, and on other checks.

Array\<string>

**approvalId**

The approval a `pending-approval` pin waits on (kept once decided).

string

**resolvedAt**

When a `pending-approval` pin reached its final state.

string format: date-time

**stoppedFollowingLatest**

A `pin`’s: `true` when it took a project that followed Latest off it, as its request asked (`stopFollowingLatest`) or as a promotion’s pin does. Absent otherwise.

boolean

**testRuns**

required

One per project whose running version the pin changes and that has active cases: a project pin has one, a workspace or org pin one per such project, a segment pin none. While the pin waits for approval, its runs are already started.

Array\<object>

A test run a pin checks a project with: the one the pin named, reused, or one it started, with correlation id `pin:<changeId>`.

object

**projectId**

required

string format: uuid

**testRunId**

required

string

**fixed**

required

The corrections a reused run made `fixed` at pin time. Empty when the runs were started: their outcome changes the statuses when each ends.

Array\<string>

**reopened**

required

The corrections a reused run reopened at pin time, as `fixed`’s.

Array\<string>

##### Example

```json
{
  "pin": {
    "scope": {
      "kind": "tenant"
    },
    "action": "pin",
    "requestedBy": {
      "kind": "user"
    },
    "status": "pinned"
  }
}
```

### 400

Malformed request body, or the test run isn’t a completed comparison: a cancelled or failed one is refused, even with the summary of the cases it got through. Or `reference-out-of-scope`: a project scope whose project doesn’t reach the agent (`details.issues`).

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"
  }
}
```

### 404

The version is not registered, or was unregistered; or the test run is 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

`pin-stops-latest`: the pin would take a project that follows Latest now off it, and the body doesn’t have `stopFollowingLatest: true`; `details.projects` lists each one, `{ projectId, version }`, with the version it runs now. Nothing is recorded. `nothing-runs-in-scope`: no version of the agent runs in the scope yet (no pin covers it, and it has no Latest), so there’s nothing to measure the version against; deploy or publish a version into the project first (into a later environment, promote a build that ships one). `project-suspended`: nothing runs there because the scope’s project is suspended. `pin-superseded`: the scope’s pinned version changed while the gate ran; check again. `release-gate-descendant-unpinned`: the change would also move a narrower scope a release gate applies to, which has no pinned version of its own and so follows this scope, without its gate; the message names each one: pin it at its current version first. `pin-scope-spans-environments`: the agent has a project in an environment order, and a workspace- or org-wide pin would act in every environment of the order at once; pin a project (or a segment of it) instead. Under `kindgi dev`, `pin-superseded` also when a pack file the version’s data blocks go into changed since the version was made, or the block left the pack; the message names the file. Or `registry-read-only`: under `kindgi dev`, a scope narrower than the whole workspace, since the pack’s files serve every scope.

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"
  }
}
```

### 422

`gate-failed`: the gate refused it. `details.checks` has every check; the refusal is recorded (`details.changeId`). With the gate’s `cases` rule, `details.passing` and `details.needs` come with `minPassing`, and `details.newlyFailing` (the cases that passed before and fail now) with `noNewFailures`.

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

`approvals-pending-limit`: the gate wants an approval, and the workspace’s approvals waiting for a decision are at its limit (set by the runtime’s operator). Nothing is recorded.

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"
  }
}
```

### 501

`pin-gate-unsupported`: a release gate applies, and the deployment can’t record a gated pin.

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/agents/{agentId}`: Fetch the highest version of an agent](https://docs.kindgi.com/v0.5.0/reference/api/operations/agentsget.md)
- [`GET /v1/agents/{agentId}/projects`: List the projects an agent runs in, and what runs in each](https://docs.kindgi.com/v0.5.0/reference/api/operations/agentsprojects.md)
- [`GET /v1/agents/{agentId}/versions`: List versions of an agent](https://docs.kindgi.com/v0.5.0/reference/api/operations/agentslistversions.md)
- [`GET /v1/agents/{agentId}/versions/{version}`: Fetch a specific agent version](https://docs.kindgi.com/v0.5.0/reference/api/operations/agentsgetversion.md)
- [Kindgi 0.4.0](https://docs.kindgi.com/v0.5.0/releases/0.4.0.md)
- [Error code `approvals-pending-limit`](https://docs.kindgi.com/v0.5.0/reference/errors.md#approvals-pending-limit)
- [Error code `gate-failed`](https://docs.kindgi.com/v0.5.0/reference/errors.md#gate-failed)
- [Error code `idempotency-store-unavailable`](https://docs.kindgi.com/v0.5.0/reference/errors.md#idempotency-store-unavailable)
- [Error code `nothing-runs-in-scope`](https://docs.kindgi.com/v0.5.0/reference/errors.md#nothing-runs-in-scope)
- [Error code `pin-gate-unsupported`](https://docs.kindgi.com/v0.5.0/reference/errors.md#pin-gate-unsupported)
- [Error code `pin-scope-spans-environments`](https://docs.kindgi.com/v0.5.0/reference/errors.md#pin-scope-spans-environments)
- [Error code `pin-stops-latest`](https://docs.kindgi.com/v0.5.0/reference/errors.md#pin-stops-latest)
- [Error code `pin-superseded`](https://docs.kindgi.com/v0.5.0/reference/errors.md#pin-superseded)
- [Error code `project-suspended`](https://docs.kindgi.com/v0.5.0/reference/errors.md#project-suspended)
- [Error code `reference-out-of-scope`](https://docs.kindgi.com/v0.5.0/reference/errors.md#reference-out-of-scope)
- [Error code `registry-read-only`](https://docs.kindgi.com/v0.5.0/reference/errors.md#registry-read-only)
- [Error code `release-gate-descendant-unpinned`](https://docs.kindgi.com/v0.5.0/reference/errors.md#release-gate-descendant-unpinned)
- [Error code `request-too-large`](https://docs.kindgi.com/v0.5.0/reference/errors.md#request-too-large)
- [Every error code](https://docs.kindgi.com/v0.5.0/reference/errors.md)
