API reference

The API is deliberately small: one endpoint to pause an agent, one to check the verdict. Base URL https://confirm.dev/api, JSON in and out.

Authentication

Pass your API key as a bearer token. Keys are created in Dashboard → API Keys and scoped to one workspace.

Authorization: Bearer cfm_live_...

Errors share one envelope everywhere: { "error": { "code": "...", "message": "..." } }.

Create a request

POST/v1/requests

Creates a PENDING approval request, emails the approver a magic link, and fires request.created to your webhooks.

Body

FieldTypeDescription
summaryrequiredstringHuman-readable description of the action, ≤500 chars. This is what the approver reads first — write it like a headline.
payloadjsonThe exact action the agent wants to take. Shown — and editable — in the review UI.
notifyrequiredstringThe approver's email address, or "group:<key>" to escalate to an approver group (Pro plan). See Teams & approval groups.
agentNamestringShown to the approver ("SupportBot requires approval"). ≤100 chars.
metadatajsonOpaque to us; echoed back in reads and webhooks. Use it for run IDs and trace correlation.
agentStatejson (≤256KB)Serialized agent context (conversation, plan, scratchpad). Stored with the request and returned in GET and webhooks so a stateless worker can rehydrate after a long approval wait. Never shown to the approver. Oversized payloads return 400 state_too_large.
reasoningstring (≤2000)The agent's explanation of why it wants this action. Shown to the approver above the payload so they can decide fast.
recentActionsstring[] (≤10 × 300)What the agent did right before asking (steps, lookups, checks). Rendered as a timeline on the review page.
ttlMinutesintegerMinutes until the request expires. 5–10080. Defaults to your workspace's configured expiry (Settings), initially 1440 (24h). Expiry is the auto-reject: the request becomes EXPIRED and your agent is notified.

Example

request
curl -X POST https://confirm.dev/api/v1/requests \
  -H "Authorization: Bearer $CONFIRM_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "summary": "Send 50 outbound emails (batch q3-outbound)",
    "payload": { "batchId": "q3-outbound", "count": 50 },
    "notify": "sdr-lead@company.com",
    "agentName": "OutreachBot",
    "reasoning": "All 50 drafts passed brand checks; batch is ready to send.",
    "recentActions": ["Drafted 50 emails from CRM data", "Ran brand-safety checks: 0 flags"],
    "agentState": { "batchId": "q3-outbound", "drafts": "s3://bucket/drafts.json" },
    "metadata": { "runId": "run_442" },
    "ttlMinutes": 120
  }'
response · 201
{
  "id": "clx2h8…",
  "object": "approval_request",
  "status": "PENDING",
  "summary": "Send 50 outbound emails (batch q3-outbound)",
  "payload": { "batchId": "q3-outbound", "count": 50 },
  "modifiedPayload": null,
  "effectivePayload": { "batchId": "q3-outbound", "count": 50 },
  "metadata": { "runId": "run_442" },
  "agentState": { "batchId": "q3-outbound", "drafts": "s3://bucket/drafts.json" },
  "reasoning": "All 50 drafts passed brand checks; batch is ready to send.",
  "recentActions": ["Drafted 50 emails from CRM data", "Ran brand-safety checks: 0 flags"],
  "agentName": "OutreachBot",
  "approverEmail": "sdr-lead@company.com",
  "approverGroup": null,
  "resolvedAt": null,
  "resolvedByEmail": null,
  "resolutionNote": null,
  "expiresAt": "2026-07-15T22:00:00.000Z",
  "createdAt": "2026-07-15T20:00:00.000Z"
}

Errors

StatusCodeWhen
400invalid_json / validation_errorMalformed body or failed field validation; the message names the field.
401unauthorizedMissing, malformed, invalid, or revoked API key.
402quota_exceededIncluded volume exhausted on Hobby, or the monthly safety cap reached on an overage plan.
403plan_requirednotify: "group:…" used on a plan without approver groups.
404unknown_groupThe group key doesn't exist in this workspace.
400empty_groupThe group exists but has no members to notify.

Retrieve a request

GET/v1/requests/:id

Returns the same shape as create, with current status. Expiry is applied lazily on read — you will never see a stale PENDING past its expiresAt. Requests belonging to other workspaces return 404.

request
curl https://confirm.dev/api/v1/requests/clx2h8… \
  -H "Authorization: Bearer $CONFIRM_API_KEY"
When status is APPROVED, execute effectivePayload — it already resolves the human-edit-wins precedence for you. payload is the historical record of what the agent originally wanted.

Webhook events

State changes push to your registered endpoints as signed POSTs — usually the better pattern than polling. Payload shape, headers, signature verification, and retry behavior are documented on the Webhooks page.