Documentation
DocsAPI reference

Agent Approval Protocol

What an adapter calls on behalf of a running agent. Create an instance and get its credential, ask for approval of a tool call, and wait for the answer.

Updated Sep 22, 2026

The Agent Approval Protocol (AAP) is the open protocol between an agent runtime and an approval provider. An adapter sits between the agent and its tools. When the agent calls a tool that needs approval, the adapter holds the call, describes it to the provider, and waits. The provider collects a decision from a person, or from the organization's approval pipeline, and hands it back. The adapter then runs the call or fails it.

The protocol carries decisions only. It never executes a tool or forwards its result. Approving or denying is the whole job, which keeps the surface to a handful of calls that any runtime can implement and any provider can serve. withHuman is one provider; an adapter written against this page works with any other.

A typical exchange:

  1. The adapter obtains a credential, either from withhuman agent install <runtime> on a developer machine or by creating an instance with a provisioner token.
  2. For a tool call, the adapter creates an approval request describing the tool and its arguments. The pipeline may decide on the spot; otherwise the request is pending.
  3. It retrieves the request with a wait, repeating until the status is approved, denied, expired, or cancelled, and acts on it.
  4. If the adapter stops waiting first (its runtime was interrupted or timed out), it cancels the request so reviewers stop seeing it.

Create an agent instance

POST/api/aap/v1/instancesBearer token

Creates a new instance of the agent the provisioner token belongs to and returns it together with its agent credential. Use this from a fleet or platform that starts agent replicas itself, so each replica gets a credential of its own. The bearer token must be a provisioner token (whp_); an agent credential is rejected.

Request

Request bodyoptional

A name for the instance, unique within the agent. Reviewers see it next to the agent's name.

Any JSON object to store with the instance, such as a region or pod name.

How long the credential stays valid, in seconds. A value above the provider's default is reduced to the default.

Response

Response codes

StatusBodyDescription
201AgentInstanceCredential

The new instance and its credential. The credential's token appears only in this response.

400ErrorResponse

Error response. A 403 from a permission check carries ForbiddenDetails in error.details.

401ErrorResponse

Error response. A 403 from a permission check carries ForbiddenDetails in error.details.

409

An instance with this name already exists for the agent

Response body201

One running copy of an agent.

Example

POST /api/aap/v1/instances
curl -X POST "$WITHHUMAN_URL/api/aap/v1/instances" \
  -H "Authorization: Bearer $WITHHUMAN_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
  "instance_name": "ci-runner-07",
  "instance_metadata": {
    "region": "eu-west"
  },
  "ttl_seconds": 86400
}'
201 response
{
  "agent_instance": {
    "id": "7ab8c8ec-7b2d-4fd6-9b52-752f9515eb71",
    "organization_id": "7ab8c8ec-7b2d-4fd6-9b52-752f9515eb71",
    "agent_slug": "string",
    "name": "ci-runner-07",
    "status": "active",
    "metadata": {
      "region": "eu-west"
    },
    "created_at": "2026-09-08T12:02:11Z",
    "last_seen_at": "2026-09-08T12:02:11Z"
  },
  "credential": {
    "id": "7ab8c8ec-7b2d-4fd6-9b52-752f9515eb71",
    "token": "whc_live_3f6b9c1d0e7a4b2c",
    "expires_at": "2026-09-08T12:02:11Z"
  }
}

Create an approval request

POST/api/aap/v1/requestsBearer tokenRequires request.create

Asks for approval to run a tool call. The organization's approval pipeline evaluates the request first and may approve or deny it on the spot, in which case the response already carries the decision. Otherwise the request goes to reviewers and comes back as pending; retrieve it with a wait to learn the outcome. A request nobody decides before its timeout becomes expired: the adapter does not run the call and tells the agent the approval timed out. An adapter that stops waiting should cancel the request so reviewers stop seeing it. Requires an Idempotency-Key header scoped to this operation and the authenticated instance. Reusing it with changed input returns 409.

Request

Parameters

ParameterTypeDescription
Idempotency-Keyrequiredheader · string

A key of your choosing that identifies this call, so a retry does not act twice. See Idempotency in the API overview.

Request body

toolstringrequired

