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.
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.
Your agent asks, the human decides, you get cryptographic proof
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.
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.
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
Notice and link
You have a new approval request. Review and respond here:
Notice and link
You have a new approval request. Review and respond here:
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"
}