Introduction
Confirm is the approval layer for AI agents. Your code calls one API before an agent does anything sensitive. The agent pauses, a human decides, and a signed webhook resumes the run. Every decision lands in an immutable audit log.
Why this exists
Agents are good enough to act and not good enough to act unsupervised. The failure modes are concrete: a support agent refunds $10,000 instead of $10, a sales agent invents a fact in a cold email, an ops agent drops a table that billing still reads. Teams either ship agents with no brakes, or bolt approvals onto Slack threads that satisfy no auditor.
Confirm makes the brakes infrastructure: one API to pause, one review page for the human, one webhook to resume, one log for compliance.
The request lifecycle
- Create. Your code calls
POST /v1/requestswith a summary, the exact action payload, and who decides. That can be a single email or an approver group like"group:finance"for escalation. - Notify. Every approver gets their own single-purpose magic link by email. No account, no app. Optionally, a Slack channel notification announces the request to the team.
- Decide. The review page shows the payload plus the context you passed:
reasoning(why the agent wants this) andrecentActions(what it did first), so the decision takes seconds. The human approves, rejects, or edits the payload before approving. Identity, timestamp, IP, and note are recorded. - Resume. We fire an HMAC-signed webhook carrying the verdict, the
effectivePayload(human edits win), and anyagentStateyou parked with the request, so a fresh worker can rehydrate and continue even hours later. - Expire. If nobody answers within the TTL, the request becomes
EXPIREDand a webhook tells your system. Silence never becomes consent.
How you integrate
Three ways in, same approval underneath. Pick by stack.
| Path | Best for | Looks like |
|---|---|---|
TypeScript SDK@confirm/sdk | Node/TS agents. The recommended path. | guard() gates your whole tool layer with policies (fail-closed), or withApproval() wraps one function. |
MCP serverconfirm-mcp | MCP agents: Claude Desktop, Cursor. | Adds a request_approval tool with one config block, no code. |
| Raw HTTP | Any language, any framework. | One POST with a bearer key. Resume on the webhook, or poll. |
What ships in the box
| Capability | What it does |
|---|---|
| Escalation routing | notify: "group:finance" fans out to a named approver group. First decision wins, and the audit names exactly who decided. |
| Approver context | reasoning and recentActions render the agent's story above the payload on the review page. |
| Payload editing | Approvers fix the JSON before approving. Your code executes effectivePayload, never the first draft. |
| State parking | agentState stores up to 256KB of serialized context and returns it in the webhook for rehydration. |
| Fail-safe expiry | Workspace default or per-request TTL. Unanswered requests expire and notify your agent. |
| Signed webhooks | HMAC-SHA256 over a timestamped payload, retried, with delivery receipts. |
| Teams | Invite collaborators with roles. Approvers never need accounts. |
| Audit log | Append-only record of every request, decision, and delivery attempt. |
Request statuses
| Status | Meaning |
|---|---|
| PENDING | Waiting on a human. The agent should stay paused. |
| APPROVED | A human said yes. Execute effectivePayload. |
| REJECTED | A human said no. Do not execute; the rejection note explains why. |
| EXPIRED | The TTL elapsed with no decision. Treat it as a rejection. |
PENDING is the only non-terminal state. Terminal states never change: a request can't be re-approved, un-rejected, or resurrected after expiry.
NEXT