> ## 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.

# Verify OTP approvals: set up the second-officer gate

> Turn on the four-eyes gate for OTP template changes and resend-burst windows, then run the request, approve, and audit workflow with two supervisors.

# Second-officer approvals for Verify

Some OTP changes are too sensitive for one operator: the exact message body a subscriber receives, and how aggressively a verification can be re-sent. This guide sets up Orbit Verify's **second-officer approvals** (a four-eyes gate) so those changes take effect only when a **second** supervisor signs off. That holds OTP-burst abuse and copy edits at bay even if a single operator account is compromised.

The full field list lives in the [OTP approvals reference](/verify/otp-approvals); this guide walks the setup and day-to-day workflow.

## 1. What an approval gates

The gate covers exactly two kinds of change:

* **OTP template changes** (`otp_template`) — new submissions and edits to the per-channel message bodies on your verification profiles (the copy a recipient reads in an SMS, WhatsApp, email, or voice OTP).
* **Resend-burst windows** (`resend_burst`) — a burst of `POST /verify/resend` calls against one verification. Tight resend limits starve a flaky integration; overly wide limits invite OTP-burst abuse, so widening the burst count gets a second set of eyes.

Everything else in Verify — sends, checks, profiles' non-template settings — flows straight through. With the gate off (the default), nothing queues at all.

## 2. Roles: requester and approver

Two roles participate — and the approver must **never** be the requester:

* **Requester** — any team member who edits OTP templates or triggers resends (typically a member or developer role). When the gate is on and their change is gated, the request lands in the approvals queue instead of applying.
* **Approver** — an **owner or admin**. They read the queue, judge each item, and approve or reject it. Only owners and admins can see the queue or edit the policy; other roles get a access-restricted notice and the API answers `403`.

Self-approval is impossible by design: the API returns `409 FOUR_EYES_VIOLATION` when the approver equals the requester, and nothing moves. Decide with a second person, every time.

## 3. Workflow: request → approve or reject → execute

1. **Requester queues the change.** They edit a template on the Verify → Configuration surface, or drive enough resends against one verification to exceed the configured burst threshold. Instead of applying, the change enters the pending queue and the caller receives a `gated: true` response telling them which kind queued.
2. **Approver opens the queue.** In the dashboard they navigate to **Outbound → OTP approvals**. The queue lists pending items oldest-first, each showing the kind, the requester's name, and the exact rendered template body (or the burst count and window) they are judging.
3. **Decision.** The approver clicks **Approve** or **Reject**. Reject may carry an optional reason (up to 1,000 characters) that goes back to the requester.
4. **Execution only on approval.** An approved template's rendered body is applied to its verification profile; an approved resend burst re-enters the normal Verify dispatch path (respecting cooldowns, rate limits, and channel fallback). A rejected item goes nowhere — and the requester can re-submit it, which queues a fresh row.

The whole flow also works over the API: the queue is served from `GET /api/v1/verify/approvals/pending`, and decisions go through `POST /api/v1/verify/approvals/:id/approve` or `.../reject`.

## 4. Audit trail

Every decision and every policy change lands in the [audit log](/guides/audit-log) with the acting user, the queue item (or organization, for policy edits), and any rejection reason:

* `verify.approval.approved` — a second officer released an item.
* `verify.approval.rejected` — with the `reason` in the detail payload.
* `verify.approval.settings.updated` — a policy edit, with the new values.

The requester can never cut their own item through: a self-approve attempt fails before any state moves, and only decisions that actually happened reach the log.

## 5. Turn the gate on (worked examples)

The policy lives on the **Policy** card of the OTP approvals page — or over the API at `PUT /api/v1/verify/approvals/settings`. Owners and admins edit it; any authenticated member can read it (`GET /settings`) to predict whether their next change will queue.

### 5.1 Gate OTP template changes

Turn on the master switch. From now on a non-supervisor's template edit queues for approval:

```bash theme={null}
curl -X PUT "https://api.orbit.devotel.io/api/v1/verify/approvals/settings" \
  -H "X-API-Key: $ORBIT_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "require_approval_default": true }'
```

```json 200 theme={null}
{
  "data": {
    "require_approval_default": true,
    "resend_burst_threshold": null,
    "resend_burst_window_seconds": 60
  },
  "meta": {
    "request_id": "req_approvals_settings_01",
    "timestamp": "2026-09-04T09:00:00.000Z"
  }
}
```

With `resend_burst_threshold` left at its `null` default, **every** resend burst queues — the strictest posture.

### 5.2 Widen the resend-burst window

To let routine resends through and gate only oversized bursts, set a threshold — for example 25 resends per verification within a 300-second window:

```bash theme={null}
curl -X PUT "https://api.orbit.devotel.io/api/v1/verify/approvals/settings" \
  -H "X-API-Key: $ORBIT_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "require_approval_default": true,
    "resend_burst_threshold": 25,
    "resend_burst_window_seconds": 300
  }'
```

Bursts at or below the threshold pass; anything bigger queues for a second officer. Valid inputs are 1 to 1,000,000 resends per threshold and 1 to 86,400 seconds per window (`resend_burst_window_seconds`).

> **Prerequisites and expected result:** you need an owner or admin role and a `verify:write`-capable key. On success the policy applies immediately — changes that match the gate start queueing from the next request onward.

## 6. Edge cases

* **Time-expired requests.** A queued resend burst can outlive its verification: if the verification expires while it waits, the approved replay is declined by the normal resend rules and the requester must start a fresh verification. The queue row stays `pending` so a supervisor can retry or reject it.
* **Rejection comments.** The optional reason is returned to the requester; keep it actionable ("Tighten the wording and re-submit"). A rejection never blocks re-submission — it queues a fresh row.
* **Pending status on the configuration page.** When a template edit queues, the requester sees it held as a pending approval instead of applied; the Verify → Configuration surface keeps showing the currently live body until a second officer releases the change. The pending queue itself lives only on **Outbound → OTP approvals** (owners and admins).
* **Gate flips mid-flight.** Turning the policy off stops new queueing; items already pending still need a decision before they execute.

## See also

* [OTP approvals reference](/verify/otp-approvals) — the policy fields, queue item shape, role rules, and the troubleshooting table.
* [Audit log](/guides/audit-log) — the decision trail this gate writes to.
* [Verify profiles: fallback chains and advanced factors](/guides/verify-fallback-chains) — the profiles whose templates this gate protects.
* [MFA factor suite](/guides/verify-factor-suite) — possession-of-secret factors alongside OTP delivery.
