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

# ActionApprovals

> Understand the exact human decision that can sit inside a governed action path.

An **ActionApproval** is a durable human decision about one exact frozen action. Checkfu creates one when an ActionPolicy returns `require_approval`; clients do not create approvals directly.

The surrounding authorization path depends on the action being reviewed.

<Warning>
  The Checkfu CLI and TypeScript SDK are private-alpha artifacts with no self-service public installation channel.
  Part of this flow is behind a rollout fence; a deployment that has not enabled it refuses the request rather than running it.
  Confirm [capability status](/getting-started/status) before making availability part of your application's contract.
</Warning>

## Two different governance paths

### Connected and platform ToolInvocations

A governed Connection or platform ToolInvocation keeps the complete ladder:

```text theme={"theme":{"light":"github-light","dark":"github-dark"}}
PermissionAssignment
  -> ActionPolicy
  -> ActionApproval, when policy requires a person
  -> live authority recheck
  -> trusted effect
```

PermissionAssignment answers whether the acting subject may use the governed resource. ActionPolicy then returns `allow`, `require_approval`, or `deny`. An ActionApproval records the human decision only for the exact action that reached the approval branch.

<Warning>
  Approval is not permission. It cannot fill in a missing PermissionAssignment, override a denial, restore a revoked Connection, or authorize a different ToolInvocation from the one reviewed.
</Warning>

### Custom tools declared by the admitted Agent

A Custom-tool call has no Connection, ToolSource, provider credential, or PermissionAssignment in its **invocation** path. The admitted Agent already declares the Tool. Checkfu evaluates ActionPolicy directly:

```text theme={"theme":{"light":"github-light","dark":"github-dark"}}
ActionPolicy
  -> allow: wait for the application result
  -> deny: do not execute the handler
  -> require_approval: ActionApproval
       -> run.action_authorized
       -> application executes the recorded call
       -> user.custom_tool_result settles the wait
```

With no restricting ActionPolicy, the Custom-tool call proceeds to its application-result wait. When approval is required, `run.action_authorized` is the durable fact that the exact call may be executed. An ActionApproval does not execute customer code, mint a provider-effect proof, or post the result.

Reviewer authorization is separate from the Custom-tool invocation path. A trusted backend that asserts an end-user reviewer with `acted_as` must prove that the Principal holds an active `approve` PermissionAssignment on the resource mapped from the approval subject and survives the current ActionPolicy ladder. That does not add PermissionAssignment to the Tool call itself.

## What is frozen

The ActionApproval binds the decision to the exact action that requested it.

A ToolInvocation review can name the Session, Run, acting Principal, Agent version, Connection and Connection revision, ToolSource and Tool versions, schema hash, and the permitted outbound destination. A Custom-tool review instead names the admitted Agent, logical Tool name, `tool_use_id`, Session, Run, and optional surface coordinate. It does not invent a Connection, ToolSource, outbound request, or thread coordinate.

The public ActionApproval resource exposes argument identity in its top-level `arguments_hash`, not argument values. Its `context_summary` carries the kind-specific frozen coordinates. Query strings, headers, request bodies, and custodied Credential values are not returned in the review resource.

For a Custom tool, the exact arguments already appear in the preceding `agent.tool_use` event. Prove that the payload shown to a reviewer is the frozen payload before presenting it as such:

```ts theme={"theme":{"light":"github-light","dark":"github-dark"}}
import { canonicalArgumentsHash } from "@checkfu/sdk"

const matches =
  (await canonicalArgumentsHash(recordedArguments)) ===
  approval.arguments_hash
```

If the hash does not match, do not execute or present that payload as the approved action.

<Note>
  The resource and event use different field names in the current wire contract: the ActionApproval resource carries top-level `arguments_hash`, while `action_approval.pending` carries overlapping authority under `frozen_context` with `arg_hash`. Match the surface you are reading.
</Note>

## Lifecycle

| Status | Meaning |
| - | - |
| `pending` | Waiting for an authorized responder |
| `approved` | A person approved the frozen action |
| `denied` | A person refused the action |
| `consumed` | A gateway proof was redeemed for its one ToolInvocation |
| `expired` | The decision window closed before use |

A connected ToolInvocation approval is time-bounded and single-use. The approve response can carry a deterministic proof token; redemption moves the ActionApproval to `consumed`. `GET /v1/action-approvals/{id}` never reveals that proof.