The name under which the tool is defined: the name an MCP server advertises in tools/list, or the runtime's own name for a built-in tool such as Bash. Never the runtime's joined spelling such as mcp__stripe__issue_refund.

serverstring

The alias of the MCP server that defines the tool, as the runtime configured it. Present only for tools served over MCP. A label the adapter observed, not a verified identity.

argumentsobjectrequired

The exact arguments the tool will run with if approved. Reviewers see this as the description of the action, so it must be complete.

The agent's own explanation of why it wants to do this. Reviewers see it as a claim from the agent, separate from the arguments.

contextobject

Where the call comes from, as observed by the adapter rather than stated by the agent: for example the runtime, session id, or working directory.

timeoutstringrequired

How long the request may wait for a decision, as a duration such as 30m or 24h. Between one second and seven days. Once it passes, the request expires.

Response

Response codes

StatusBodyDescription
201AAPApprovalRequest

The request, either already decided by the pipeline or pending review

400ErrorResponse

Error response. A 403 from a permission check carries ForbiddenDetails in error.details.

401ErrorResponse

Error response. A 403 from a permission check carries ForbiddenDetails in error.details.

409

The idempotency key was already used for a different request

429

The organization has reached its limit of pending requests

Response body201

iduuidrequired
toolstringrequired
serverstring
argumentsobjectrequired
timeoutstringrequired
contextobject
statusenumrequired

One of pending, approved, denied, expired, cancelled

created_atdate-timerequired
deadline_atdate-timerequired

Immutable decision. Approved calls must start within five minutes of decided_at. Reads and retries never extend expires_at.

Example

POST /api/aap/v1/requests
curl -X POST "$WITHHUMAN_URL/api/aap/v1/requests" \
  -H "Authorization: Bearer $WITHHUMAN_TOKEN" \
  -H "Idempotency-Key: 4f1c9a2e-7b3d-4e8f-a1c5-2d6b8e0f9a31" \
  -H "Content-Type: application/json" \
  -d '{
  "tool": "issue_refund",
  "server": "stripe",
  "arguments": {
    "amount": 4900,
    "reason": "duplicate_charge"
  },
  "agent_reasoning": "Refunding the duplicate charge for [email protected].",
  "context": {
    "run_id": "4821",
    "framework": "claude-code"
  },
  "timeout": "30m"
}'
201 response: Pending review
{
  "id": "7ab8c8ec-7b2d-4fd6-9b52-752f9515eb71",
  "tool": "issue_refund",
  "arguments": {
    "amount": 4900,
    "reason": "duplicate_charge"
  },
  "timeout": "30m",
  "status": "pending",
  "created_at": "2026-09-17T12:00:00Z",
  "deadline_at": "2026-09-17T12:30:00Z"
}
201 response: Approved with a five-minute execution window
{
  "id": "7ab8c8ec-7b2d-4fd6-9b52-752f9515eb71",
  "tool": "issue_refund",
  "arguments": {
    "amount": 4900,
    "reason": "duplicate_charge"
  },
  "timeout": "30m",
  "status": "approved",
  "created_at": "2026-09-17T12:00:00Z",
  "deadline_at": "2026-09-17T12:30:00Z",
  "decision": {
    "status": "approved",
    "note": "Refund the duplicate charge.",
    "decided_at": "2026-09-17T12:02:00Z",
    "expires_at": "2026-09-17T12:07:00Z"
  }
}

Retrieve an approval request

GET/api/aap/v1/requests/{id}Bearer tokenRequires request.read

Returns the request and, once it has been decided, its decision. Pass wait to hold the response until the request is decided or the wait runs out, whichever comes first. Only the instance that created the request may retrieve it. A request stops changing once it is approved, denied, expired, or cancelled.

Request

Parameters

ParameterTypeDescription
idrequiredpath · uuid

The request's id.

waitquery · string

How long to hold the response for a decision, as a duration such as 30s. Capped at 30 seconds. Omit to return immediately.

Response

Response codes

StatusBodyDescription
200AAPApprovalRequest

The request in its current state

401ErrorResponse

Error response. A 403 from a permission check carries ForbiddenDetails in error.details.

404ErrorResponse

