> ## 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 SDK

> Record, approve, and export tamper-proof evidence for AI decisions — in 3 lines of code

# Proof SDK

The Cronozen Proof SDK provides a lightweight, type-safe client for the Decision Proof API. Record AI decisions, add human approvals, and export audit-ready evidence.

<Info>
  **Package**: `cronozen` · **Size**: 11.2KB · **Dependencies**: Zero · **Formats**: CJS + ESM + TypeScript
</Info>

***

## Installation

```bash theme={null}
npm install cronozen
```

***

## Quick Start

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

const cz = new Cronozen({
  apiKey: process.env.CZ_KEY!,
  baseUrl: "https://cronozen.com/api/v1",
})

// 1. Record a decision
const event = await cz.decision.record({
  type: "agent_execution",
  actor: { id: "support_agent", type: "ai_agent" },
  action: {
    type: "refund_approved",
    input: { orderId: "ORD-1234", amount: 45000 },
  },
  aiContext: { model: "gpt-4", confidence: 0.87 },
})

// 2. Add human approval (seals with SHA-256)
const approval = await cz.decision.approve(event.id, {
  approver: { id: "team_lead", type: "human", name: "Team Lead" },
  result: "approved",
  reason: "Policy-compliant refund under $100K threshold",
})

console.log(approval.sealedHash)
// → "sha256:9943798c6313e9dd2cffa71686176d4125a65777..."

// 3. Export audit-ready evidence
const report = await cz.evidence.export(event.id)
// → JSON-LD v2 document at schema.cronozen.com/decision-proof/v2
```

***

## API Reference

### `cz.decision.record(params)`

Create a new decision event with structured metadata.

```typescript theme={null}
const event = await cz.decision.record({
  type: "settlement_created",            // Event type identifier
  actor: {
    id: "finance_bot",                   // Who/what made the decision
    type: "ai_agent",                    // "human" | "ai_agent" | "system" | "service"
    name: "Finance Bot",                 // Optional display name
  },
  action: {
    type: "approve_settlement",          // What action was taken
    description: "Auto-approved based on amount threshold",
    input: {                             // Action inputs (any JSON)
      settlementId: "STL-5678",
      amount: 150000,
      currency: "KRW",
    },
    output: {                            // Action outputs (any JSON)
      priority: "high",
    },
  },
  aiContext: {                           // Optional: AI involvement details
    model: "claude-sonnet-4-5-20250514",
    provider: "anthropic",
    confidence: 0.92,
    reasoning: "Amount within auto-approval threshold",
  },
  tags: ["settlement", "finance", "auto"],  // Optional: filterable tags
  idempotencyKey: "settle-STL-5678",        // Optional: prevent duplicates
})
```

**Returns**: `DecisionEventResponse`

```typescript theme={null}
{
  id: string
  decisionId: string
  type: DecisionEventType
  status: "recorded"           // Initially "recorded"
  actor: DecisionActor
  action: DecisionAction
  occurredAt: string
  aiContext?: AIContext
  tags: string[]
  evidence?: {
    id: string
    status: "pending"
    chainHash?: string
    chainIndex?: number
  }
  createdAt: string
  updatedAt: string
}
```

**Event types**: `agent_execution` | `workflow_step` | `human_approval` | `ai_recommendation` | `automated_action` | `policy_decision` | `escalation` | `custom`

#### Idempotency

Pass `idempotencyKey` to safely retry without creating duplicates. If the same key is sent again, the original event is returned instead of creating a new one.

```typescript theme={null}
// Safe to retry — same key returns same event
const event = await cz.decision.record({
  type: "settlement_created",
  actor: { id: "cron_job", type: "system" },
  action: {
    type: "daily_settlement",
    input: { date: "2026-03-12" },
  },
  idempotencyKey: "daily-settle-2026-03-12",
})
```

<Info>
  Use idempotency keys for any event triggered by webhooks, queue consumers, or cron jobs where retries are expected.
</Info>

### `cz.decision.approve(id, params)`

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

```typescript theme={null}
const approval = await cz.decision.approve(event.id, {
  approver: {
    id: "center_director",
    type: "human",                      // "human" | "system"
    name: "Center Director",
  },
  result: "approved",                   // "approved" | "rejected"
  reason: "Reviewed and approved per settlement policy v2.1",
})

