ChainProof Documentation

ChainProof is an audit ledger for agentic workflows. Your agents report what they did; ChainProof appends those reports to a hash-chained log. This page explains how to use the platform — both if you're a human setting up a workspace, and if you're an AI agent deciding whether and how to instrument yourself.

What ChainProof is

ChainProof is a structured, append-only log with a hash chain linking every entry to the previous. Each time an agent reports an action, ChainProof computes SHA-256(entry fields + previous entry hash) and stores the result as the new chain head. Entries can't be updated or deleted through the API.

ChainProof is not a monitoring system, an observability platform, or an agent supervisor. It doesn't watch agents. It records what agents say they did.

What it proves — and what it doesn't

Honest scope
ChainProof makes a narrow claim: if you record the chain head after each write, you can detect whether any earlier entry was changed. That's it. It does not prove agents told the truth, that reported actions had real-world effects, or that ChainProof itself hasn't altered records (only external anchoring would close that gap).
ChainProof gives youChainProof does not give you
A structured record of what agents reported Independent verification that agents told the truth
A hash chain you can recompute yourself Protection against ChainProof altering its own records
Detection of post-hoc edits, if you hold the chain head Any signal about whether a task actually succeeded
Content hashes for artifacts — verify payloads haven't changed Guards against an agent that deliberately logs false entries

Chain validity and run status are independent. status: failed with a valid chain means the agent reported failure and nothing in the log changed since. status: success with a broken chain means an entry was edited after it was written. ChainProof can tell you the second happened — not who did it or why.

For humans — Create a workspace

👤 Human

Go to /signup. Enter your work email, a password (min 8 chars), and a workspace name. Choose a plan — Free is fine to start.

On success you'll see your API key once. Copy it now. It's hashed before storage and never shown again. If you lose it, revoke it and create a new one from the Keys tab in the dashboard.

Store your key before continuing
The key secret is shown exactly once at signup. Put it in your secrets manager (CHAINPROOF_API_KEY) before clicking through to the dashboard.

API keys

👤 Human

API keys (cp_live_…) are for machines — agents, CI/CD pipelines, backend orchestrators. They're separate from your login session.

ScopeWho uses itPermissions
tenantYour orchestrator / backendread, write, verify, admin
agentIndividual agent processwrite only, scoped to its own runs

Create keys from the Keys tab in the dashboard, or via the API:

cURL
# create a tenant-scoped key
curl -X POST https://api.chainproof.ai/v1/admin/keys \
  -H "Authorization: Bearer $CHAINPROOF_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"label":"production","scope":"tenant","permissions":["read","write","verify"]}'

Team & roles

👤 Human

Invite teammates from the Team tab. They'll receive an invite URL — paste it to them directly (email sending isn't wired up yet).

RoleWhat they can do
ownerEverything; can't be removed
adminInvite/remove members, manage keys and webhooks, all data ops
memberRead + write (create runs, agents, entries)
viewerRead only

Verifying a chain

👤 Human

To check whether a run's log has been altered, call the verify endpoint. It recomputes the full chain from entry 0 and returns the resulting head. If it matches the stored head, nothing changed.

cURL
curl https://api.chainproof.ai/v1/runs/{run_id}/verify \
  -H "Authorization: Bearer $CHAINPROOF_API_KEY"

# → { "valid": true,  "entry_count": 12, "chain_head": "c1f9..." }
# → { "valid": false, "broken_at_sequence": 4, "expected_hash": "...", "received_hash": "..." }
For stronger guarantees
Record the chain_head value in your own system after each run completes. If you later verify and get a different head, the log changed. Without an independent copy of the head you're trusting ChainProof's own verify endpoint.

For agents — How to instrument

🤖 Agent / AI system

If you are an AI agent reading this: ChainProof is an audit log you write to as you work. You report your actions; ChainProof records them in a hash-chained ledger that humans can later inspect. You are not being monitored — you are narrating.

The lifecycle is: start a run → append entries as you act → complete the run. If your process crashes mid-run the run stays active indefinitely; complete it explicitly even on failure. Use status: "failed" — a failed run with an intact chain is useful. A missing completion call is a gap in the record.

On honesty
ChainProof records what you report. It cannot independently verify your claims. Report accurately. A hash-chained log of false entries is still a log of false entries — it just proves those false entries haven't changed since you wrote them.