A Managed-tool purchase approval carries no redeemable provider-effect proof or provider credential. The decision stays bound to its original invocation, selection, quoted terms and expiry; execution and result release still require their separate current authority.

A Custom-tool approval carries no provider-effect proof. Its approved decision advances the exact wait and produces `run.action_authorized`; the application then supplies `user.custom_tool_result`.

## Responding

The public resource exposes:

* `GET /v1/action-approvals` for backfill and reconciliation;
* `GET /v1/action-approvals/{id}` for one frozen decision; and
* `POST /v1/action-approvals/{id}/responses` for `approve` or `deny` with optimistic concurrency.

Send the current `version` as `expected_version`. Optional `instructions` are recorded with the resolution. Use one stable `Idempotency-Key` for the logical response and reuse it only for an exact retry.

A keyed retry can replay the captured HTTP response. It does not extend approval expiry, make a consumed proof reusable, or turn a proof-free Custom-tool response into a bearer credential.

For CMA-shaped clients, the reviewer may instead send `user.tool_confirmation` to the ordinary Session event endpoint with the same `tool_use_id`. `allow` moves the same wait into its Custom-result phase; it does not authorize the platform to execute customer code or replace the later `user.custom_tool_result`. Authenticated server context supplies event attribution; clients do not send `authored_by`.

<Card title="Handle an approval" icon="user-check" href="/guides/handle-an-approval">
  Build the event-driven queue, distinguish the two action paths, and continue safely.
</Card>

## Who may respond

A root or admin API key can respond under the role path. The audit record names the authenticated key.

A trusted control-plane backend can instead include `acted_as` to assert an end-user Principal. The current responder contract then verifies that the backend may assert people and that the Principal has live approval authority on the resource mapped from the approval subject. A matching ActionPolicy denial still wins. Runner and model credentials cannot approve their own work.

This responder check answers **who may make the human decision**. It does not change whether the underlying Custom-tool invocation uses PermissionAssignment; it does not.

## Standing approvals

An eligible connected ToolInvocation approval can mint a bounded **StandingApproval** for future matching calls. It is a separate, listable, revocable resource.

A StandingApproval can only downgrade `require_approval` to `allow` for its exact subject, Connection, Tool, scope, revision, and expiry. It never:

* overrides `deny`;
* fills in a missing PermissionAssignment;
* applies to a Custom-tool call;
* survives Connection rotation; or
* widens beyond its recorded argument scope.

Revoking it affects the next matching ToolInvocation immediately. Prior decisions and audit records remain intact.

## Revocation after approval

Connected ToolInvocations recheck live authority immediately before provider I/O. Any of these changes can refuse an already-approved action:

| Change after review | Result |
| - | - |
| Connection revoked, unhealthy, or no longer callable | Refused before provider I/O |
| Required PermissionAssignment removed or expired | Refused |
| ActionPolicy now denies | Refused |
| Tool schema or Connection revision changed | Frozen-context mismatch; request again |
| Approval expired or was already consumed | Refused |

Approval records what a person decided. It does not preserve stale authority.

After a connected ToolInvocation completes, the gateway checks current Run
authority before returning its result or offering a spill, and again after a
spill wait. When the Run is canceled, expired, or its authority service is
unavailable, the gateway withholds its response and spill sidecar. The
Run-authority error explains that the provider call completed; retries of that
ToolInvocation cannot repeat the effect. A spill implementation owns authority
enforcement during its own asynchronous write. This Run check does not
establish permission to share the result with another audience.

A Custom-tool approval has no provider-side recheck because Checkfu does not perform that effect. Your application must still enforce its own tenant, account, and row-level authorization immediately before executing the handler.

## Audit and uncertain effects

The Session event log records when an ActionApproval becomes pending and when it resolves. Audit records the authenticated responder, optional asserted Principal, frozen coordinates, policy path, and trusted effect outcome where Checkfu owns the effect. Neither surface returns custodied Credential values.

A ToolInvocation can settle as completed, denied, or indeterminate. `indeterminate` means Checkfu cannot prove whether the provider accepted the effect. Recovery must reconcile that uncertainty instead of treating it as a safe automatic retry.

For a Custom tool, the customer application owns effect safety. Use provider-supported idempotency, a durable effect ledger, or explicit reconciliation before retrying an ambiguous remote result.

## Questions are not approvals

A Question asks a person for ordinary input. It grants no authority, has no approval proof, and is answered with `user.question_answer`. Both Questions and ActionApprovals can park a Run, so branch on the action kind rather than treating every wait as permission to act.


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