Skip to main content

Human-in-the-Loop Approvals

Agents operate autonomously, but some actions need human sign-off. 1claw's approval system lets agents request permission for sensitive operations while humans retain final control. Approvals work through the dashboard, API, or the mobile companion app.

How it works​

Agent                           1claw                         Human
| | |
| POST /v1/approvals/request | |
| { action, summary, reason } | |
| ----------------------------> | |
| | Create pending approval |
| | Route to agent's creator |
| | --------------------------> |
| | |
| | Human reviews |
| | (dashboard, mobile, API) |
| | |
| | POST /v1/approvals/{id}/ |
| | decide |
| | <------------------------- |
| | |
| <-- approval result -------- | |
| (approved or denied) | |
  1. The agent creates an approval request: what the action is, a summary for the human, and a payload describing what will actually happen.
  2. 1claw derives the risk tier from the agent's approval policy and the payload, and routes the request to the human who registered the agent (agents.created_by).
  3. The human reviews and decides (approve or deny) through the dashboard, mobile app, or API.

Which actions an agent may request​

Two families:

  • Business actions, named namespace.verb — refund.create, social.post, invoice.send. These are yours to define; 1claw does not act on them, it asks a human and tells you the answer.
  • Access requests — access_request, policy_request, binding_request. These ask a human to widen what the agent itself may do.

Actions that 1claw executes on your behalf when approved — policy_change, card_order, agent_transaction, agent_execution, agent_sign_intent — are created by the platform and rejected on this endpoint with a 403. They are raised for you when you call the endpoint that needs them (ordering a card, say), because the wording a human reads and the side effect that follows have to come from the same place.

Creating an approval request​

curl -X POST https://api.1claw.co/v1/approvals/request \
-H "Authorization: Bearer $AGENT_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"action": "refund.create",
"target_type": "order",
"target_id": "order_1234",
"summary": {
"title": "Refund $49.99",
"body": "Customer says the order arrived damaged.",
"fields": [
{ "label": "Order", "value": "#1234" },
{ "label": "Reason", "value": "Damaged on arrival" }
]
},
"payload": {
"amount_usd": "49.99",
"customer_email": "a.user@example.com"
},
"reason": "Customer support ticket #5567"
}'

The response is 202 with the approval id, plus risk_tier — the tier actually enforced — and human_summary, the line your operator will see.

summary and payload are different things​

summary is what the human reads. payload is what happens if they approve.

Keep them accurate to each other. The risk tier and, later, the auto-approval learning are derived from payload, never from summary — so a flattering summary changes what your operator reads and nothing about how strong the approval has to be. Both are shown in the dashboard, side by side, which means a summary that disagrees with its payload is visible rather than persuasive.

Risk tiers​

TierMeaningMobile app behavior
1InformationalSimple tap to approve
2Moderate riskBiometric verification required
3High riskTOTP code + countdown timer

The server sets the tier. You may send declared_risk_tier to say how sensitive you believe an action is, and 1claw takes the higher of that and the floor it derives itself:

  • an amount at or above the threshold in the agent's action_approval_policy (default $50) is at least tier 2;
  • a recipient the agent has not paid before is at least tier 2, whatever the amount;
  • actions that grant or destroy authority are tier 3.

So you can ask for a stricter review than the policy requires. You cannot ask for a weaker one: an agent declaring tier 1 on a $500 refund gets tier 2, and the response returns both risk_tier (enforced) and declared_risk_tier (asked for), with declared_below_floor: true. Your operator sees that too — an agent that keeps under-declaring is worth looking at.

risk_tier is still accepted as the old name for declared_risk_tier.

Telling 1claw when to ask​

Set action_approval_policy on the agent — in the dashboard under the agent's guardrails, or over the API:

{
"default_mode": "deny",
"rules": [
{
"action_type": "refund.create",
"mode": "approve",
"require_for_amount_above_usd": "50",
"summary_template": "Refund {{amount_usd}} to {{customer_email}}"
}
]
}

summary_template is filled from the payload and becomes the line sent to SMS, push, and email. If a named field is missing from the payload, no summary is sent rather than half a sentence.

Editing this policy counts as widening a guardrail — raising a threshold or switching a rule to allow takes a human out of the loop — so it goes through the same approval flow as loosening a transaction limit. Malformed rules are rejected when you save them, not ignored when they are read.

Deciding an approval​

# Approve
curl -X POST "https://api.1claw.co/v1/approvals/$APPROVAL_ID/decide" \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{"decision": "approved"}'

