> ## Documentation Index
> Fetch the complete documentation index at: https://docs.kontext.security/llms.txt
> Use this file to discover all available pages before exploring further.

# Policies API

> Add, enforce and remove policies, read what they block, and manage policy versions from a script.

export const EtagSequence = () => <div className="k-fig-wrap">
    <svg className="k-fig" viewBox="0 0 640 424" width="100%" style={{
  maxWidth: 640
}} role="img" aria-label="Your automation reads the policy and its ETag. Someone else saves a change. The API rejects the PUT with the old If-Match with 412. Read the latest policy and reconcile your edit before retrying with its ETag.">
      <defs>
        <marker id="etag-arrow" viewBox="0 0 10 10" refX="8" refY="5" markerWidth="7" markerHeight="7" orient="auto-start-reverse">
          <path className="arrowhead" d="M 1 1 L 8 5 L 1 9" />
        </marker>
      </defs>
      <text className="t-strong" x="120" y="22" textAnchor="middle">Your automation</text>
      <path className="lifeline" d="M 120 34 L 120 410" />
      <text className="t-strong" x="520" y="22" textAnchor="middle">Policy API</text>
      <path className="lifeline" d="M 520 34 L 520 410" />
      <path className="line" d="M 120 66 L 520 66" markerEnd="url(#etag-arrow)" />
      <text className="t-small mono" x="320.0" y="59" textAnchor="middle">GET /policy</text>
      <path className="line-dashed" d="M 520 104 L 120 104" markerEnd="url(#etag-arrow)" />
      <text className="t-small mono" x="320.0" y="97" textAnchor="middle">200 · ETag: "A"</text>
      <rect className="box-warn" x="200" y="130" width="240" height="30" rx="6" />
      <text className="t-small" x="320" y="149" textAnchor="middle">Someone else saves a change</text>
      <path className="line" d="M 120 206 L 520 206" markerEnd="url(#etag-arrow)" />
      <text className="t-small mono" x="320.0" y="199" textAnchor="middle">PUT /policy · If-Match: "A"</text>
      <path className="line-dashed" d="M 520 244 L 120 244" markerEnd="url(#etag-arrow)" />
      <text className="t-small mono" x="320.0" y="237" textAnchor="middle">412 Precondition Failed</text>
      <path className="line" d="M 120 292 L 520 292" markerEnd="url(#etag-arrow)" />
      <text className="t-small mono" x="320.0" y="285" textAnchor="middle">GET /policy</text>
      <path className="line-dashed" d="M 520 330 L 120 330" markerEnd="url(#etag-arrow)" />
      <text className="t-small mono" x="320.0" y="323" textAnchor="middle">200 · ETag: "B"</text>
      <text className="t-small" x="320" y="356" textAnchor="middle">Reconcile your edit with the latest policy before retrying.</text>
      <path className="line" d="M 120 394 L 520 394" markerEnd="url(#etag-arrow)" />
      <text className="t-small mono" x="320.0" y="387" textAnchor="middle">PUT /policy · If-Match: "B"</text>
    </svg>
  </div>;

Use `https://api.kontext.security/api/v1` as the base URL. Kontext requires an owner or admin caller with the route's scope. See [API overview](/api/overview) to get a token. To let an agent manage policies, create a service account or connect the agent under **Settings → Agent access** with the **Policy author** preset. It grants `management:policy:read`, `management:policy:write` and `management:logs:read`.

## Routes

| Method | Path                                 | Scope                     | Purpose                                                                              |
| ------ | ------------------------------------ | ------------------------- | ------------------------------------------------------------------------------------ |
| POST   | `/policy/actions`                    | `management:policy:write` | Add, enforce, stop or delete one policy.                                             |
| GET    | `/policy/deployment`                 | `management:policy:read`  | Read the enforced version, the observing version, the mode, and the deployment ETag. |
| PUT    | `/policy/deployment`                 | `management:policy:write` | Replace the deployment.                                                              |
| GET    | `/policy`                            | `management:policy:read`  | Read the current policy set and its ETag.                                            |
| PUT    | `/policy`                            | `management:policy:write` | Validate and save a complete policy set as a new version.                            |
| POST   | `/policy/validations`                | `management:policy:write` | Validate source without saving it.                                                   |
| GET    | `/policy/versions`                   | `management:policy:read`  | List the newest 50 saved versions.                                                   |
| GET    | `/policy/versions/{policyVersionId}` | `management:policy:read`  | Read one version with its validation.                                                |
| GET    | `/policy/rule-templates`             | `management:policy:read`  | Read each catalogue preset's Cedar, coverage and known gaps.                         |
| POST   | `/policy/rule-template-drafts`       | `management:policy:write` | Compose a policy set with one preset added or removed, without saving it.            |
| POST   | `/policy/replay`                     | `management:policy:read`  | Replay a version against recorded calls.                                             |
| GET    | `/policy/blocked`                    | `management:policy:read`  | Read what the enforced policies blocked in a window.                                 |

