# Customer management API

Read decisions and policy revisions, validate policy changes, and automate workspace operations.

Updated: 2026-09-22

Canonical: https://docs.kastra.ai/reference/management-api

## Authenticate and select a workspace

The base URL is `https://api.kastra.ai/api/v1`. Customer management uses a user JWT or an authorized operator/device credential, with current workspace membership and route-specific permissions. For automation, create a scoped operator token through the [CLI](https://docs.kastra.ai/reference/cli#ci-authentication) and store it as `KASTRA_TOKEN`. An environment runtime API key does not grant policy-authoring access.

Send `Authorization: Bearer ...` and `X-Organization-ID: WORKSPACE_UUID`. Use an environment UUID in `environment_id` or `X-Environment-ID` on routes supporting environment filters; policy authoring also accepts an environment name through `X-Environment`. Always set the intended environment on writes. Workspace and environment display names are not interchangeable with their UUIDs.

```sh
curl --fail-with-body --get 'https://api.kastra.ai/api/v1/environments' \
  -H "Authorization: Bearer $KASTRA_TOKEN" \
  -H "X-Organization-ID: $KASTRA_WORKSPACE_ID"
```

The response has `success: true` and a `data` array. Select the environment you intend to inspect and retain its `id`; use its `name` where a policy predicate or MCP argument requires a name.

## Decision history

`GET /api/v1/decisions` returns `data: []` for an empty result and a `pagination` object. `GET /api/v1/decisions/{decisionID}` reads one authorized decision.

```sh
curl --fail-with-body --get 'https://api.kastra.ai/api/v1/decisions' \
  -H "Authorization: Bearer $KASTRA_TOKEN" \
  -H "X-Organization-ID: $KASTRA_WORKSPACE_ID" \
  --data-urlencode "environment_id=$KASTRA_ENVIRONMENT_ID" \
  --data-urlencode 'decision=DENY' \
  --data-urlencode 'source=claude_code' \
  --data-urlencode 'limit=20'
```

| Query | Meaning |
| --- | --- |
| `environment_id` | Authorized environment UUID; omitted scope includes all authorized environments |
| `decision` | `ALLOW`, `DENY`, or `PENDING_REVIEW` |
| `source` | Recorded origin, such as `claude_code`, `codex_cli`, `mcp`, or `proxy` |
| `from`, `to` | RFC3339 time bounds |
| `model`, `provider`, `workload_type`, `jurisdiction` | Request context filters |
| `email` | Actor email filter; MCP calls this argument `actor_email` |
| `search` | Text across reason, model, workload type, and actor email |
| `attr.x-kastra-attr-tool` | Example per-attribute filter; supply the attribute's value |
| `shadow_mode` | `true` or `false` |
| `shadow_deny`, `degraded`, `content_scan` | Strict boolean filters for shadow denials, billing degradation, or scan findings |
| `effect` | Metered effect: `rate_limit` or `spend_cap` |
| `limit`, `cursor` | Page size and opaque continuation token |

Pass `pagination.next_cursor` unchanged to the next request and retain the original filters. Stop when it is empty. Treat `total_count` as a lower bound when `total_count_capped` is true. Do not interpret pending history as authorization; use the [checkpoint's effective decision](https://docs.kastra.ai/approvals/overview).

`GET /api/v1/decisions/export` accepts the shared context filters, `format=json` or `format=csv`, and `max_results` up to 10,000. Export requires the corresponding entitlement; list-only `search` and `cursor` are not export pagination. Narrow large time windows rather than assuming an export contains every matching record. [Evidence exports](https://docs.kastra.ai/evidence/exports).

## Policies

Policy reads require workspace membership. Policy writes require ADMIN or OWNER and applicable plan access. Paths below are relative to `/api/v1`.

| Method and path | Input / result |
| --- | --- |
| `GET /policies` | Optional environment scope, `limit` (default 20, maximum 100), `offset`; `data` array and `meta.total`, `meta.limit`, `meta.offset` |
| `GET /policies/active` | Explicit environment scope; reads its active policy |
| `GET /policies/{policyID}` | Reads a policy by UUID |
| `GET /policies/{policyID}/revisions` | Reads revision history |
| `GET /policies/{policyID}/revisions/{revisionNumber}` | Reads one revision |
| `GET /policies/{policyID}/revisions/{rev1}/diff/{rev2}` | Compares two revisions |
| `GET /policies/{policyID}/export` | Exports the policy |
| `POST /policies/validate` | Validates a candidate body; does not activate it |
| `POST /policies` | Creates a policy from `name`, `description`, `deny`, `required_attributes`, `default_decision` |
| `PUT /policies/{policyID}` | Updates editable fields and optional `change_note`; inspect the resulting revision |
| `POST /policies/{policyID}/activate` | Body: `{"revision_number":1}`; use the actual reviewed revision |
| `POST /policies/{policyID}/deactivate` | Deactivates the policy |
| `POST /policies/{policyID}/revisions/{revisionNumber}/rollback` | Rolls back to a selected revision |
| `POST /policies/{policyID}/promote` | `target_environment` (name), `activate_immediately` (boolean), optional `change_note` |
| `DELETE /policies/{policyID}` | Deletes the selected policy |

The API receives the policy body directly, without the `.kastra` file's `metadata` / `spec` wrapper. For example, save this as `candidate.json`:

```json
{
  "name": "docs-validation-example",
  "default_decision": "allow",
  "deny": [{
    "reason": "Block only the harmless documentation marker",
    "jurisdiction": "*",
    "model_prefix": "*",
    "effect": "deny",
    "conditions": [{
      "attribute": "x-kastra-attr-tool-input",
      "operator": "contains",
      "value": "kastra-docs-deny"
    }]
  }]
}
```

```sh
curl --fail-with-body 'https://api.kastra.ai/api/v1/policies/validate' \
  -H "Authorization: Bearer $KASTRA_TOKEN" \
  -H "X-Organization-ID: $KASTRA_WORKSPACE_ID" \
  -H 'X-Environment: docs-test' \
  -H 'Content-Type: application/json' \
  --data-binary @candidate.json
```

Create `docs-test` first or substitute an existing test environment. Check the returned validation result and warnings, not just HTTP success. A candidate, a saved revision, and an active revision are distinct states. Follow [policy as code](https://docs.kastra.ai/policies/policy-as-code) for a CLI workflow and [policy syntax](https://docs.kastra.ai/reference/policy-attributes) for operators and effects.

## Policy Builder

The [Build policies with AI guide](https://docs.kastra.ai/policies/ai-drafting) walks through conversation, tests and review. These console API routes require a user bearer session with current Admin or Owner membership. Send `X-Organization-ID: WORKSPACE_UUID` and explicitly select the environment with `X-Environment-ID: ENVIRONMENT_UUID`. Sessions are private to their author in that workspace and environment; responses use `Cache-Control: no-store`. Creating a session or sending messages requires AI authoring access. Committing requires policy-authoring access and the entitlements for the proposed effects.

Paths below are relative to `/api/v1/policies/builder`.

| Method and path | Input / result |
| --- | --- |
| `GET /sessions` | Lists the author's unexpired sessions in the selected environment |
| `POST /sessions` | `{}` starts an empty additive draft; optional `paths` selects scope. To edit the current base explicitly, supply `base_policy_id` and `base_revision` |
| `GET /sessions/{sessionID}` | Reads the current session snapshot |
| `PUT /sessions/{sessionID}` | Current `version`, complete `document`, and `paths`; optional `challenge_suites` selects independent challenges |
| `DELETE /sessions/{sessionID}` | Discards the session; leaves any committed policy intact |
| `POST /sessions/{sessionID}/messages` | Current `version`, a UUID `id`, and non-empty `content`; returns the session while generation continues |
| `GET /sessions/{sessionID}/events` | Authenticated server-sent events containing session snapshots |
| `POST /sessions/{sessionID}/tests` | Current `version` and `examples`; returns `session` and `verification` |
| `POST /sessions/{sessionID}/stop` | Stops the running turn; no version field is required |
| `POST /sessions/{sessionID}/rebase` | Current `version`; updates the base and invalidates previous verification |
| `POST /sessions/{sessionID}/commit` | Reviewed `version`, `draft_hash`, `suite_hash`, and explicit `publish` boolean; returns a commit receipt |

Scope values are `agent_prompt`, `agent_tool`, `proxy_prompt`, `proxy_output`, `mcp_local`, and `mcp_hosted`. Editing replaces the supplied document and paths; it is not a partial patch. Omitting `challenge_suites` preserves the selection; `[]` clears it. Request objects reject unknown fields.

Successful Builder responses are direct JSON, without a `success` / `data` wrapper: listing returns an array; create, read, edit, message, stop and rebase return a session; tests return `{ "session": ..., "verification": ... }`; deletion returns `{ "deleted": true }`. A commit receipt contains `policy_id`, `revision`, `published`, and `draft_hash`. Inspect the receipt to confirm activation. `publish: false` is available only when creating a policy in an environment without an existing one.

Use the latest returned session `version` for each versioned request. Retrying a message uses the same UUID and content; reusing its ID with different content conflicts. Open event streams with authenticated `fetch`: each `state` event contains a complete session, and its event ID is the session version. Reconnect the stream or fetch the session after a disconnect; reconnecting must not resend the message. A matching commit retry returns the existing receipt.

Invalid input returns 400, missing or expired drafts 404, stale versions or conflicting state 409, and unavailable Builder configuration 503. Keep the draft on a conflict, reload, and rerun tests before reviewing again. These routes are documented here; they are not yet included in the downloadable OpenAPI schema.

## Asynchronous AI authoring jobs

These administrator-gated asynchronous job APIs remain available alongside the conversational Builder:

| Operation | Start route | Poll route |
| --- | --- | --- |
| Draft one rule | `POST /api/v1/policies/draft-rule/jobs` | `GET /api/v1/policies/draft-rule/jobs/{id}` |
| Summarize change | `POST /api/v1/policies/draft-change-summary/jobs` | `GET /api/v1/policies/draft-change-summary/jobs/{id}` |
| Narrow a flagged rule | `POST /api/v1/decisions/{decisionID}/suggest-policy-fix/jobs` | `GET /api/v1/decisions/{decisionID}/suggest-policy-fix/jobs/{id}` |

Use a user session and the required AI authoring entitlement. The single-rule request requires a non-empty `description`; preserve unknown result fields in API clients. Read the asynchronous job status and nested result; an accepted job is not an installed policy. Older synchronous drafting routes are deprecated and remain bounded by the request timeout. Keep drafting failures visible and inspect the error before retrying.

## Audit and approvals

`GET /api/v1/audit` lists administrative events, with `environment_id`, `event_type`, `entity_type`, `entity_id`, `actor_id`, and RFC3339 `from` / `to` filters. It uses offset pagination: `limit` defaults to 50 (maximum 100), and `offset` defaults to zero. The response returns `pagination.limit` and `pagination.offset`; advance by the page size and stop on a short page.

Checkpoint lists use a different, opt-in cursor contract. See [approval pagination and resolution](https://docs.kastra.ai/approvals/overview), including the distinction between runtime readers and authorized human approvers.

## Handle errors explicitly

Native errors use `success: false`, a human-readable `error`, and a stable `code` where documented. Handle authentication, authorization, invalid input, conflicts, entitlements, throttling, and service failure separately. A decision query rejected with `422` / `invalid_request` can require a smaller time range or additional filters. Preserve unknown response fields for forward compatibility.

The [downloadable OpenAPI schema](https://docs.kastra.ai/openapi.json) includes the runtime contract and selected management reads. This guide covers additional policy operations; internal administration routes are not customer integration endpoints.