# Deny
curl -X POST "https://api.1claw.co/v1/approvals/$APPROVAL_ID/decide" \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{"decision": "denied"}'

Auto-execution on approval​

Some approvals do something when approved: 1claw applies the policy change, places the card order, or submits the transaction, so the human does not have to go and do it afterwards.

Those approvals are created by 1claw itself, on the endpoint that needs them — which is why an agent cannot request one directly. A business action is the other way round: 1claw records the decision and your code acts on it.

Listing pending approvals​

curl -s "https://api.1claw.co/v1/approvals?status=pending" \
-H "Authorization: Bearer $TOKEN" \
| jq '.approvals[] | {id, action, human_summary, risk_tier, created_at}'

An agent may call the same route with its own token and gets only the approvals it created — enough to find one whose id was lost across a restart, never the organization's queue. total is the agent's own pending count.

Cancelling a request you made​

An agent that answered locally, or no longer needs the decision, withdraws the request so the human is not asked for something already settled:

curl -s -X POST "https://api.1claw.co/v1/approvals/$APPROVAL_ID/cancel" \
-H "Authorization: Bearer $AGENT_TOKEN" -H "Content-Type: application/json" \
-d '{"reason":"answered locally"}'

First answer wins: if the human decided before the cancel landed, the response is the existing decision (200) and nothing changes. A cancellation reaches webhook subscribers as approval.decided with decision: "cancelled" and is audited as approval.cancelled. The human it was addressed to may cancel too. SDK: client.approvals.cancel(id, reason?); Python client.approvals.cancel(id, reason=); MCP cancel_approval.

Dashboard​

The approval inbox is at /approvals in the dashboard. It shows:

  • Pending, approved, and denied requests
  • Risk tier badges (color-coded)
  • Agent name and the human who created the agent
  • The plain-language summary, its fields, and separately the payload
  • Approve/Reject buttons

The sidebar shows a badge with the count of pending approvals.

Mobile companion app​

The mobile app (iOS, TestFlight) provides push-notification-based approval. When an agent submits a request:

  1. The human receives a push notification
  2. Tapping opens the approval detail screen
  3. Risk tier determines the verification step:
    • Tier 1: tap "Approve"
    • Tier 2: Face ID / Touch ID
    • Tier 3: Enter TOTP code within countdown

Patterns​

Agent requests access to new secrets​

// Agent realizes it needs a secret it cannot access
try {
await agentClient.secrets.get(vaultId, "production/stripe-key");
} catch (err) {
if (err.status === 403) {
// Request access through the approval flow
await agentClient.approvals.request({
action: "access_request",
target_type: "agent",
target_id: agentId,
summary: {
vault_id: vaultId,
path_pattern: "production/stripe-key",
permissions: ["read"],
},
reason: "Need Stripe key to process refunds",
});
}
}

Agent requests a higher spend limit​

await agentClient.approvals.request({
action: "policy_request",
target_type: "agent",
target_id: agentId,
summary: {
update_agent: {
tx_daily_limit_eth: "10.0",
},
},
reason: "Portfolio rebalancing requires larger daily limit during high-volatility periods",
declared_risk_tier: 3,
});

Gate high-value transactions on approval​

For this pattern, the agent checks with its own logic before submitting a transaction:

const txValue = 5.0; // ETH
const APPROVAL_THRESHOLD = 1.0;

if (txValue > APPROVAL_THRESHOLD) {
const approval = await agentClient.approvals.request({
action: "transaction",
target_type: "transaction",
target_id: agentId,
summary: `Transfer ${txValue} ETH to 0xRecipient... on Ethereum`,
reason: "Large position exit required by strategy",
risk_tier: 3,
});

// Poll for decision (or set up a webhook)
// Only proceed if approved
}

// Submit the transaction
const tx = await agentClient.agents.submitTransaction(agentId, {
chain: "ethereum",
to: "0xRecipient...",
value: txValue.toString(),
});

Combine with treasury proposals​

For multisig operations, use treasury proposals instead of approvals. Proposals collect EIP-712 signatures and auto-execute when the threshold is met. See Safe integration.

For non-multisig operations (policy changes, guardrail updates, access requests), use the approval system described here.

Webhooks​

Set up a webhook to get notified when approvals are decided:

curl -X POST https://api.1claw.co/v1/webhooks \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{
"url": "https://your-server.com/webhooks/1claw",
"events": ["policy.created", "policy.updated", "policy.deleted"],
"secret": "your-webhook-signing-secret"
}'

Further reading​