// approval.sealedHash   → "sha256:..." (only if approved)
// approval.sealedAt     → "2026-03-12T13:05:00Z"
// approval.evidenceLevel → "AUDIT_READY"
```

Once approved, the event is **sealed** — its hash becomes part of the immutable chain.

<Warning>
  Calling `approve()` on an already-sealed event throws `ConflictError` (409). This is by design — sealed events cannot be modified.
</Warning>

### `cz.decision.get(id)`

Retrieve a single decision event with full metadata.

```typescript theme={null}
const event = await cz.decision.get("cmmnhiobs0002bfi9mlu8eof4")
// → { id, decisionId, type, status, actor, action, evidence, ... }
```

### `cz.decision.list(params?)`

List decision events with optional filters.

```typescript theme={null}
const { data, pagination } = await cz.decision.list({
  type: "settlement_created",    // Filter by event type
  status: "sealed",              // "recorded" | "pending_approval" | "approved" | "rejected" | "sealed"
  tag: "settlement",             // Filter by single tag
  limit: 50,                     // 1-100 (default: 20)
  offset: 0,                     // Pagination offset
})
```

**Returns**: `DecisionEventListResponse`

```typescript theme={null}
{
  data: DecisionEventResponse[]
  pagination: {
    total: number
    limit: number
    offset: number
    hasMore: boolean
  }
}
```

### `cz.evidence.get(id)`

Retrieve sealed evidence with full hash chain verification data. Returns the complete event payload including input data, AI context, approval chain, and chain integrity status.

```typescript theme={null}
const evidence = await cz.evidence.get("cmmnhiobs0002bfi9mlu8eof4")

console.log(evidence.status)              // "sealed"
console.log(evidence.evidenceLevel)       // "AUDIT_READY"
console.log(evidence.chain.hash)          // "sha256:9e2b..."
console.log(evidence.chain.previousHash)  // "sha256:7f3a..." or null (genesis)
console.log(evidence.chain.index)         // 42
console.log(evidence.chain.domain)        // "proof"
console.log(evidence.approval)            // { approver, result, reason, approvedAt }
```

Use `evidence.get()` for chain integrity verification and compliance checks. Use `evidence.export()` when you need a portable audit document.

<Info>
  Throws `NotFoundError` if the decision hasn't been sealed yet.
</Info>

### `cz.evidence.export(id)`

Export audit-ready document in JSON-LD v2 format.

```typescript theme={null}
const report = await cz.evidence.export("cmmnhiobs0002bfi9mlu8eof4")

// report["@context"]                   → "https://schema.cronozen.com/decision-proof/v2"
// report["@type"]                      → "DecisionProof"
// report.verification.hashAlgorithm    → "SHA-256"
// report.verification.verifyUrl        → "https://cronozen.com/verify/..."
```

**Export includes**:

* Structured decision record (actor, action, input, AI context)
* Approval chain with timestamps
* SHA-256 hash chain link and position
* Evidence level and verification metadata

***

## Error Handling

The SDK provides 9 typed error classes for precise error handling:

| Error Class           | HTTP Status | When It Occurs                                 |
| --------------------- | ----------- | ---------------------------------------------- |
| `BadRequestError`     | 400         | Malformed request                              |
| `AuthenticationError` | 401         | Invalid or missing API key                     |
| `ForbiddenError`      | 403         | API key lacks required scope                   |
| `NotFoundError`       | 404         | Event ID does not exist                        |
| `ConflictError`       | 409         | Event is already sealed (cannot approve twice) |
| `ValidationError`     | 422         | Missing required fields, invalid types         |
| `RateLimitError`      | 429         | Too many requests                              |
| `TimeoutError`        | —           | Request exceeded timeout                       |
| `NetworkError`        | —           | Connection failed                              |

```typescript theme={null}
import {
  Cronozen,
  CronozenError,
  AuthenticationError,
  ForbiddenError,
  NotFoundError,
  ConflictError,
  ValidationError,
  BadRequestError,
  RateLimitError,
  TimeoutError,
  NetworkError,
} from "cronozen"

