Connect agents built in code
Send the tool calls of an agent you build in code to withHuman for approval.
If you build your own agent with a framework such as LangGraph, Pydantic AI or Mastra, the Agent Approval Protocol (AAP) SDKs send its tool calls to withHuman. Each call runs only once it's approved.
The SDKs work with any AAP provider. This guide covers what you need for withHuman. The AAP SDK docs cover every option in depth.
Before you start
You'll need:
- An agent in withHuman for your code to connect to. Use a service agent for an agent your organization runs. See Agent creation.
- Permission to issue credentials for that agent. Owners and admins have it.
- Approval rules for its calls. See Set up approval pipelines.
1. Get an API key
Open Agents, select your agent and select New instance. Choose Generate an API key, enter an instance name such as support-server-01, and select Generate API key.
Copy the Agent API key before you leave the page. It's shown only once. Store it where your agent reads its secrets. The examples read it from the WITHHUMAN_TOKEN environment variable.
Generate a separate key for each place the agent runs, so you can manage each one on its own. The page shows when the key expires.
To let runs pause while they wait, choose Pause runs while waiting instead. See Pausing instead of waiting.
If your platform starts new copies of the agent by itself, register each copy with a provisioner token instead.
2. Add the SDK
Every example passes the same two values:
- The AAP address. For hosted withHuman, that's
https://app.withhuman.ai/api/aap. On a self-hosted deployment, use its address followed by/api/aap. - The agent API key from step 1.
Find your framework below. Add the SDK to every agent and sub-agent you run, so none of their calls skip approval.
LangChain, LangGraph and Deep Agents
pip install "agentapprovalprotocol[langgraph]"import os
from agentapprovalprotocol.langgraph import ApprovalMiddleware
approvals = ApprovalMiddleware("https://app.withhuman.ai/api/aap", os.environ["WITHHUMAN_TOKEN"])
agent = create_agent(model, tools=tools, middleware=[approvals])For Deep Agents, also pass your subagents through deep_agent_subagents. See the LangGraph guide.
Pydantic AI
Needs Python 3.11 or later.
pip install "agentapprovalprotocol[pydantic-ai]"import os
from agentapprovalprotocol.pydantic_ai import ApprovalCapability
approvals = ApprovalCapability("https://app.withhuman.ai/api/aap", os.environ["WITHHUMAN_TOKEN"])
agent = Agent(model, tools=tools, capabilities=[approvals])See the Pydantic AI guide.
Google Agent Development Kit
pip install "agentapprovalprotocol[adk]"import os
from agentapprovalprotocol.adk import ApprovalPlugin, check_coverage
approvals = ApprovalPlugin("https://app.withhuman.ai/api/aap", os.environ["WITHHUMAN_TOKEN"])
app = App(name="support", root_agent=agent, plugins=[approvals])
check_coverage(app, approvals)Register the plugin before your other plugins. check_coverage tells you about agents whose calls the plugin can't see. See the ADK guide.
OpenAI Agents SDK
pip install "agentapprovalprotocol[openai-agents]"import os
from agentapprovalprotocol.openai_agents import Approvals
approvals = Approvals("https://app.withhuman.ai/api/aap", os.environ["WITHHUMAN_TOKEN"])
agent = approvals.protect(Agent(name="Support", tools=tools, handoffs=handoffs))
result = await Runner.run(agent, "Refund the duplicate charge")Call protect after the agent is fully configured. See the OpenAI Agents SDK guide.
Claude Agent SDK
pip install "agentapprovalprotocol[claude-agent-sdk]"import os
from agentapprovalprotocol.claude_agent_sdk import ApprovalHooks
approvals = ApprovalHooks("https://app.withhuman.ai/api/aap", os.environ["WITHHUMAN_TOKEN"])
options = approvals.options(ClaudeAgentOptions(mcp_servers=servers))
async for message in query(prompt=prompt, options=options):
...If you've connected Claude Code on the same machine with the withHuman CLI, don't load your user settings in the SDK. Otherwise every call asks for approval twice. See the Claude Agent SDK guide.
CrewAI
CrewAI supports Python 3.10 to 3.13. Its extra can't be installed alongside openai-agents.
pip install "agentapprovalprotocol[crewai]"import os
from agentapprovalprotocol.crewai import ApprovalHooks
ApprovalHooks("https://app.withhuman.ai/api/aap", os.environ["WITHHUMAN_TOKEN"]).install()
crew.kickoff()CrewAI runs always wait for each decision; they can't pause. See the CrewAI guide.
Vercel AI SDK
Needs AI SDK 7 and Node.js 22 or later.
npm install @agentapprovalprotocol/ai-sdk @agentapprovalprotocol/sdk aiimport { Client, Gate } from "@agentapprovalprotocol/sdk";
import { approvals } from "@agentapprovalprotocol/ai-sdk";
const gate = new Gate(new Client("https://app.withhuman.ai/api/aap", process.env.WITHHUMAN_TOKEN!));
const agent = new ToolLoopAgent({ model, tools, ...approvals({ gate }) });approvals works the same with generateText and streamText. See the AI SDK guide.
Mastra
Needs @mastra/core 1.76 and Node.js 22.13 or later.
npm install @agentapprovalprotocol/mastra @agentapprovalprotocol/sdkimport { Client, Gate } from "@agentapprovalprotocol/sdk";
import { Approvals } from "@agentapprovalprotocol/mastra";
import { Agent } from "@mastra/core/agent";
const approvals = new Approvals(new Gate(new Client("https://app.withhuman.ai/api/aap", process.env.WITHHUMAN_TOKEN!)));
const agent = new Agent(approvals.config({ id: "support", name: "Support", instructions, model, tools }));
await approvals.check(agent);check throws if a sub-agent or tool would skip approval. See the Mastra guide.
Your own tool loop
If you call tools yourself, check each call with a Gate before running it.
pip install agentapprovalprotocolimport os
from agentapprovalprotocol import Call, Client, Gate
gate = Gate(Client("https://app.withhuman.ai/api/aap", os.environ["WITHHUMAN_TOKEN"]))
with gate.guard(Call(key=f"{run_id}:{tool_call.id}", tool=tool_call.name, arguments=tool_call.args)) as verdict:
result = run_tool(tool_call) if verdict else verdict.reasonnpm install @agentapprovalprotocol/sdkimport { Client, Gate } from "@agentapprovalprotocol/sdk";
const gate = new Gate(new Client("https://app.withhuman.ai/api/aap", process.env.WITHHUMAN_TOKEN!));
const result = await gate.guard(
{ key: `${runId}:${toolCall.id}`, tool: toolCall.name, arguments: toolCall.args },
() => runTool(toolCall),
);
const output = result.kind === "allow" ? result.value : result.reason;The key names the call within its run, and must stay the same if the call is retried. See the AAP SDK docs.
What to expect
Every tool call your agent makes becomes an approval request. Your approval pipelines approve it, deny it or send it to a reviewer in the review queue.
- While a call waits for a reviewer, your agent waits too. After about nine minutes without a decision, the call doesn't run and the agent is told why.
- A denied call doesn't run. The agent gets the reason as the tool's result, so it can explain or try something else.
- An approved call runs once. The request page then shows Used, when the call started, and Result, whether it succeeded or failed.
Tools that run at the model provider, such as a model's built-in web search, can't wait for approval. The SDKs stop a run that uses one, so use a local tool instead.
Pausing instead of waiting
A run can pause at a call that needs a reviewer, so your process doesn't sit waiting. When the decision arrives, withHuman notifies your receiver: a small web endpoint from the SDK that resumes the run. Each framework guide in the AAP SDK docs shows how to turn on pausing with mode="async".
Before you start:
- withHuman must be able to reach the receiver at a public HTTPS address. Private and local addresses are refused. For local development, expose the receiver through a tunnel such as ngrok.
- The receiver is set up with the instance. An instance from Generate an API key can't add one later. Use a new instance instead.
- An approval stays valid for 30 minutes. If the run hasn't resumed by then, the call doesn't run and the agent is told the approval expired.
Receiver setup
-
Open your agent, select New instance and choose Pause runs while waiting.
-
Select Generate signing secret. Copy the Registration ID and Signing secret. The secret is shown only once, and you have an hour to finish.
-
Start your receiver with both values. With FastAPI and LangGraph:
pythonimport os from fastapi import FastAPI from agentapprovalprotocol import AsyncClient, Receiver from agentapprovalprotocol.langgraph import resumer receiver = Receiver( secret=os.environ["AAP_WEBHOOK_SECRET"], registration_id=os.environ["AAP_REGISTRATION_ID"], client=lambda: AsyncClient("https://app.withhuman.ai/api/aap", load_token()), resumers={"langgraph": resumer(agent)}, ) app = FastAPI() app.add_route("/aap/events", receiver.asgi(), methods=["POST"])Add the receiver as a route, as shown. Mounting it with
app.mountadds a redirect, which withHuman doesn't follow.load_token()stands for reading the API key from your secret store; it runs the first time a run resumes. -
Back in withHuman, enter an Instance name and your Receiver URL, such as
https://agent.example.com/aap/events. Select Verify receiver. -
withHuman checks that the receiver answers, then shows the instance's Agent API key once. Give it to your agent and your receiver.
If verification fails, fix the receiver and select Verify receiver again. The secret stays on the page. To register a receiver from code instead, see the AAP SDK docs.
Checking it works
Ask your agent to do something that uses a tool your rules send for review. Then:
- Open the agent's Instances tab. Last seen for your instance should show that it just contacted withHuman.
- Open Review queue and approve the request. The Review queue guide explains deciding requests.
- Open the request again. Used and Result show that your agent ran the call.
Calls your rules decide automatically don't reach the queue. Find them under the agent's Approval requests.
Troubleshooting
| What you see | What to do |
|---|---|
| The agent is told the approval provider refused its credential. | The key is wrong, expired or revoked, or the agent or instance is disabled. Check the instance in Instances, and generate a new key if needed. |
| The agent is told the adapter couldn't obtain a valid AAP decision. | Calls aren't reaching withHuman. Check that the address ends in /api/aap and that the machine can reach it over HTTPS. |
| Some calls never appear in withHuman. | Add the SDK to every agent and sub-agent. For ADK and Mastra, run the coverage check shown in your framework's section. |
| Every call asks for approval twice. | Something else on the machine also sends calls to withHuman, such as the Claude Code hook. Use one or the other. |
| The receiver isn't verified. | Check that it's running at the public HTTPS address you entered, with the registration ID and signing secret from the page. For local development, use a tunnel. |
| The registration expired. | Generate a new signing secret and start the receiver with the new values. |
| Pause runs while waiting isn't offered. | Your deployment can't send webhooks. Ask whoever runs it. |
| A paused run never resumes. | Check that the receiver is still running at the registered address, and that the instance was registered with it. |
| The agent is told the approval expired. | The run resumed more than 30 minutes after the approval. Ask the agent to try the call again. |