## Change one policy

The dashboard and `kontext-admin` agent skill use `POST /policy/actions` for the actions on **Policies**. First read the deployment and its response headers:

```bash theme={"system"}
curl --fail-with-body --include \
  'https://api.kontext.security/api/v1/policy/deployment' \
  --header "Authorization: Bearer $KONTEXT_ACCESS_TOKEN"
```

Copy the complete `ETag` header value, including its double quotes, into `DEPLOYMENT_ETAG`. Send it in `If-Match` for the action:

```bash theme={"system"}
curl --fail-with-body --include \
  'https://api.kontext.security/api/v1/policy/actions' \
  --header "Authorization: Bearer $KONTEXT_ACCESS_TOKEN" \
  --header "If-Match: $DEPLOYMENT_ETAG" \
  --header 'Content-Type: application/json' \
  --data '{"action": "add", "templateId": "block-github-force-push"}'
```

| `action`  | Other fields                                                                                                | Effect                                                                               |
| --------- | ----------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------ |
| `add`     | Send a preset ID in `templateId` or one Cedar policy with an `@id` in `cedar`. Optionally include `scopes`. | Adds the policy to the **Observing** list.                                           |
| `enforce` | `policyId`                                                                                                  | Moves the policy to the **Enforced** list. Switches an Observe workspace to Enforce. |
| `stop`    | `policyId`                                                                                                  | Stops enforcing the policy and keeps a copy in the **Observing** list.               |
| `delete`  | Send `policyId` and `where`, either `observing` or `enforced`.                                              | Removes the policy from that list.                                                   |

Set `scopes` to a list of `{ "endpointId": "<endpoint ID>", "agentId": "openai-codex" }` objects. Omit `endpointId` to cover every endpoint or `agentId` to cover both agents. You can scope custom Cedar policies only if their IDs start with `custom:`. Kontext saves actions in paused workspaces without resuming them.

The response includes the updated deployment, a plain-language `message`, `changedPolicyIds`, `validation`, and a new ETag for your next action. A missing `If-Match` returns 428. A stale value returns 412. For 412, read the deployment again and review the intervening change. Rebuild the intended action against the current deployment, then retry with its ETag. Adding an existing policy or enforcing one already in **Enforced** returns 409.

## Read what policies do

Use `GET /policy/blocked?days=7` to read each enforced policy's blocked count and up to 20 of its newest blocked calls. Set `days` to 7, 14 or 30. Kontext sets `truncated` to true for windows with more than 2,000 Deny decisions. Counts then cover only the newest 2,000.

Send `enforcingVersionId`, `observingVersionId` and `days` to `POST /policy/replay`. Replay checks the newest 2,000 recorded calls in the window and returns how many each policy would newly block or allow. The `needsCapture` count covers calls that need full tool input for assessment. Each workspace runs one replay at a time. The API returns 409 when too many are waiting.

## Save a policy set

Use these routes to manage the complete Cedar source.

<Frame caption="The API rejects a stale If-Match with 412 to preserve the other edit.">
  <EtagSequence />
</Frame>

Send the complete source to `PUT /policy` as `{"policyText": "..."}`, up to 1,048,576 UTF-8 bytes. Use `If-Match` with the ETag from your last `GET /policy`, or `If-None-Match: *` for the first set. A missing precondition returns 428. A stale precondition returns 412. The response body includes validation results and diagnostics. Send the same body to `POST /policy/validations` to validate without saving.

Use the optional `origin` field to name the action in [History](/policies/history), for example `{"kind": "enforced", "subject": "Block GitHub force pushes"}`. Set `kind` to `added`, `enforced`, `stopped`, `edited` or `deleted`. Set `subject` to at most 200 characters. Without `origin`, History uses a title that describes the changes.

Saving creates a version unless an API save without `origin` reuses identical source. Endpoints use it only after you select it in the deployment.

## Replace the deployment

<Warning>
  Use `PUT /policy/deployment` to set the mode for the whole workspace. With `"rolloutMode": "enforce"`, Kontext blocks calls with every policy in `policyVersionId`. Use `POST /policy/actions` to change one policy.
</Warning>

Send the deployment ETag in `If-Match` and this body:

```json theme={"system"}
{
  "policyVersionId": "<saved-version-uuid>",
  "observingVersionId": null,
  "rolloutMode": "enforce"
}
```

Set `rolloutMode` to `observe`, `enforce` or `disabled`. Kontext pauses the workspace in `disabled` mode. Only this mode accepts a null `policyVersionId`. The dashboard replays `observingVersionId` alongside the enforced version. Endpoints do not receive that version. Omit it to keep the current observing version or set it to null to clear it. Endpoints apply changes on their next check-in. Read [rollout and failure behavior](/policies/rollout) before you choose `enforce`.
