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

# Proof API

> Decision evidence API — record, approve, query, and export tamper-proof decision records

# Proof API

Base URL: `https://cronozen.com/api/v1`

## Authentication

The Proof API uses **API Key** authentication, not JWT tokens. Include your key in the `Authorization` header:

```bash theme={null}
Authorization: Bearer czk_live_abc123...
```

### API Key Scopes

Each API key has one or more scopes that control access:

| Scope         | Permissions                                |
| ------------- | ------------------------------------------ |
| `proof:read`  | Query events, get evidence, export reports |
| `proof:write` | Record events, add approvals               |

A key with `proof:read` cannot record or approve decisions. A key with `proof:write` can do everything.

```bash theme={null}
# Read-only key — can query and export, but cannot record
curl https://cronozen.com/api/v1/decision-events \
  -H "Authorization: Bearer czk_live_readonly_..."

# Write key — full access
curl -X POST https://cronozen.com/api/v1/decision-events \
  -H "Authorization: Bearer czk_live_full_..." \
  -H "Content-Type: application/json" \
  -d '{ ... }'
```

<Info>
  API keys are hashed with SHA-256 before storage. The plaintext key is only shown once at creation time. Treat it like a password.
</Info>

<Warning>
  Proof API keys are separate from JWT tokens used for the rest of the Cronozen platform. A JWT token will not authenticate against the Proof API, and vice versa.
</Warning>

***

## Endpoints

### POST /v1/decision-events

Record a new decision event.

**Scope required**: `proof:write`

**Request:**

```json theme={null}
{
  "type": "agent_execution",
  "actor": {
    "id": "support_agent",
    "type": "ai_agent",
    "name": "Support Agent"
  },
  "action": {
    "type": "refund_approved",
    "description": "Auto-approved refund based on policy threshold",
    "input": {
      "orderId": "ORD-1234",
      "amount": 45000
    },
    "output": {
      "refundId": "REF-5678"
    }
  },
  "aiContext": {
    "model": "gpt-4",
    "provider": "openai",
    "confidence": 0.87,
    "reasoning": "Amount within auto-approval threshold"
  },
  "tags": ["refund", "support", "auto-approved"],
  "idempotencyKey": "refund-ORD-1234-20260312"
}
```

| Field                  | Type      | Required | Description                                                                                                                                                        |
| ---------------------- | --------- | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `type`                 | string    | Yes      | Event type: `agent_execution` \| `workflow_step` \| `human_approval` \| `ai_recommendation` \| `automated_action` \| `policy_decision` \| `escalation` \| `custom` |
| `actor`                | object    | Yes      | Who/what made the decision                                                                                                                                         |
| `actor.id`             | string    | Yes      | Actor identifier                                                                                                                                                   |
| `actor.type`           | string    | Yes      | `human` \| `ai_agent` \| `system` \| `service`                                                                                                                     |
| `actor.name`           | string    | No       | Display name                                                                                                                                                       |
| `action`               | object    | Yes      | What action was taken                                                                                                                                              |
| `action.type`          | string    | Yes      | Action type identifier                                                                                                                                             |
| `action.description`   | string    | No       | Human-readable description                                                                                                                                         |
| `action.input`         | object    | No       | Action input data (any JSON)                                                                                                                                       |
| `action.output`        | object    | No       | Action output data (any JSON)                                                                                                                                      |
| `aiContext`            | object    | No       | AI involvement details                                                                                                                                             |
| `aiContext.model`      | string    | No       | Model name (e.g., `gpt-4`, `claude-sonnet-4-5-20250514`)                                                                                                           |
| `aiContext.provider`   | string    | No       | AI provider (e.g., `openai`, `anthropic`)                                                                                                                          |
| `aiContext.confidence` | number    | No       | Confidence score (0–1)                                                                                                                                             |
| `aiContext.reasoning`  | string    | No       | Decision reasoning                                                                                                                                                 |
| `tags`                 | string\[] | No       | Filterable tags for categorization                                                                                                                                 |
| `idempotencyKey`       | string    | No       | Prevents duplicate recording (see below)                                                                                                                           |

**Response (201):**

```json theme={null}
{
  "data": {
    "id": "cmmnhiobs0002bfi9mlu8eof4",
    "decisionId": "refund-ORD-1234-20260312",
    "type": "agent_execution",
    "status": "recorded",
    "actor": {
      "id": "support_agent",
      "type": "ai_agent",
      "name": "Support Agent"
    },
    "action": {
      "type": "refund_approved",
      "input": { "orderId": "ORD-1234", "amount": 45000 }
    },
    "occurredAt": "2026-03-12T13:00:00.000Z",
    "tags": ["refund", "support", "auto-approved"],
    "evidence": {
      "id": "evi_x1y2z3",
      "status": "pending",
      "chainHash": null,
      "chainIndex": null
    },
    "createdAt": "2026-03-12T13:00:00.000Z",
    "updatedAt": "2026-03-12T13:00:00.000Z"
  }
}
```

