Proof of Action

Human-in-the-Loop Approvals

AI agents and automation pipelines ask a person to approve, over Telegram or WhatsApp — or any other channel, on request. Every decision is signed: a proof token anyone can check offline.

Proof Holdingsproof.holdings · verification api
Oversight recordNº 2026-0121
subjectAdverse action · applicant #6471
systemcredit-model v3 — solely automated
channelhuman review — via WhatsApp
decisionapproved by a person, on the record
received14:02:07 UTC
verdictOversight recordedthe review itself is signed — not a screenshot
Mintedph_ctl_2f9a…e41 · ES256

Built for Agents, Pipelines, and Automation

Add human checkpoints wherever trust matters. No web UI to build — approvals arrive in messaging apps your team already uses.

AI Agent Approvals

Give Claude, LangChain, or CrewAI agents a trust layer. Our MCP server (167 tools) lets any agent ask a person before an irreversible action.

CI/CD Deploy Gates

Require human approval before production deploys. Integrate with any CI/CD pipeline via API or MCP. Cryptographic proof of every release decision.

Automation Checkpoints

Add a person to a workflow through the API: pause for a sensitive decision, resume when approved. n8n, Zapier and Make connectors are planned.

How It Works

Your agent asks, the human decides, you get cryptographic proof

§ 01

Create Confirmation

Your agent or pipeline calls POST /api/v1/confirmations with the request text encrypted in your own process (the SDK helper createAndWait does it) and the HITL config ID for the approval channel.

§ 02

Human Decides

The approver gets a short notice with a link, never the request itself. They open it, unlock it in their browser, read it, and approve or deny, optionally with a comment.

§ 03

Proof Generated

You receive a signed proof token (ES256) of the decision — approve or deny, with its time. Anyone can check it offline against our published key; the comment stays on the record.

Approval Channels

The chat carries a notice and a link — the request itself opens, decrypted, only in the approver's browser

telegram logo

Telegram

Notice and link

🔔 team.management — Production deploys

You have a new approval request. Review and respond here:

Review & Approve
whatsapp logo

WhatsApp

Notice and link

🔔 team.management — Production deploys

You have a new approval request. Review and respond here:

Review & Approve

API Integration

Add human approvals to your agent or pipeline in minutes

1. Create the approval request

# The approver decrypts in their browser, so "message" is a ciphertext envelope —
# build it with an SDK (encryptMessage / encrypt_message) using the config's public key
# from GET /api/v1/hitl/YOUR_HITL_CONFIG_ID/keys. Plaintext is refused: message_not_encrypted.
# The v1 envelope below is for a config with no enrolled approvers; with approvers, wrap the
# key for each of them too (v2) or the call is refused: envelope_missing_approvers.
curl -X POST https://api.proof.holdings/api/v1/confirmations \
  -H "Authorization: Bearer pk_live_YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "hitl_id": "YOUR_HITL_CONFIG_ID",
    "message": "{\"v\":1,\"alg\":\"RSA-OAEP-256+AES-256-GCM\",\"ek\":\"...\",\"iv\":\"...\",\"ct\":\"...\",\"tag\":\"...\",\"kid\":\"...\"}"
  }'

2. Wait for the decision

# Poll for approval status
curl https://api.proof.holdings/api/v1/confirmations/CONFIRMATION_ID \
  -H "Authorization: Bearer pk_live_YOUR_API_KEY"

Response Example

{
  "id": "507f1f77bcf86cd799439011",
  "hitl_id": "YOUR_HITL_CONFIG_ID",
  "message": "{\"v\":1,\"alg\":\"RSA-OAEP-256+AES-256-GCM\",\"ek\":\"...\",\"iv\":\"...\",\"ct\":\"...\",\"tag\":\"...\",\"kid\":\"...\"}",
  "status": "approved",
  "response": {
    "action": "approve",
    "text": "{\"v\":1,\"alg\":\"RSA-OAEP-256+AES-256-GCM\",\"ek\":\"...\",\"iv\":\"...\",\"ct\":\"...\",\"tag\":\"...\",\"kid\":\"...\"}",
    "channel": "telegram",
    "at": "2026-03-15T10:51:30Z",
    "authorization_id": null
  },
  "proof_token": "eyJhbGciOiJFUzI1NiIsInR5cCI6IkpXVCJ9...",
  "timeout_at": "2026-03-15T11:50:00Z",
  "resolved_at": "2026-03-15T10:51:30Z",
  "created_at": "2026-03-15T10:50:00Z",
  "updated_at": "2026-03-15T10:51:30Z"
}