> ## Documentation Index
> Fetch the complete documentation index at: https://docs.oneshotagent.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Approvals

> Let a named person approve an agent's action before it runs, by policy on paid calls or on request

<img src="https://mintcdn.com/freebutter/jPFJoUgC4P5j3KGx/images/objects/approvals.webp?fit=max&auto=format&n=jPFJoUgC4P5j3KGx&q=85&s=d70983001b4201d0420c8cd126d31cc5" alt="" className="docs-object" noZoom width="1200" height="600" data-path="images/objects/approvals.webp" />

An **approval** is a named person's decision about one agent action. The person gets a one-tap link by email or SMS and needs no OneShot account. Every decision is recorded as a signed receipt. An agent that can't be audited isn't autonomous. It's unsupervised.

Approvals work in two ways:

* **Action policy.** Rules on your agent decide which email, SMS, voice and commerce calls run, which are denied, and which wait for a person. A held call is not run and not charged.
* **Ask directly.** Your agent asks a person to decide something (`POST /v1/approvals`) and acts on the answer itself.

An agent with **no policy is unaffected** until you set one.

## Action policy

A policy is a set of rules on the agent. The API checks it before any money moves.

```typescript theme={null}
await agent.policy.set({
  approver: { email: 'ops@acme.com' },          // and/or phone: '+14155550123'
  timeout_seconds: 3600,                        // unanswered = denied
  rules: [
    { id: 'big-orders', tool: 'commerce', when: { amount_over_usdc: 50 }, action: 'require_approval' },
    { id: 'new-domains', tool: 'email', when: { recipient_domain_not_in: ['acme.com'] }, action: 'require_approval' },
    { id: 'night-calls', tool: 'voice', when: { outside_hours: { timezone: 'America/New_York', start: '09:00', end: '18:00' } }, action: 'deny' },
  ],
});
```

A rule has a `tool` (`email`, `sms`, `voice`, `commerce`, `physical_mail`, `linkedin_invite`, `linkedin_invite_withdraw`, `linkedin_react`, `linkedin_reply`, `linkedin_message`, `build`, `browser`, or `*`), conditions under `when`, and an `action` (`require_approval` or `deny`). A rule matches only when all its conditions hold. If several rules match, `deny` wins over `require_approval`.

| Condition | Matches when |
| - | - |
| `always: true` | every call of that tool |
| `amount_over_usdc` | the quoted charge is above this amount |
| `recipients_over` | the call has more recipients than this |
| `recipient_domain_not_in` | any email recipient's domain is not in the list (email only) |
| `outside_hours` | the call happens outside these hours in that time zone (`days`: 0 = Sunday, default Monday to Friday) |

The policy is checked on every paid route with a side effect outside OneShot: `email/send`, `sms/send`, `voice/call`, `commerce/buy`, `physical-mail/send`, `linkedin/invite`, `linkedin/invite/withdraw`, `linkedin/react`, `linkedin/reply`, `linkedin/message`, `build` and `browser`. It runs once the price is known and **before any payment is settled or credit is debited**. Routes that only read (research, enrichment, search, verification, `linkedin/profile-view`, `linkedin/sync`) are not gated. For `build` and `browser`, the rule sees what the quote will run, not the request body. Only a wallet session can set it; an access token can read it but not change it.

### When a call is held

The call returns `403` and nothing is sent or charged:

```json theme={null}
{
  "error": "approval_required",
  "status": "pending_approval",
  "approval_id": "appr_01J...",
  "approval": { "status": "pending", "action_summary": "Buy 1 × Standing desk ($412.00)", "expires_at": "..." }
}
```

The approver is paged once per distinct call. Retrying the same call while it is pending returns the same approval. After approval, repeat **the same call** with the approval id; it runs once:

```typescript theme={null}
try {
  await agent.commerceBuy(order);
} catch (err) {
  if (!(err instanceof ApprovalRequiredError)) throw err;
  const decided = await agent.approvals.waitFor(err.approvalId);
  if (decided.status === 'approved') {
    await agent.commerceBuy({ ...order, approvalId: err.approvalId });
  }
}
```

Over HTTP, send the id in the `X-Approval-Id` header. An approval releases only the action it was granted for (same recipients, content, product and quantity), within 24 hours of the decision. A second use returns `409 approval_already_used`. If the released call fails before dispatch (for example, a payment that doesn't settle), the approval is restored so you can retry.

A call a rule denies returns `403 action_denied_by_policy` with the rule id. The SDKs raise `ApprovalRequiredError` and `ActionDeniedError`.

## Asking directly

`POST /v1/approvals` asks a person a question. Your agent reads the answer and acts on it.

```typescript theme={null}
const approval = await agent.approvals.create({
  action_summary: 'Send the signed LOI to Acme',
  preview: { contact: 'jane@acme.com', value: 'pilot, 4 weeks' },
  approver: { phone: '+14155550123' },
  expires_in_seconds: 7200,
});
const decided = await agent.approvals.waitFor(approval.approval_id);
```

These approvals are informational. They can't release a held paid call.

## What the approver sees

The link opens a page with the summary, the preview, and **Approve** / **Deny** buttons (with an optional note). Opening the link decides nothing, so email scanners that follow links can't approve by accident. Each link works once and expires at the deadline. An unanswered approval is **denied**.

## Compute goals

When a compute goal needs human input and your policy has an approver, the approver gets the same kind of link. Their decision resolves the goal's `human_approval` task, as `POST /v1/compute/:goalId/respond` would. If you answer through `/respond` first, the link stops working.

## Receipts

Every decision (approved, denied, expired, or cancelled) writes a signed `$0` receipt in the `approval` subcategory. It records who was asked (as a digest), the decision, when and how it was made, and a digest of what the approver saw. A call released by an approval has `approvalId` in its receipt metadata, so the action and the decision link to each other.

## Endpoints

| Method | Path | |
| - | - | - |
| `GET` | `/v1/agents/me/action-policy` | Current policy, or `null` |
| `PUT` | `/v1/agents/me/action-policy` | Replace the policy (wallet only) |
| `DELETE` | `/v1/agents/me/action-policy` | Remove the policy (wallet only) |
| `POST` | `/v1/approvals` | Ask a person directly |
| `GET` | `/v1/approvals` | List approvals (`?status=`, `?limit=`) |
| `GET` | `/v1/approvals/:id` | Read one approval |
| `POST` | `/v1/approvals/:id/cancel` | Withdraw a pending approval |

All use the same signed agent proof as budgets. An agent can have at most 20 pending approvals at once.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.