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

# Action approvals: the human-in-the-loop gate

> The model behind Orby's approval card — which tool calls propose an action instead of executing it, how a proposal moves from pending to approved, rejected, or expired, who can propose versus who can decide, and the durable event trail each transition writes.

# Action approvals: the human-in-the-loop gate

Large parts of the [Orby operator assistant](/concepts/orby-operator-assistant)
are read-only and harmless. The danger part is its tools: Orby can send a
message, reassign an inbox conversation, pause a campaign, or scaffold a
draft agent. When a turn calls one of those tools, the call does not execute.
It raises a **pending action** — a durable, auditable proposal with the exact
arguments and an estimated cost — and a person must approve it before the
tool runs. That gate is the action-approval model; this page explains its
rules so you can reason about any proposal Orby raises, now or after new
tools are registered.

## 1. What an approval gate is

Every Orby tool declares a **confirmation policy**, and the gate consults
three facts before it lets any tool execute:

* **Policy `never`.** The tool runs inline in the turn: reads, searches,
  summaries, drafts, cost previews. No proposal is created.
* **Policy `always`.** The tool pauses at a proposal. A pending action is
  created and nothing executes until an operator approves it. This is the
  policy assigned to chargeable and side-effecting tools — message sends,
  conversation reassignment, campaign state changes.
* **Policy `threshold`.** The tool pauses only when its estimated cost
  meets or exceeds a dollar threshold. A chargeable tool's own cost
  estimator runs before the gate consults the amount, so the card always
  quotes the real price it is asking you to approve.

Two additional gate sources sit on top of the declared policy, as
defense-in-depth:

* **Destructive verb names.** A tool whose name contains a destructive verb
  — delete, remove, release, revoke, cancel, purge, drop, refund, charge,
  disable — is gated regardless of its declared policy, so a forgotten
  `always` on a dangerous verb cannot bypass review.
* **Fail-closed persistence.** A proposal that cannot be recorded never
  executes. The tool is refused outright with
  `PENDING_ACTION_PERSIST_FAILED` rather than slipping through ungated.

A proposal is never "whatever the model meant." The pending action records
the tool name, the exact validated arguments, the proposing operator, the
estimated cost, and a deterministic idempotency key. What you approve is
that concrete payload, and the audit trail keeps it.

## 2. Proposal lifecycle: pending → approved / rejected / expired

A pending action has one of six states: `pending`, `executing`, `done`,
`cancelled`, `undone`, and `failed`. The gate transitions a proposal along
one of three paths:

* **Approve.** The approve call atomically claims the row (`pending` →
  `executing`), re-checks ownership, re-validates the persisted arguments
  against the tool's input schema, re-checks the operator's role at
  decision time rather than proposal time, and then runs the executor. On
  success the row ends at `done` with its result; on a thrown executor it
  ends at `failed` with an error code.
* **Reject.** The reject call atomically marks the row `cancelled`. The
  tool never runs. Cancellation is terminal — a later approve on the same
  row is refused.
* **Expire.** A proposal's window is five minutes. When the window elapses
  without a decision, the row is cancelled with `PENDING_ACTION_EXPIRED`
  and nothing executes. The expiry exists so a stale proposal cannot be
  approved after its context has moved — and so an abandoned browser tab
  never leaves a "ready to fire" row hanging for hours.

Two terminal-state rules make the gate safe for retries and races:

* **Single resolution.** Only the first approve or reject that claims the
  row wins. A second approval attempt, or two operators racing the same
  card, is refused with `PENDING_ACTION_RACE` /
  `PENDING_ACTION_NOT_APPROVABLE`, so an action can never run twice.
* **Delayed-fire window.** Approved destructive tools can carry a short
  undo window (\~30 seconds) before the executor fires, recorded on the same
  row; cancelled by the operator inside that window the row ends `undone`.

The full error-code vocabulary and its recovery playbooks live in
[Troubleshooting: Orby pending-action approval](/troubleshooting/orby-tool-approval).

## 3. Actor model: who requests, who decides

The approval gate is deliberately asymmetric — the act of proposing is
open to the assistant, the act of executing is reserved for the operator.

| Question                        | Answer                                                                                                                                                                                                                                |
| ------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Who can **request** a proposal? | Orby, acting as the signed-in operator, whenever a turn invokes a gated tool. The proposal is pinned to the proposing operator's user id, the operator's organization, and (for org-configurable gates) the tenant id.                |
| Who can **decide** it?          | The same operator who proposed it — every approve/reject/poll route enforces ownership on (operator, organization). A teammate cannot approve another operator's proposal; the clean hand-off is to re-ask Orby for a fresh proposal. |
| Which roles reach the gate?     | All operator roles — owner, admin, developer, viewer, billing — may run the gate (approve/reject/poll); the role gate sits after the auth middleware and rejects API-key-authenticated requests outright.                             |
| What re-verifies the decision?  | At approve time, the tool's `requiredRoles` set is re-checked against the operator's **current** role. A role demotion between proposal and approval closes the gate.                                                                 |

Two structural properties are worth internalizing:

* **The gate re-grounds on approval.** The tool arguments are re-read and
  re-validated at decision time. If the conversation you were reassigning
  was already moved, or the persisted args no longer match the tool's
  schema, the approval refuses to run on stale input — it does not act on
  a snapshot of the past.
