Guide/governance3 min read

How to Add Human Approval to an AI Agent

How to add human approval to an AI agent: gate sensitive actions on your Account Kit, surface pending approvals, and replay on approve. A step-by-step guide.

Read the docs →

Guide/governance
TL;DR
  • Human approval for AI agents means sensitive actions freeze until a person approves not a suggestion in the prompt.
  • On Naïve, gating is configured per primitive on the Account Kit; the API returns 202 pending_approval instead of executing.
  • Cards, domains, verification, formation, and new app connections default to requiring approval for agent calls.
  • Your app surfaces the pending action; a human approves; the API replays the frozen call.
  • Human dashboard/session callers bypass the gate only agent (API-key/MCP) calls are gated.
  • Five steps: assign a kit, attempt a gated action, handle pending, approve, verify executed.

Human approval for an AI agent means a sensitive action freezes until a person decides — issue a card, connect a new app, form a company — instead of executing immediately. The unsafe pattern is asking the model to "check with a human first"; the safe pattern is the platform returning pending_approval and replaying only after approve.

This is a step-by-step guide using Naïve's approvals primitive, scoped to one tenant user. For the full spend/audit/revoke picture, see spend caps, approvals & revocation for AI agents.

What counts as "sensitive"?

Naïve gates these by default for agent calls (per published docs):

Actionaction_type
Issue a virtual cardcards.create
Create a cardholdercards.cardholder.create
Top up a cardcards.topup
Purchase a domaindomains.purchase
Start verificationverification.start
Form a companyformation.create
File the formationformation.submit
Connect / sign up for a 3rd-party serviceconnections.connect

Override any primitive in the Account Kit with requiresApproval: true (force) or false (opt out).

Step 1 — Assign an Account Kit with gating

Ensure the tenant user has a kit that enables the primitive you want to gate. Default kits gate the actions above; a Pro tier might opt out of card approval but keep domain purchases gated.

// The kit is assigned at provision or via account-kits API — see docs for your flow
const alice = naive.forUser("alice-tenant-id");

AI agent permissions explains how kits enforce policy server-side.

Step 2 — Agent attempts a gated action

The agent calls the primitive normally. If gated, execution stops:

import { isPendingApproval } from "@usenaive-sdk/server";
 
const res = await alice.cards.create({
  name: "Ads",
  spendingLimitCents: 50000,
});
 
if (isPendingApproval(res)) {
  console.log(res.approval_id, res.title);
  // → surface to your UI: "Agent wants to issue card 'Ads' — approve?"
}

The response is HTTP 202, not an error — the action is parked, not failed.

Step 3 — Surface the pending approval

Your product shows the human who can decide: the developer, the customer's admin, or the end-user — depending on your model. Include title, action_type, and approval_id so the decision is informed.

List pending items:

naive approvals list --status pending --user alice-tenant-id

Step 4 — Approve or deny

A human approves; the API replays the frozen call:

await alice.approvals.approve(res.approval_id);

Or deny with a reason:

naive approvals deny <approval-id> --reason "not authorized" --user alice-tenant-id

On approve, status becomes executed with the action's result. On deny, denied — the action never ran.

Step 5 — Verify the outcome

const a = await alice.approvals.get(res.approval_id);
// a.status === "executed" → inspect a.result

Check the per-user audit log if you need the full trail — gateway decision, approval, execution.

How do you know it worked?

  • Agent call on a gated primitive → pending_approval, not a completed resource.
  • Approve → same resource exists as if the agent had succeeded directly.
  • Deny → no side effect; agent receives denial on retry.
  • Human dashboard action on the same primitive → executes without the gate (per published docs).

Why this matters in multi-tenant apps

One shared approval queue for all customers is a data-leak waiting to happen. Scope approvals to the tenant user: naive.forUser(customerId).approvals.* so Customer A's pending card issue never appears in Customer B's inbox. That's structural isolation — the same pattern as multi-tenant AI agents.

Where to go next

That's the approval loop: kit gates → agent pending → human decides → replay or deny. Wire one primitive first (card issue is the classic demo), then expand. The approvals docs are the source of truth; the governed agent profile shows where approval sits in the full lifecycle.

Frequently Asked Questions
How do I add human approval to an AI agent?+
Configure requiresApproval on sensitive primitives in the tenant user's Account Kit. When the agent triggers a gated action, the API returns pending_approval (HTTP 202) with an approval_id instead of executing. Surface that pending action to a human; on approve, the API replays the frozen call. Cards, domains, verification, formation, and connecting new apps default to gated (per published docs).
Which AI agent actions should require approval?+
Anything high-stakes: issuing money instruments, purchasing domains, starting verification or formation, connecting a new third-party app. Naïve gates these by default for agent calls. Lower-risk read-only actions can opt out with requiresApproval: false in the Account Kit.
What happens when an agent hits an approval gate?+
The action does not execute. The API returns status pending_approval, an approval_id, the action_type, and a human-readable title. Your application shows the pending item to an authorized human. Approve → API replays and returns executed with the result. Deny → terminal denied status, action never runs.
Do human operators go through the approval gate?+
No. Human callers from the dashboard or an MCP session bypass the gate. Only agent calls (API-key or MCP agent context on a real tenant user) are subject to requiresApproval (per published docs).
Can I require approval for custom actions?+
Gating is per primitive on the Account Kit — primitives_config.<primitive>.requiresApproval for native primitives, connections_config.requiresApproval for third-party apps. Set true to force approval, false to opt out of the default.
NT
Naïve Team

Building the agent-native backend.

Keep reading