Agent approval demo
What this demo shows
Section titled “What this demo shows”An AI agent requests a campaign creation through the Vauchflow MCP server using a scoped vf_ak_* key. Instead of executing immediately, the server responds with 202 Accepted and a pending_approval result. An operator reviews the request on the dashboard, approves it, and the campaign is created — at which point the agent.action.executed webhook fires and the full audit trail is permanently recorded.
This is the governance differentiator: the agent never has direct write power. High-risk actions are held for a human operator by default, with a complete, tamper-evident record of who requested what and who authorized it.
Before you start
Section titled “Before you start”- A Vauchflow tenant on Starter or above. Agent keys (
vf_ak_*) are gated on the Starter plan; Free-plan tenants do not have access to the agent key endpoint. - A secret key (
vf_sk_*) to issue the agent key. You can find or create one in the dashboard under Settings → API Keys. - An MCP-capable client. This walkthrough uses Claude Desktop as the worked example; any MCP 2024-11-05-compatible client works.
- A webhook endpoint to receive
agent.action.executed— or you can poll the approval ID directly. The demo shows both approaches.
Step 1 — Issue a scoped agent key
Section titled “Step 1 — Issue a scoped agent key”Use your secret key to create a short-lived agent key scoped to exactly the operations this agent needs. For campaign creation plus read-back, grant CAMPAIGN_WRITE and CAMPAIGN_READ.
curl -X POST https://api.vauchflow.com/v1/api-keys/agent \ -H "X-API-Key: vf_sk_YOUR_SECRET_KEY" \ -H "Content-Type: application/json" \ -d '{ "name": "Claude Desktop — campaign agent", "scopes": ["CAMPAIGN_WRITE", "CAMPAIGN_READ"], "ttlDays": 7 }'Response:
{ "id": "3fa85f64-5717-4562-b3fc-2c963f66afa6", "name": "Claude Desktop demo", "rawKey": "vf_ak_...", "displayKey": "vf_ak_Ab...", "type": "AGENT", "environment": "SANDBOX", "status": "ACTIVE", "scopes": ["CAMPAIGN_WRITE", "CAMPAIGN_READ"], "expiresAt": "2026-05-25T10:00:00Z"}Agent keys are revocation-only — the rotation endpoint rejects them with 422. To replace a key, revoke the old one and issue a new one. See AI agents overview for the full TTL semantics.
Step 2 — Connect the agent (MCP)
Section titled “Step 2 — Connect the agent (MCP)”Paste the following into your Claude Desktop configuration file (~/Library/Application Support/Claude/claude_desktop_config.json on macOS; the equivalent path on Windows and Linux is documented in Claude Desktop’s settings):
{ "mcpServers": { "vauchflow": { "url": "https://api.vauchflow.com/v1/mcp", "headers": { "X-API-Key": "vf_ak_your-agent-key-here" } } }}Restart Claude Desktop, then type a natural-language prompt in the chat window:
Create a Black Friday campaign with a 20% discount, max 1000 redemptions.
Claude Desktop’s tool-calling loop sees the prompt, selects the create_campaign MCP tool, and sends a JSON-RPC tools/call request to POST /v1/mcp with the structured campaign parameters. The agent key in the header scopes the call to CAMPAIGN_WRITE only — it cannot touch vouchers, customers, billing, or any other surface. See MCP server for the full tool list and required scopes.
Step 3 — The server responds 202 pending_approval
Section titled “Step 3 — The server responds 202 pending_approval”Because create_campaign is a high-risk write and the caller is a vf_ak_* key, the request routes to the operator approval queue rather than executing immediately. The MCP tool returns a successful result — not a 4xx error — with a pending_approval status.
The full JSON-RPC wire response Claude Desktop receives:
{ "jsonrpc": "2.0", "id": "<req-id>", "result": { "content": [{ "type": "text", "text": "{\"status\":\"pending_approval\",\"approval_id\":\"d4e5f6a7-b8c9-4d01-a234-56789abcdef0\",\"action_type\":\"create_campaign\",\"expires_at\":\"2026-05-18T10:15:00Z\",\"poll_url\":\"/v1/agent-approvals/d4e5f6a7-b8c9-4d01-a234-56789abcdef0\",\"message\":\"This action requires operator approval before execution.\"}" }], "isError": false }}Claude surfaces this to the user as something like:
Your campaign creation request was sent for operator approval. Approval ID: d4e5f6a7-b8c9-4d01-a234-56789abcdef0. I’ll let you know the outcome once an operator reviews it.
This is the designed response, not an error. The agent has successfully submitted the request into a governed queue. From this point the agent can poll GET /v1/agent-approvals/d4e5f6a7-... with its vf_ak_* key, or wait for the agent.action.executed webhook event.
For the full lifecycle state machine, the default policy table (which actions require approval versus auto-execute), and per-tenant policy override instructions, see Approval queue.
Step 4 — Operator approves on the dashboard
Section titled “Step 4 — Operator approves on the dashboard”When a pending_approval entry is created, the dashboard sidebar shows a badge with the pending count (updated by a background poll so operators see new requests without refreshing).
Operators navigate to Activity → Agent Approvals to see the queue. Each entry displays:
- The action type (
create_campaign), the time it arrived, and the remaining time before expiry. - A structured request body summary — a human-readable digest of the parameters (campaign name, discount type, discount value, max redemptions). The raw JSON body is never shown in the UI or included in API responses: a PII guard prevents personally identifiable data from surfacing in operator views.
- An Approve button and a Reject button.
+------------------------------------------------+| Agent Approval Request || Action: create_campaign || Submitted: 2026-05-18T10:00:12Z || Expires: 2026-05-18T10:15:12Z (15 min) || || Request summary || name Black Friday || discountType PERCENTAGE || discountValue 20 || maxRedemptions 1000 || || Requested by agent key: vf_ak_Ab… (id: …) || || [ Approve ] [ Reject ] |+------------------------------------------------+(Described UI — not a screenshot)
Who can approve: dashboard users with the OWNER or ADMIN role. Users with the DEVELOPER role can view the list and entry detail but the approve and reject buttons are not rendered for them. Approval via the REST API also requires an OWNER or ADMIN role (or a secret key). See API reference — Agent approvals for the endpoint permission table.
Default expiry: 15 minutes from creation. Entries past their expiresAt are swept automatically. If the TTL elapses before an operator acts, the entry transitions to expired and the action will not execute.
Step 5 — Campaign is created; agent.action.executed fires
Section titled “Step 5 — Campaign is created; agent.action.executed fires”Once an operator approves the entry, Vauchflow replays the stored request under a fresh tenant context. On success the approval entry transitions to executed.
Polling the approval entry (using the originating agent key):
curl https://api.vauchflow.com/v1/agent-approvals/d4e5f6a7-b8c9-4d01-a234-56789abcdef0 \ -H "X-API-Key: vf_ak_your-agent-key"Response when executed:
{ "approvalId": "d4e5f6a7-b8c9-4d01-a234-56789abcdef0", "status": "EXECUTED", "executionResult": { "id": "cmp_01HX...", "name": "Black Friday", "status": "ACTIVE" }}The agent.action.executed webhook event — if you have subscribed to agent.action.* events, your endpoint receives:
{ "id": "evt_01HX...", "type": "agent.action.executed", "timestamp": "2026-05-18T10:08:44Z", "data": { "approvalId": "d4e5f6a7-b8c9-4d01-a234-56789abcdef0", "actionType": "create_campaign", "executionResult": { "id": "cmp_01HX...", "name": "Black Friday", "status": "ACTIVE" } }}Every delivery carries the X-Vauchflow-Signature: t=<timestamp>,v1=<hmac-sha256> header — verify it against your signing secret to reject replays. See Webhooks — Verifying signatures for the reference verification snippet; do not reimplement the algorithm from scratch.
Step 6 — The audit trail
Section titled “Step 6 — The audit trail”The agent_approvals row persists permanently and is queryable by operators via the REST API and dashboard. It contains:
- The originating agent key id — identifies which
vf_ak_*key submitted the request (never the raw key value). - The
request_body_summary— a structured digest of the parameters. The raw request body is never stored in the approvals table. - The operator user id who approved (or rejected) the entry, with timestamp.
- The
executionResult— the created or updated entity from the replay, exactly as returned by the underlying use case. - Full timestamps:
created_at,updated_at,expires_at, plus the operator action timestamp.
If the agent sent the optional Vauchflow-Agent-Context header, the following fields are also recorded and queryable:
| Audit field | Source |
|---|---|
agent | The agent= key — e.g. claude-desktop |
client | The client= key — e.g. vauchflow-mcp@0.1.0 |
prompt_fp | An 8-character hex fingerprint of the originating prompt — computed client-side; the raw prompt is never transmitted or stored |
conv | The conv= conversation id — links multi-turn actions to a single session |
Example header as sent by Claude Desktop:
Vauchflow-Agent-Context: agent=claude-desktop;client=vauchflow-mcp@0.1.0;prompt_fp=8a3f1b2c;conv=conv_01H8XYZThe full lifecycle is also visible via the webhook event stream in chronological order:
agent.action.pending_approval → agent.action.approved → agent.action.executed
For the rejection and expiry paths the terminal events are agent.action.rejected and agent.action.expired respectively. Subscribe to all five event types on a single webhook endpoint to build a complete event log without polling. See Webhooks — Event types for the full event table.
What “production-safe” actually means here
Section titled “What “production-safe” actually means here”The governance properties that make this safe for enterprise use:
- The agent never has direct write power. A
vf_ak_*key is scoped to exactly the operations you grant, expires automatically (1–90 days, default 7), and is revocation-only. There is no rotation path that extends its lifetime — revoke and reissue. - Human-in-the-loop is default-on for high-risk writes. Campaign creation, bulk voucher issuance above the threshold, and webhook configuration route to the approval queue by default. Per-tenant policy overrides exist, but the defaults are conservative. An operator must take an explicit action to bypass the queue for any of these operations.
- The audit trail is comprehensive and independently auditable. Every approval entry records the scoped key id, the request digest, the operator id and timestamp, the execution outcome, and the agent context fingerprint. Compliance and security teams have the data they need — the agent identity, the conversation trace, and the human authorization record — without procuring a separate audit add-on or parsing application logs.
Next steps
Section titled “Next steps”- AI agents overview — decision matrix, key scopes, and integration surfaces.
- Approval queue — full policy table, lifecycle states, and operator reference.
- MCP server — complete tool list, dry-run pattern, and JSON-RPC error codes.