* **The gate pins the identity, not the session.** The proposing operator's
  user id is recorded on the row, and the decision routes re-load that id
  at decision time. A delegated or leaked credential cannot decide someone
  else's proposal.

## 4. The durable event trail

Orby does not dispatch platform webhooks for gate transitions — the gate
is an operator-facing surface, not a customer-facing event plane. What it
does emit is a triple-sink record on every transition:

* **An audit-log row** (`orby.tool_action.approved` /
  `orby.tool_action.rejected` / `orby.tool_action.expired`) naming the
  tool, the operator, the tenant, the surface (chat intercept vs explicit
  button), and the approximate IP/user-agent of the decision.
* **The row itself in-place**, with terminal status, executed/cancelled
  timestamps, and a sanitized result/error payload — so the row is the
  second sink an auditor can read directly.
* **A sanitized executor result** persisted on the row, so what the
  approval report says is what the audit trail will replay.

Reach the audit log from your workspace's audit surface or via the event
ledger; the row stays queryable through `GET
/api/v1/orby/tool-actions/:id` — the poll surface the dashboard's
confirmation card uses to refresh the countdown and the result.

## 5. Where this differs from the re-auth challenge

Two distinct human-gates exist in Orbit and they are easy to confuse. The
[destructive-operation re-auth challenge](/concepts/re-auth-challenge-model)
gates **irreversible settings and identity mutations** — workspace deletion,
GDPR erasure, HIPAA disable, 2FA changes, conversation force-transfer — by
demanding a fresh single-use `X-Reauth-Challenge` token minted on a
session-recency or step-up namespace within the last five minutes.

The action-approval model instead gates **agent- and inbox-driven
operations** — the tools Orby, or in some deployments other agent runtimes,
can invoke — by raising a durable pending row whose decision is a human
click inside the operator surface. They differ in three ways:

| Concern            | Re-auth challenge                                                             | Action approval gate                                               |
| ------------------ | ----------------------------------------------------------------------------- | ------------------------------------------------------------------ |
| What it proves     | "A live human is at this keyboard right now"                                  | "A specific operator endorsed this exact operation"                |
| What it protects   | Irreversible settings/identity mutations                                      | Data-changing assistant/inbox tools                                |
| How it proves it   | Short-lived single-use challenge token re-presented with the destructive call | Durable pending row resolved on a dedicated approve/reject route   |
| Who can satisfy it | Any session holder for session-recency; the credential holder for step-up     | The operator who raised the proposal, re-verified at decision time |

A tool can of course sit behind both gates — a proposal to disable HIPAA
would still have to mint the re-auth challenge before its executor runs —
but the gates exist for different reasons and fail in different ways.

## 6. Configure which actions require approval

The gate's master posture is **opt-in per organization**, kept in the
Admin Panel under "Approval requirements." Read or replace it with:

```bash theme={null}
curl -X GET "https://orbit.devotel.io/api/v1/settings/action-approvals" \
  -H "Authorization: Bearer <your-session-token>"
```

The response carries `enabled` (the master switch, default off) and
`require_approval_for` (the subset of known high-risk action types that
must pause for approval). Update it with:

```bash theme={null}
curl -X PUT "https://orbit.devotel.io/api/v1/settings/action-approvals" \
  -H "Authorization: Bearer <your-session-token>" \
  -H "Content-Type: application/json" \
  -d '{"enabled": true, "require_approval_for": ["refund", "data_export"]}'
```

The body is strict — unknown keys are rejected, unknown action types are
dropped server-side, and the list is canonically re-ordered — so a
hand-edited config blob can never widen the gate beyond the known types.
The known types, in canonical order:

| Gate-eligible action type | What it covers                                     |
| ------------------------- | -------------------------------------------------- |
| `sender_id_registration`  | Registering a sender ID                            |
| `number_port`             | Porting a number                                   |
| `refund`                  | Issuing a refund                                   |
| `data_export`             | Exporting data                                     |
| `data_deletion`           | Deleting data                                      |
| `campaign_launch`         | Launching a campaign                               |
| `subscription_change`     | Changing a plan/subscription                       |
| `template_submission`     | Submitting a messaging template for carrier review |

While the workflow is disabled, or the action type is not listed, the
gate is inert and the operation executes without review — the fail-open
default. Turning the feature on never surprises an org that has not opted
into approval review.

## 7. Related troubleshooting surfaces

If an approval surfaces as a `PENDING_ACTION_*` error, jump directly to
[Troubleshooting: Orby pending-action approval](/troubleshooting/orby-tool-approval)
for the code table and recovery playbook. If the failure is an
authorization or session rejection on the same Orby panel, see
[Orby operator errors](/troubleshooting/orby-operator-errors).

## See also

* [Orby operator assistant architecture](/concepts/orby-operator-assistant)
  — the session, thread, and tool-registry model this gate plugs into.
* [Destructive-operation re-auth challenge](/concepts/re-auth-challenge-model)
  — the session-recency and step-up gates for settings/identity mutations.
* [Orby Assistant API](/api-reference/orby) — the approve/reject/poll
  endpoint contract the dashboard calls.
* [Using Orby in the dashboard](/guides/orby-in-dashboard) — the operator
  workflow on top of the gate.
* [Settings endpoints](/api-reference/endpoints/settings) — the
  Admin Panel "Approval requirements" read/write contract.
