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

# OTP approvals: two-person sign-off for templates and resend bursts

> Turn the four-eyes gate on for your org, approve or reject queued OTP template changes and verification resend bursts, and read the self-approve rejection.

# OTP approvals

OTP approvals is the **second-supervisor gate** for the two highest-leverage pieces of Orbit Verify:

* an OTP message template going live, and
* a resend burst on an existing verification (a flurry of resend requests against one verification).

When the gate is on, anyone who submits a matching change becomes the **requester**. Nothing leaves the queue until a **different** owner or admin approves it — the requester may not approve their own request. A self-approve attempt is rejected with `409 Conflict`. This is the same four-eyes pattern used for campaign launches, so the queue reads identically across gated surfaces.

## Turn the policy on or off

The policy has three fields:

| Field                      | Type           | Meaning                                                                                                                                                                                                                  |
| -------------------------- | -------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `require_approval_default` | boolean        | Master switch. When `false`, nothing is gated and every request flows straight through. When `true`, matching OTP template changes and resend bursts queue for a second sign-off.                                        |
| `resend_burst_threshold`   | number \| null | Optional resend count that gates only oversized bursts. Leave it blank (`null`) to gate **every** resend burst; set a number so only bursts above that many resends per verification queue. Blank is the strictest form. |
| kind coverage              | fixed          | The gate covers exactly two kinds: `otp_template` (new submissions and edits) and `resend_burst`. No other Verify traffic queues.                                                                                        |

Update the policy from the dashboard (any owner/admin), or read and write it over the API:

```
GET  /api/v1/verify/approvals/settings
PUT  /api/v1/verify/approvals/settings
```

A valid PUT body is the row with your changes:

```json theme={null}
{
  "require_approval_default": true,
  "resend_burst_threshold": 25
}
```

## Work the queue

Every pending item shows the requester (the teammate's user id), the kind (`otp_template` or `resend_burst`), the rendered template body or the burst metadata it would release, and when it was queued.

Work the queue from the dashboard under **Outbound → OTP approvals**, or over the API:

```
GET  /api/v1/verify/approvals/pending
POST /api/v1/verify/approvals/:id/approve
POST /api/v1/verify/approvals/:id/reject
```

* **Approve** releases the request. A gated template goes live; a gated resend burst re-enters the ordinary Verify dispatch chain and delivers through the normal channel router — approval decides nothing more than the policy gate, it never touches the delivery path itself.
* **Reject** holds the request back. The body may carry an optional `reason` so the requester sees why; with no `reason` the requester just sees the rejection.
* **Self-approve is a hard no.** If the approver is the same user as the requester, the API returns `409 Conflict`. It is the single most-asked-about constraint — the whole point of a four-eyes gate is that the person who submits is not the person who signs.

Only owner and admin roles may read the pending queue, approve, reject, or write the settings; everyone else gets `403`.

## Read a pending item

A queued resend burst looks like this:

```json theme={null}
{
  "id": "apr_01jxm1p8qf2ek9xtfa0kwewb4n",
  "kind": "resend_burst",
  "template_id": null,
  "verification_id": "vrf_02a4e7cfd7b54f0a94db3382b42d0db1",
  "rendered_body": null,
  "burst_count": 9,
  "window_seconds": 300,
  "requested_by_user_id": "usr_0f3b1c…",
  "request_note": "retry the failed resend before the verification expires",
  "created_at": "2026-08-23T10:11:00.000Z"
}
```

For `otp_template` rows, `rendered_body` carries the exact template copy the approver judges; for `resend_burst` rows it stays `null` and the approver decides against the `burst_count` / `window_seconds` pair. When the gate is off, the pending endpoint returns an empty list and nothing accrues.

## Troubleshooting

* **"I approved my own request and it came back with 409."** Working as designed — the approver must not be the requester. Have a second owner or admin decide the item, or reject it yourself to clear the queue.
* **"The pending queue is empty even though I just submitted a template."** The gate is off (`require_approval_default` is `false`), or the burst fell below `resend_burst_threshold` when a threshold is set. Also check your role: the queue is supervisor-only and other roles get `403`.
* **"Approving a burst doesn't resend immediately."** Approval only releases the gate. The resend is handed back to the normal Verify dispatch and still respects the verification's expiry, channel fallback, and rate limits — nothing is sent from a parallel path.

Still stuck? See the [Verify overview](/verify/overview) and the [Troubleshooting index](/reference/troubleshooting).