try {
  await cz.decision.approve(id, payload)
} catch (e) {
  if (e instanceof ConflictError) {
    // 409 — Event already sealed. This is safe to ignore on retries.
    const existing = await cz.evidence.get(id)
    console.log("Already sealed at:", existing.sealedAt)
  }
  if (e instanceof ForbiddenError) {
    // 403 — API key scope insufficient
    console.error("Need proof:write scope")
  }
  if (e instanceof NotFoundError) {
    // 404 — Event does not exist
  }
  if (e instanceof ValidationError) {
    // 422 — Invalid request body
    console.error("Validation errors:", e.details)
  }
  if (e instanceof CronozenError) {
    // Any other API error
    console.error(e.code, e.status, e.details)
  }
}
```

### Handling 409 Conflict (Sealed Events)

The most common non-trivial error. Occurs when two approvers act simultaneously, or retry logic re-sends an already-successful approval.

```typescript theme={null}
async function safeApprove(eventId: string, payload: ApproveDecisionRequest) {
  try {
    return await cz.decision.approve(eventId, payload)
  } catch (e) {
    if (e instanceof ConflictError) {
      // Already sealed — not an error, just return the existing evidence
      return await cz.evidence.get(eventId)
    }
    throw e
  }
}
```

<Info>
  Combine `idempotencyKey` on `decision.record()` with `ConflictError` handling on `decision.approve()` for fully retry-safe integrations.
</Info>

***

## Non-Blocking Integration

For production use, wrap Proof calls so they never block your business logic:

```typescript theme={null}
function fireProof(
  cz: Cronozen,
  fn: (cz: Cronozen) => Promise<unknown>,
  label: string,
): void {
  fn(cz).catch((error) => {
    console.error(`[Proof] ${label} failed (non-blocking):`, error)
  })
}

// In your settlement handler:
const settlement = await createSettlement(data)

// Non-blocking evidence recording
fireProof(cz, (cz) =>
  cz.decision.record({
    type: "automated_action",
    actor: { id: actorId, type: "system" },
    action: {
      type: "settlement_created",
      input: { settlementId: settlement.id, amount: settlement.amount },
    },
    tags: ["settlement", vertical],
    idempotencyKey: `settlement-created-${settlement.id}`,
  }),
  `settlement-record-${settlement.id}`,
)
```

<Warning>
  If `CZ_KEY` environment variable is not set, you can make the SDK client silently disable itself. This means you can add Proof integration to your codebase and activate it per-environment by simply setting the key.
</Warning>

***

## Integration Patterns

### Settlement Flow

```
Settlement Created    →  cz.decision.record()   →  Event recorded
Director Approves     →  cz.decision.approve()  →  SHA-256 sealed
Audit Request         →  cz.evidence.export()   →  JSON-LD report
```

### AI Agent Workflow

```
Agent Decides         →  cz.decision.record({ aiContext })
Human Reviews         →  cz.decision.approve()
Compliance Check      →  cz.evidence.get() + verify chain
External Audit        →  cz.evidence.export()
```

### Webhook (Coming Soon)

```
Event Sealed          →  Webhook fires to your endpoint
                      →  { event, chainHash, approval }
```

***

## Configuration

```typescript theme={null}
const cz = new Cronozen({
  apiKey: process.env.CZ_KEY!,              // Required: API key
  baseUrl: "https://cronozen.com/api/v1",   // Required: API endpoint
  timeout: 10000,                            // Optional: request timeout (ms)
  fetch: customFetch,                        // Optional: custom fetch implementation
})
```

| Option    | Required | Default            | Description                                      |
| --------- | -------- | ------------------ | ------------------------------------------------ |
| `apiKey`  | Yes      | —                  | API key with `proof:write` or `proof:read` scope |
| `baseUrl` | Yes      | —                  | Proof API base URL                               |
| `timeout` | No       | `10000`            | Request timeout in ms                            |
| `fetch`   | No       | `globalThis.fetch` | Custom fetch implementation                      |

***

## What's Next

* **Proof Admin Console** — Visual decision timeline and audit dashboard
* **Multi-approval policies** — Route approvals by amount, risk level, or decision type
* **Webhook notifications** — Real-time alerts on specific event types
* **Batch recording** — Record multiple decisions in a single API call

<Card title="Get API Key" icon="key" href="https://cronozen.com/auth">
  Sign up and generate your Proof API key to get started.
</Card>
