# Evaluation API

Submit an action, decode ALLOW or DENY, and negotiate a supported HOLD response.

Updated: 2026-09-22

Canonical: https://docs.kastra.ai/reference/evaluate

## Endpoint and authentication

Use `POST https://api.kastra.ai/v1/evaluate` with a Kastra environment API key or supported Edge device credential. This is a cooperative API: your integration must consult it before the action and enforce the result. Kastra does not execute the submitted action for you.

## Send explicit context

Create a test `evaluate.json`:

```json
{
  "jurisdiction": "US",
  "model": "example-agent",
  "workload_type": "tool-call",
  "environment": "dev",
  "request_id": "documentation-example-001",
  "action": "inspect_repository",
  "attributes": {
    "x-kastra-attr-tool": "example_read_tool"
  }
}
```

Use real policy context and an existing environment. The example tool and model are illustrative identifiers, not built-in integrations.

`jurisdiction`, `model` and `workload_type` must be non-empty. `source`, when supplied, must be a supported decision source. A fingerprint cannot be combined with a non-empty prompt or attributes. Use the API key's bound environment; a device caller should select its environment explicitly.

```sh
curl -i https://api.kastra.ai/v1/evaluate \
  -H "Authorization: Bearer $KASTRA_API_KEY" \
  -H 'Content-Type: application/json' \
  -H 'Accept-Kastra-Hold: 1' \
  --data-binary @evaluate.json
```

## Decode every outcome

| Response | Required handling |
| --- | --- |
| `200` with ALLOW | Proceed only with the evaluated action |
| `403` with a decision envelope | Decode the reason and stop; a policy denial is not a transport failure |
| `202` HOLD | Retain the checkpoint and wait for an effective permission outcome |
| Opted-in `429` | Handle the negotiated rate-limit result |
| Other errors | Apply the integration's explicit error posture; never turn unknown output into permission |

`Accept-Kastra-Hold: 1` opts into the HOLD contract. Without it, a matching HOLD rule falls back to denial for compatibility. The server must also have the checkpoint subsystem enabled. An ALLOW/DENY envelope and a HOLD envelope are different wire shapes; parse the negotiated shape rather than assuming all responses have the same fields.

Normal `200`/`403` responses wrap the result in `{ "success": true, "data": { ... } }`. Inspect `data.decision`, `data.reason`, and the returned identifiers. A negotiated `202` also uses `success` and `data`, but its data contains `decision: "HOLD"`, `checkpoint_id`, expiry, timeout behavior, and connection information instead of an ordinary decision record. The current default normal-evaluation storage-failure response is `503`; see [failure behavior](https://docs.kastra.ai/reference/failure-behavior).

## Checkpoint lifecycle

Read `GET /v1/checkpoints/{id}` with an authorized credential and inspect status plus `effective_decision`. Human approval requires a current authorized membership. Device cancellation/heartbeat and human resolution are separate operations. [Approval guide](https://docs.kastra.ai/approvals/overview).

## Limits and retries

Discover effective byte limits with `GET /v1/limits`. The default prompt ceiling is 1 MiB; oversized input is denied, not silently treated as fully evaluated. Use an `Idempotency-Key` for a supported transport retry when appropriate, preserving the request. It is distinct from proxy HOLD operation identity. The current idempotency middleware can be bypassed for device-only context or a storage error; do not treat it as a universal exactly-once guarantee. Direct evaluation matches principal only when established through server-bound identity; an unverified body principal does not become a principal predicate.

Model output controls require a surface that supplies output context. Posting a prompt to this pre-inference endpoint does not make it a proxy output scanner.

## Display-only content preview

`POST /v1/content/preview` accepts an environment API key and only a JSON `content` string. The complete body must fit within 256 KiB. It returns `success: true` with `data.content` and `data.redaction_count`, using `Cache-Control: no-store`. Invalid input returns 400; an oversized body returns 413.

This stateless masking helper does not evaluate policy, call a model, or store the supplied content. Render its output as plain text; do not display the original as a fallback on failure. Its supported detectors do not guarantee anonymity of arbitrary prose.