#### Idempotency

Pass an `idempotencyKey` to prevent duplicate events from retries or network issues. If the same key is sent again, the original event is returned (200) instead of creating a duplicate.

```bash theme={null}
# First call — creates event
curl -X POST .../v1/decision-events \
  -d '{ "idempotencyKey": "settle-STL-001", ... }'
# → 201 Created

# Retry with same key — returns original
curl -X POST .../v1/decision-events \
  -d '{ "idempotencyKey": "settle-STL-001", ... }'
# → 200 OK (same event, no duplicate)
```

<Info>
  Use idempotency keys for any event that might be retried — settlement webhooks, queue consumers, or cron-triggered recordings.
</Info>

***

### GET /v1/decision-events

List decision events with filters.

**Scope required**: `proof:read`

**Query parameters:**

| Parameter | Type   | Description                                        |
| --------- | ------ | -------------------------------------------------- |
| `type`    | string | Filter by event type (e.g., `agent_execution`)     |
| `status`  | string | `recorded` \| `sealed` \| `approved` \| `rejected` |
| `tag`     | string | Filter by a single tag                             |
| `limit`   | number | Max results, 1–100 (default: 20)                   |
| `offset`  | number | Pagination offset (default: 0)                     |

**Example:**

```bash theme={null}
# Events tagged with "settlement", sealed only
curl "https://cronozen.com/api/v1/decision-events?tag=settlement&status=sealed&limit=50" \
  -H "Authorization: Bearer czk_live_..."
```

**Response (200):**

```json theme={null}
{
  "data": [
    {
      "id": "cmmnhiobs0002bfi9mlu8eof4",
      "decisionId": "settle-STL-001",
      "type": "automated_action",
      "status": "sealed",
      "actor": { "id": "finance_bot", "type": "system" },
      "action": { "type": "settlement_created" },
      "tags": ["settlement", "finance"],
      "createdAt": "2026-03-12T13:00:00.000Z",
      "updatedAt": "2026-03-12T13:05:00.000Z"
    }
  ],
  "pagination": {
    "total": 142,
    "limit": 50,
    "offset": 0,
    "hasMore": true
  }
}
```

***

### POST /v1/decision-events/{id}/approvals

Add human approval and seal the event with SHA-256.

**Scope required**: `proof:write`

**Request:**

```json theme={null}
{
  "approver": {
    "id": "team_lead",
    "type": "human",
    "name": "Kim"
  },
  "result": "approved",
  "reason": "Verified against refund policy v2.1"
}
```

| Field           | Type   | Required | Description               |
| --------------- | ------ | -------- | ------------------------- |
| `approver`      | object | Yes      | Who approved the decision |
| `approver.id`   | string | Yes      | Approver identifier       |
| `approver.type` | string | Yes      | `human` \| `system`       |
| `approver.name` | string | No       | Display name              |
| `result`        | string | Yes      | `approved` \| `rejected`  |
| `reason`        | string | No       | Reason for the decision   |

**Response (200):**

```json theme={null}
{
  "data": {
    "approvalId": "apr_m1n2o3",
    "decisionId": "refund-ORD-1234-20260312",
    "approver": {
      "id": "team_lead",
      "type": "human",
      "name": "Kim"
    },
    "result": "approved",
    "reason": "Verified against refund policy v2.1",
    "evidenceLevel": "AUDIT_READY",
    "sealedHash": "sha256:9943798c6313e9dd2cffa71686176d4125a65777...",
    "sealedAt": "2026-03-12T13:05:00.000Z",
    "createdAt": "2026-03-12T13:05:00.000Z"
  }
}
```

**Error — Already sealed (409):**

```json theme={null}
{
  "error": {
    "code": "CONFLICT",
    "message": "Event is already sealed. Sealed events cannot be modified."
  }
}
```

<Warning>
  Once an event is sealed, it is **immutable**. Attempting to approve an already-sealed event returns `409 Conflict`. This is by design — sealed events are part of the hash chain and cannot be altered without breaking chain integrity.
</Warning>

***

### GET /v1/evidence/{id}

Retrieve sealed evidence with full hash chain verification data.

**Scope required**: `proof:read`

**Response (200):**