Error response. A 403 from a permission check carries ForbiddenDetails in error.details.

Response body200

iduuidrequired
toolstringrequired
serverstring
argumentsobjectrequired
timeoutstringrequired
contextobject
statusenumrequired

One of pending, approved, denied, expired, cancelled

created_atdate-timerequired
deadline_atdate-timerequired

Immutable decision. Approved calls must start within five minutes of decided_at. Reads and retries never extend expires_at.

Example

GET /api/aap/v1/requests/{id}
curl -X GET "$WITHHUMAN_URL/api/aap/v1/requests/{id}?wait=30s" \
  -H "Authorization: Bearer $WITHHUMAN_TOKEN"
200 response: Pending review
{
  "id": "7ab8c8ec-7b2d-4fd6-9b52-752f9515eb71",
  "tool": "issue_refund",
  "arguments": {
    "amount": 4900,
    "reason": "duplicate_charge"
  },
  "timeout": "30m",
  "status": "pending",
  "created_at": "2026-09-17T12:00:00Z",
  "deadline_at": "2026-09-17T12:30:00Z"
}
200 response: Approved with a five-minute execution window
{
  "id": "7ab8c8ec-7b2d-4fd6-9b52-752f9515eb71",
  "tool": "issue_refund",
  "arguments": {
    "amount": 4900,
    "reason": "duplicate_charge"
  },
  "timeout": "30m",
  "status": "approved",
  "created_at": "2026-09-17T12:00:00Z",
  "deadline_at": "2026-09-17T12:30:00Z",
  "decision": {
    "status": "approved",
    "note": "Refund the duplicate charge.",
    "decided_at": "2026-09-17T12:02:00Z",
    "expires_at": "2026-09-17T12:07:00Z"
  }
}

Cancel an approval request

DELETE/api/aap/v1/requests/{id}Bearer tokenRequires request.create

Withdraws a pending request. Use it when the runtime that asked stops waiting for the answer: it was interrupted, timed out locally, or is shutting down. Reviewers stop seeing the request, notifications stop, and anyone holding it is released. Only the instance that created the request may cancel it; another instance of the same agent gets a 403. A cancelled request is final and answers cancelled with a protocol decision, so a long poll in flight returns at once. Cancelling again returns the same cancelled request. A request that was already approved, denied, or expired is unchanged and answers 409 with code already_terminal. This is an optional capability of the Agent Approval Protocol: adapters must treat a 404 or 405 from a provider that does not offer it as nothing to do.

Request

Parameters

ParameterTypeDescription
idrequiredpath · uuid

The request's id.

Response

Response codes

StatusBodyDescription
200AAPApprovalRequest

The request, now cancelled

401ErrorResponse

Error response. A 403 from a permission check carries ForbiddenDetails in error.details.

403ErrorResponse

Error response. A 403 from a permission check carries ForbiddenDetails in error.details.

404ErrorResponse

Error response. A 403 from a permission check carries ForbiddenDetails in error.details.

409

The request was already decided or expired (code already_terminal)

Response body200

iduuidrequired
toolstringrequired
serverstring
argumentsobjectrequired
timeoutstringrequired
contextobject
statusenumrequired

One of pending, approved, denied, expired, cancelled

created_atdate-timerequired
deadline_atdate-timerequired

Immutable decision. Approved calls must start within five minutes of decided_at. Reads and retries never extend expires_at.

Example

DELETE /api/aap/v1/requests/{id}
curl -X DELETE "$WITHHUMAN_URL/api/aap/v1/requests/{id}" \
  -H "Authorization: Bearer $WITHHUMAN_TOKEN"
200 response: Cancelled by the adapter
{
  "id": "7ab8c8ec-7b2d-4fd6-9b52-752f9515eb71",
  "tool": "issue_refund",
  "arguments": {
    "amount": 4900,
    "reason": "duplicate_charge"
  },
  "timeout": "30m",
  "status": "cancelled",
  "created_at": "2026-09-17T12:00:00Z",
  "deadline_at": "2026-09-17T12:30:00Z",
  "decision": {
    "status": "cancelled",
    "note": "The agent stopped waiting.",
    "decided_at": "2026-09-17T12:02:00Z"
  }
}