> For the complete documentation index, see [llms.txt](https://docs.maetra.io/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.maetra.io/govern-api/checkpoints.md).

# Checkpoints

A **checkpoint** asks for approval of a single agent action. You create one before the action runs; Maetra evaluates it against your active [policies](/govern-api/policies.md) and returns a decision — immediately for fast-path outcomes, or after a human responds.

### Lifecycle

```
create ─▶ evaluate ─┬─▶ approved / rejected / blocked   (terminal, signed)
                    └─▶ pending ─▶ (human decides) ─▶ approved / rejected
                                └─▶ (timeout)      ─▶ expired
```

| Status      | Meaning                                          |
| ----------- | ------------------------------------------------ |
| `pending`   | Awaiting a human decision. Poll for the outcome. |
| `approved`  | Authorised. A `decision_token` is issued.        |
| `rejected`  | A reviewer declined.                             |
| `blocked`   | A policy auto-blocked it (no human needed).      |
| `expired`   | No decision before the timeout.                  |
| `cancelled` | Cancelled before resolution.                     |

Proceed with the action **only** when `status` is `approved`.

### Create a checkpoint

`POST /v1/checkpoints` — requires scope `govern:checkpoints:write`.

Evaluated synchronously: a fast-path policy may return a terminal decision in the same response; otherwise you get a `pending` checkpoint to poll.

**Request body**

| Field              | Type      | Required | Description                                                  |
| ------------------ | --------- | -------- | ------------------------------------------------------------ |
| `action`           | string    | ✓        | The action name, e.g. `transfer_funds`.                      |
| `payload`          | object    |          | Structured details of the action.                            |
| `agent_name`       | string    |          | Human-readable caller (use when the agent isn't registered). |
| `agent_id`         | string    |          | Registered agent ID (see [Agents](/agents.md)).              |
| `context`          | string    |          | Free-text context for reviewers.                             |
| `reasoning`        | string    |          | The agent's reasoning.                                       |
| `autonomy_level`   | string    |          | Agent autonomy level, `L0`–`L5`.                             |
| `policy_ids`       | string\[] |          | Restrict evaluation to these policies.                       |
| `policy_group_ids` | string\[] |          | Restrict evaluation to these policy groups.                  |
| `timeout_seconds`  | integer   |          | Wall-clock ceiling for a human decision.                     |

{% tabs %}
{% tab title="cURL" %}

```bash
curl -X POST "https://api.maetra.io/v1/checkpoints" \
  -H "Authorization: Bearer $MAETRA_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
  "action": "delete_customer",
  "agent_name": "ops-agent",
  "payload": {
    "customer_id": "cus_41ab"
  },
  "reasoning": "GDPR erasure request #8821."
}'
```

{% endtab %}

{% tab title="JavaScript" %}

```javascript
const res = await fetch("https://api.maetra.io/v1/checkpoints", {
  method: "POST",
  headers: {
    Authorization: `Bearer ${process.env.MAETRA_API_KEY}`,
    "Content-Type": "application/json",
  },
  body: JSON.stringify({
      "action": "delete_customer",
      "agent_name": "ops-agent",
      "payload": {
          "customer_id": "cus_41ab"
      },
      "reasoning": "GDPR erasure request #8821."
  }),
});
if (!res.ok) throw new Error(`Maetra API ${res.status}`);
const data = await res.json();
console.log(data);
```

{% endtab %}

{% tab title="TypeScript" %}

```typescript
interface CheckpointDecision {
  checkpoint_id: string;
  status: string;
  decision_token: string | null;
  expires_at: string | null;
  evals: unknown[];
}

const res = await fetch("https://api.maetra.io/v1/checkpoints", {
  method: "POST",
  headers: {
    Authorization: `Bearer ${process.env.MAETRA_API_KEY}`,
    "Content-Type": "application/json",
  },
  body: JSON.stringify({
      "action": "delete_customer",
      "agent_name": "ops-agent",
      "payload": {
          "customer_id": "cus_41ab"
      },
      "reasoning": "GDPR erasure request #8821."
  }),
});
if (!res.ok) throw new Error(`Maetra API ${res.status}`);
const data = (await res.json()) as CheckpointDecision;
```

{% endtab %}

{% tab title="Python" %}

```python
import os, requests

res = requests.post(
    "https://api.maetra.io/v1/checkpoints",
    headers={"Authorization": f"Bearer {os.environ['MAETRA_API_KEY']}"},
    json={
        "action": "delete_customer",
        "agent_name": "ops-agent",
        "payload": {
            "customer_id": "cus_41ab"
        },
        "reasoning": "GDPR erasure request #8821."
    },
)
res.raise_for_status()
print(res.json())
```

{% endtab %}

{% tab title="Rust" %}

```rust
use serde_json::json;

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    let key = std::env::var("MAETRA_API_KEY")?;
    let client = reqwest::Client::new();
    let res = client
        .post("https://api.maetra.io/v1/checkpoints")
        .bearer_auth(&key)
        .json(&json!({
            "action": "delete_customer",
            "agent_name": "ops-agent",
            "payload": {
                "customer_id": "cus_41ab"
            },
            "reasoning": "GDPR erasure request #8821."
        }))
        .send()
        .await?;
    let data: serde_json::Value = res.json().await?;
    println!("{data:#}");
    Ok(())
}
```

{% endtab %}

{% tab title="C++" %}

```cpp
#include <curl/curl.h>
#include <cstdlib>
#include <string>

int main() {
    CURL* curl = curl_easy_init();
    std::string auth = "Authorization: Bearer " + std::string(std::getenv("MAETRA_API_KEY"));
    struct curl_slist* headers = nullptr;
    headers = curl_slist_append(headers, auth.c_str());
    headers = curl_slist_append(headers, "Content-Type: application/json");
    curl_easy_setopt(curl, CURLOPT_URL, "https://api.maetra.io/v1/checkpoints");
    curl_easy_setopt(curl, CURLOPT_CUSTOMREQUEST, "POST");
    curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headers);
    curl_easy_setopt(curl, CURLOPT_POSTFIELDS, R"({  "action": "delete_customer",  "agent_name": "ops-agent",  "payload": {    "customer_id": "cus_41ab"  },  "reasoning": "GDPR erasure request #8821."})");
    curl_easy_perform(curl);   // response is written to stdout by default
    curl_slist_free_all(headers);
    curl_easy_cleanup(curl);
    return 0;
}
```

{% endtab %}

{% tab title="Java" %}

```java
import java.net.URI;
import java.net.http.*;

var client = HttpClient.newHttpClient();
var request = HttpRequest.newBuilder()
    .uri(URI.create("https://api.maetra.io/v1/checkpoints"))
    .header("Authorization", "Bearer " + System.getenv("MAETRA_API_KEY"))
    .header("Content-Type", "application/json")
    .method("POST", HttpRequest.BodyPublishers.ofString("""
{
  "action": "delete_customer",
  "agent_name": "ops-agent",
  "payload": {
    "customer_id": "cus_41ab"
  },
  "reasoning": "GDPR erasure request #8821."
}"""))
    .build();
var response = client.send(request, HttpResponse.BodyHandlers.ofString());
System.out.println(response.body());
```

{% endtab %}
{% endtabs %}

#### With a registered agent

The call above uses `agent_name` because the agent isn't registered — the common case. If the agent **is** [registered](/agents.md), pass its `agent_id` instead (or alongside `agent_name`) so the checkpoint is attributed to it:

```json
{
  "action": "delete_customer",
  "agent_id": "agt_5Ab2",
  "payload": { "customer_id": "cus_41ab" },
  "reasoning": "GDPR erasure request #8821."
}
```

#### Response

```json
{
  "checkpoint_id": "cp_7Yh2Qa",
  "agent_id": null,
  "agent_name": "ops-agent",
  "status": "pending",
  "reason": null,
  "decision_token": null,
  "expires_at": "2026-07-07T12:05:00.000Z",
  "evals": [
    { "policy_name": "Destructive actions", "status": "pending", "quorum_required": 2, "quorum_met": 0, "pool_size": 4 }
  ]
}
```

| Field                     | Type           | Description                                                                                     |
| ------------------------- | -------------- | ----------------------------------------------------------------------------------------------- |
| `checkpoint_id`           | string         | The checkpoint's ID.                                                                            |
| `agent_id` / `agent_name` | string \| null | The caller identity you supplied.                                                               |
| `status`                  | enum           | `pending`, `approved`, `rejected`, `expired`, `blocked`, `cancelled`.                           |
| `reason`                  | string \| null | Human or policy reason, when available.                                                         |
| `decision_token`          | string \| null | Signed JWT proving a terminal decision — see [Decision tokens](/govern-api/decision-tokens.md). |
| `expires_at`              | string \| null | ISO 8601 expiry.                                                                                |
| `evals`                   | array          | Per-policy evaluation detail (below).                                                           |

**Eval object:** `policy_id`, `policy_name`, `applicability`, `status`, `quorum_required`, `quorum_met`, `pool_size`.

### Get a checkpoint (cold poll)

`GET /v1/checkpoints/{id}` — scope `govern:checkpoints:read`. Returns the current state without waiting. Keep ≥1 second between polls of the same checkpoint; prefer the long-poll below.

{% tabs %}
{% tab title="cURL" %}

```bash
curl "https://api.maetra.io/v1/checkpoints/cp_7Yh2Qa" \
  -H "Authorization: Bearer $MAETRA_API_KEY"
```

{% endtab %}

{% tab title="JavaScript" %}

```javascript
const res = await fetch("https://api.maetra.io/v1/checkpoints/cp_7Yh2Qa", {
  method: "GET",
  headers: {
    Authorization: `Bearer ${process.env.MAETRA_API_KEY}`,
  },
});
if (!res.ok) throw new Error(`Maetra API ${res.status}`);
const data = await res.json();
console.log(data);
```

{% endtab %}

{% tab title="TypeScript" %}

```typescript
const res = await fetch("https://api.maetra.io/v1/checkpoints/cp_7Yh2Qa", {
  method: "GET",
  headers: {
    Authorization: `Bearer ${process.env.MAETRA_API_KEY}`,
  },
});
if (!res.ok) throw new Error(`Maetra API ${res.status}`);
const data = (await res.json());
```

{% endtab %}

{% tab title="Python" %}

```python
import os, requests

res = requests.get(
    "https://api.maetra.io/v1/checkpoints/cp_7Yh2Qa",
    headers={"Authorization": f"Bearer {os.environ['MAETRA_API_KEY']}"},
)
res.raise_for_status()
print(res.json())
```

{% endtab %}

{% tab title="Rust" %}

```rust
use serde_json::json;

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    let key = std::env::var("MAETRA_API_KEY")?;
    let client = reqwest::Client::new();
    let res = client
        .get("https://api.maetra.io/v1/checkpoints/cp_7Yh2Qa")
        .bearer_auth(&key)
        .send()
        .await?;
    let data: serde_json::Value = res.json().await?;
    println!("{data:#}");
    Ok(())
}
```

{% endtab %}

{% tab title="C++" %}

```cpp
#include <curl/curl.h>
#include <cstdlib>
#include <string>

int main() {
    CURL* curl = curl_easy_init();
    std::string auth = "Authorization: Bearer " + std::string(std::getenv("MAETRA_API_KEY"));
    struct curl_slist* headers = nullptr;
    headers = curl_slist_append(headers, auth.c_str());
    curl_easy_setopt(curl, CURLOPT_URL, "https://api.maetra.io/v1/checkpoints/cp_7Yh2Qa");
    curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headers);
    curl_easy_perform(curl);   // response is written to stdout by default
    curl_slist_free_all(headers);
    curl_easy_cleanup(curl);
    return 0;
}
```

{% endtab %}

{% tab title="Java" %}

```java
import java.net.URI;
import java.net.http.*;

var client = HttpClient.newHttpClient();
var request = HttpRequest.newBuilder()
    .uri(URI.create("https://api.maetra.io/v1/checkpoints/cp_7Yh2Qa"))
    .header("Authorization", "Bearer " + System.getenv("MAETRA_API_KEY"))
    .method("GET", HttpRequest.BodyPublishers.noBody())
    .build();
var response = client.send(request, HttpResponse.BodyHandlers.ofString());
System.out.println(response.body());
```

{% endtab %}
{% endtabs %}

### Wait for a decision (long-poll)

`GET /v1/checkpoints/{id}/wait` — scope `govern:checkpoints:read`. Holds the connection open until the checkpoint changes state or the hold elapses. The efficient way to wait for a human.

* Query `timeout`: hold seconds, `1`–`55` (default `50`).
* `200` — changed; body is the new state. `202` — no change; reconnect.

{% tabs %}
{% tab title="cURL" %}

```bash
curl "https://api.maetra.io/v1/checkpoints/cp_7Yh2Qa/wait?timeout=50" \
  -H "Authorization: Bearer $MAETRA_API_KEY"
```

{% endtab %}

{% tab title="JavaScript" %}

```javascript
const res = await fetch("https://api.maetra.io/v1/checkpoints/cp_7Yh2Qa/wait?timeout=50", {
  method: "GET",
  headers: {
    Authorization: `Bearer ${process.env.MAETRA_API_KEY}`,
  },
});
if (!res.ok) throw new Error(`Maetra API ${res.status}`);
const data = await res.json();
console.log(data);
```

{% endtab %}

{% tab title="TypeScript" %}

```typescript
const res = await fetch("https://api.maetra.io/v1/checkpoints/cp_7Yh2Qa/wait?timeout=50", {
  method: "GET",
  headers: {
    Authorization: `Bearer ${process.env.MAETRA_API_KEY}`,
  },
});
if (!res.ok) throw new Error(`Maetra API ${res.status}`);
const data = (await res.json());
```

{% endtab %}

{% tab title="Python" %}

```python
import os, requests

res = requests.get(
    "https://api.maetra.io/v1/checkpoints/cp_7Yh2Qa/wait?timeout=50",
    headers={"Authorization": f"Bearer {os.environ['MAETRA_API_KEY']}"},
)
res.raise_for_status()
print(res.json())
```

{% endtab %}

{% tab title="Rust" %}

```rust
use serde_json::json;

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    let key = std::env::var("MAETRA_API_KEY")?;
    let client = reqwest::Client::new();
    let res = client
        .get("https://api.maetra.io/v1/checkpoints/cp_7Yh2Qa/wait?timeout=50")
        .bearer_auth(&key)
        .send()
        .await?;
    let data: serde_json::Value = res.json().await?;
    println!("{data:#}");
    Ok(())
}
```

{% endtab %}

{% tab title="C++" %}

```cpp
#include <curl/curl.h>
#include <cstdlib>
#include <string>

int main() {
    CURL* curl = curl_easy_init();
    std::string auth = "Authorization: Bearer " + std::string(std::getenv("MAETRA_API_KEY"));
    struct curl_slist* headers = nullptr;
    headers = curl_slist_append(headers, auth.c_str());
    curl_easy_setopt(curl, CURLOPT_URL, "https://api.maetra.io/v1/checkpoints/cp_7Yh2Qa/wait?timeout=50");
    curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headers);
    curl_easy_perform(curl);   // response is written to stdout by default
    curl_slist_free_all(headers);
    curl_easy_cleanup(curl);
    return 0;
}
```

{% endtab %}

{% tab title="Java" %}

```java
import java.net.URI;
import java.net.http.*;

var client = HttpClient.newHttpClient();
var request = HttpRequest.newBuilder()
    .uri(URI.create("https://api.maetra.io/v1/checkpoints/cp_7Yh2Qa/wait?timeout=50"))
    .header("Authorization", "Bearer " + System.getenv("MAETRA_API_KEY"))
    .method("GET", HttpRequest.BodyPublishers.noBody())
    .build();
var response = client.send(request, HttpResponse.BodyHandlers.ofString());
System.out.println(response.body());
```

{% endtab %}
{% endtabs %}

### Recommended flow

1. `POST /v1/checkpoints`.
2. If `status` is already terminal, act on it (and verify the token).
3. If `pending`, long-poll `/wait` until terminal.
4. On `approved`, [verify the decision token](/govern-api/decision-tokens.md), then act. On anything else, don't.