```json theme={null}
{
  "data": {
    "id": "evi_x1y2z3",
    "decisionId": "refund-ORD-1234-20260312",
    "status": "sealed",
    "evidenceLevel": "AUDIT_READY",
    "event": {
      "type": "agent_execution",
      "actor": { "id": "support_agent", "type": "ai_agent" },
      "action": {
        "type": "refund_approved",
        "input": { "orderId": "ORD-1234", "amount": 45000 }
      },
      "occurredAt": "2026-03-12T13:00:00.000Z",
      "aiContext": {
        "model": "gpt-4",
        "confidence": 0.87
      }
    },
    "approval": {
      "approver": { "id": "team_lead", "type": "human", "name": "Kim" },
      "result": "approved",
      "reason": "Verified against refund policy v2.1",
      "approvedAt": "2026-03-12T13:05:00.000Z"
    },
    "chain": {
      "hash": "sha256:9943798c6313e9dd2cffa71686176d4125a65777...",
      "previousHash": "sha256:7f3a8b2c...",
      "index": 42,
      "domain": "proof"
    },
    "sealedAt": "2026-03-12T13:05:00.000Z",
    "createdAt": "2026-03-12T13:00:00.000Z"
  }
}
```

| Field                | Description                                                     |
| -------------------- | --------------------------------------------------------------- |
| `chain.hash`         | SHA-256 hash of this event (content + previousHash + timestamp) |
| `chain.previousHash` | Hash of the preceding event in the chain (null for genesis)     |
| `chain.index`        | Position in the sequential chain                                |
| `chain.domain`       | Hash chain domain (e.g., `proof`)                               |
| `evidenceLevel`      | `DRAFT` → `DOCUMENTED` → `AUDIT_READY`                          |

<Info>
  The `evidence.get()` endpoint returns the full event payload including input data, AI context, and chain position. Use this for compliance checks and chain integrity verification. Use `evidence.export()` when you need a portable audit document.
</Info>

***

### GET /v1/evidence/{id}/export

Export a sealed event as a JSON-LD v2 audit document.

**Scope required**: `proof:read`

**Response (200):**

```json theme={null}
{
  "@context": "https://schema.cronozen.com/decision-proof/v2",
  "@type": "DecisionProof",
  "version": "2.0",
  "exportedAt": "2026-03-12T14:00:00.000Z",
  "evidence": {
    "id": "evi_x1y2z3",
    "decisionId": "refund-ORD-1234-20260312",
    "status": "sealed",
    "evidenceLevel": "AUDIT_READY",
    "event": { "..." : "..." },
    "approval": { "..." : "..." },
    "chain": { "..." : "..." }
  },
  "verification": {
    "hashAlgorithm": "SHA-256",
    "chainDomain": "proof",
    "chainIndex": 42,
    "chainHash": "sha256:9943798c...",
    "previousHash": "sha256:7f3a8b2c...",
    "verifyUrl": "https://cronozen.com/verify/cmmnhiobs0002bfi9mlu8eof4"
  }
}
```

This document is self-contained and can be stored, shared, or submitted to auditors independently of the Cronozen platform.

***

## Error Reference

| Status | Code               | Description                | Common Cause                              |
| ------ | ------------------ | -------------------------- | ----------------------------------------- |
| 400    | `BAD_REQUEST`      | Malformed request          | Invalid JSON body                         |
| 401    | `UNAUTHORIZED`     | Invalid or missing API key | Wrong key, expired key, missing header    |
| 403    | `FORBIDDEN`        | Insufficient scope         | `proof:read` key trying to write          |
| 404    | `NOT_FOUND`        | Event not found            | Invalid event ID, evidence not yet sealed |
| 409    | `CONFLICT`         | Event already sealed       | Attempting to approve a sealed event      |
| 422    | `VALIDATION_ERROR` | Invalid request body       | Missing required fields, invalid types    |
| 429    | `RATE_LIMIT`       | Too many requests          | Retry after a short delay                 |
| 500    | `INTERNAL_ERROR`   | Internal error             | Contact support                           |

All error responses follow this format:

```json theme={null}
{
  "error": {
    "code": "CONFLICT",
    "message": "Event is already sealed. Sealed events cannot be modified.",
    "details": {}
  }
}
```

### 409 Conflict — Sealed Event

The most common non-trivial error. Occurs when calling `POST /v1/decision-events/{id}/approvals` on an event that has already been approved and sealed.

**Why this happens:**

* Concurrent approval attempts (two approvers clicking simultaneously)
* Retry logic re-sending an already-successful approval
* Webhook handler firing multiple times

**How to handle:**

```typescript theme={null}
import { ConflictError } from "cronozen"

try {
  await cz.decision.approve(eventId, payload)
} catch (e) {
  if (e instanceof ConflictError) {
    // Already sealed — this is fine, the approval succeeded previously
    const existing = await cz.evidence.get(eventId)
    console.log("Already sealed at:", existing.sealedAt)
  }
}
```

Use `idempotencyKey` on the original `decision.record()` call to prevent duplicate events upstream.

***

<CardGroup cols={2}>
  <Card title="Proof SDK" icon="code" href="/examples/proof-sdk">
    TypeScript SDK reference with error handling and integration patterns
  </Card>

  <Card title="Proof Pipeline" icon="shield" href="/architecture/proof-pipeline">
    How the 4-stage evidence pipeline works under the hood
  </Card>
</CardGroup>