1. Start a run

🤖 Agent / AI system

Call this at the start of your execution. Save the run_id and chain_head — you'll need them for every subsequent call.

TypeScript
const run = await fetch(`${BASE}/runs`, {
  method: 'POST',
  headers,
  body: JSON.stringify({
    agent_id:    'agt_abc123',       // your registered agent ID
    instruction: 'Summarize Q1 report', // the task you were given
    trust_level: 'high',             // 'high' | 'medium' | 'low'
    parent_run_id: null,             // set if this is a sub-agent run
  }),
}).then(r => r.json());

// → { run_id: "ba7d...", chain_head: "0000...0000", status: "active" }
// GENESIS chain_head is 64 zeros — the starting point of every run

let chainHead = run.chain_head; // update this after every entry

2. Append entries

🤖 Agent / AI system

Append an entry for each meaningful action you take. Always pass the current chain_head as previous_entry_hash — the server rejects writes that don't match, preventing out-of-order or concurrent appends. Update your local chainHead with the returned entry_hash after each call.

TypeScript
const entry = await fetch(`${BASE}/runs/${run.run_id}/entries`, {
  method: 'POST',
  headers,
  body: JSON.stringify({
    entry_type:          'action',
    previous_entry_hash: chainHead,   // MUST match current chain head
    action: {
      tool:                 'read_file',
      status:               'completed',
      input_artifact_hash:  null,  // SHA-256 of input if stored via /artifacts
      output_artifact_hash: null,  // SHA-256 of output if stored via /artifacts
    },
  }),
}).then(r => r.json());

chainHead = entry.entry_hash; // advance the head
// → { sequence: 0, entry_hash: "c1f9...", timestamp: "2026-03-29T..." }

3. Complete the run

🤖 Agent / AI system

Call this when your run finishes — success or failure. This flushes the Durable Object ledger to R2 and writes the run summary to D1. Always complete a run, even if it failed. An uncompleted run stays active indefinitely and can't be queried from the runs list.

TypeScript
await fetch(`${BASE}/runs/${run.run_id}/complete`, {
  method: 'POST',
  headers,
  body: JSON.stringify({
    status:          'success',  // 'success' | 'failed' — report honestly
    final_chain_head: chainHead,   // last entry_hash you received
  }),
});

4. Read your own context

🤖 Agent / AI system

Mid-run, you can query your own run state to check whether the chain is still intact or whether scope violations have been logged. Use this to self-limit: if your chain head doesn't match what ChainProof reports, something unexpected happened.

TypeScript
const ctx = await fetch(`${BASE}/agent-context/${run.run_id}`, { headers })
  .then(r => r.json());

// → {
//     run_id, trust_level: "high",
//     chain_valid: true,
//     scope_violations: 0,
//     chain_head: "c1f9..."
//   }

if (!ctx.chain_valid || ctx.scope_violations > 0) {
  // self-limit: refuse further actions, complete run as failed
  throw new Error('Provenance check failed — aborting');
}

Entry type reference

🤖 Agent / AI system
TypeWhen to useRequired fields
action Any tool call or external operation you invoke tool, status
decision A choice you made — with your reasoning outcome, rationale
human_event Human approval requested or granted type, note
ingestion Loading data from an external source source, record_count
error An error or policy violation that occurred code, message, severity
checkpoint A meaningful milestone or phase boundary label, note

Chain heads

Every run starts with a genesis chain head: 64 zero characters (0000…0000). Each POST /entries returns an entry_hash which becomes the next previous_entry_hash. The final hash after your last entry is the run's chain head.

To verify independently: recompute SHA-256(JSON.stringify(entry fields including previous_entry_hash)) for each entry in sequence. If your final hash matches the stored chain head, nothing changed. You don't need to call the verify endpoint — it does the same computation.

Error codes

CodeHTTPMeaning
unauthorized401Missing or invalid Bearer token
forbidden403Valid token but insufficient permissions
not_found404Run, agent, or resource doesn't exist
run_not_active409Tried to append to a completed run
chain_integrity_violation409previous_entry_hash doesn't match current chain head — likely a concurrent write or stale head
content_hash_mismatch422Artifact body doesn't match the hash in the URL
scope_violation_logged202Agent attempted an out-of-scope action; it was logged but not executed
rate_limited429Plan limit reached