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

# The Verify dashboard: history surface, filters, and the role-access guard

> What the /verify console page actually renders — the OTP history table with its filter and export lanes, the send/test-OTP dialogs, and the role guard that keeps the page and its paid carrier sends to owner, admin, and developer seats.

# The Verify dashboard: history surface, filters, and role access

The **Outbound → Verify** page is the OTP console surface: a per-verification history table, the analytics and fraud panes, and a header action row whose **Send Test OTP** lane triggers paid carrier sends. It is also a **role-gated** surface — the seat's organization role must be `owner`, `admin`, or `developer` for the page to render at all. The guide-level walkthrough in [Verify console](/guides/verify-console) teaches you which screens to click; this page explains the surface behind them, so you can predict what a given seat will see and what lanes the page exports.

Everything on this page is a **tenant-owned control**. The history rows are the org's own verification sessions, the filters run client-side on the current page of rows, the paid test-send dialog is bounded by the same fraud pre-flights and account-level pre-send gates as an API send, and the CSV export pins each row's `created_at` to the operator's local zone so the exported file matches the rendered column.

## 1. The page and the guard — routing layer

The page is a Next.js route: `apps/web/src/app/[locale]/(dashboard)/verify/page.tsx`. The whole content body is wrapped in a `RoleGuard` against the seats that are authorized to move money: the page surfaces recipient PII (live phone numbers in the history table) AND the dialog row can trigger a paid carrier send, so both reads and writes are kept away from read-only seats.

The guard resolves the member's role from Clerk's active org membership, with the same DB-truth fallback the rest of the dashboard uses: a Clerk role the map does not recognise is defaulted to `viewer` (denied), and a Clerk fallback that would deny the page is re-checked once against the API's `/me` role before the static access-denied screen renders. The Clerk role string is only a best-effort mirror of the DB truth, so an invited billing seat that accidentally decodes as deny isn't pushed onto the denied screen while the backend would grant it.

The allowed set is **owner, admin, developer**:

| Role         | Reads history + row drawer | Filters and CSV export | Opens the send/test-OTP dialog |
| ------------ | -------------------------- | ---------------------- | ------------------------------ |
| `owner`      | yes                        | yes                    | yes                            |
| `admin`      | yes                        | yes                    | yes                            |
| `developer`  | yes                        | yes                    | yes                            |
| `billing`    | denied                     | denied                 | denied                         |
| `viewer`     | denied                     | denied                 | denied                         |
| `supervisor` | denied                     | denied                 | denied                         |

The denial is whole-page: a non-allowed role sees the access-denied surface, not a gated subset. (Inbox-side supervisor routes, which open **to supervisors** and stay denied here, are a routed by a different lane; this page deliberately excludes them because OTP sends are never a supervisor action.)

## 2. The page surface — history rows, tabs, and the send lane

What the page renders, from top to bottom:

* **Header row.** Title, subtitle, and the action cluster: Export (CSV of the filtered rows), Fraud Score (pre-send risk dip on a target), Fraud Gate (composite operator-fusion verdict), Check Verification (resolve a code against an in-flight session), Bulk send, and **Send Test OTP** — the dialog that triggers a paid carrier send through the same `/send` gate chain as an API-driven send.
* **The tabs.** `overview` (history table + headline KPI tiles), `configuration` (verify profiles and templates), `analytics` (windowed aggregates), `fraudGuard` (fraud analytics), and `voiceBiometrics` (enrollment manage UI when the add-on is present). All tabs share the same guard; the per-tab filter bar persists in the URL.
* **The history table.** Each renderable row is a **Verification** — id, recipient `to`, channel, status (`pending` / `in_progress` / `verified` / `expired` / `failed`), config name, attempts, `created_at`, optional `verified_at` and `expires_at`, and an `is_test` marker that disambiguates dashboard-initiated test sends from API-driven production rows. The `verification.detail` drawer tucks a richer per-row view: fallback execution timeline, masked code-attempts log, and per-hop status coding.
* **The stats row.** The Overview KPI tiles resolve to either the **org-wide** `GET /verify/analytics` aggregate (the authoritative server-side `COUNT(*)`) or the **filtered** in-page reduction — which one the row uses depends on the active filters. The org-wide path is bounded to a rolling look-back window; the window-bounded label on the gauge card exists so "Total Sent" does not over-read as an all-time account count.

The **filter bar** (channel, status, date range, custom range, free-text search) is URL-persisted, so a filter survives a tab switch and can be deep-linked. The free-text search lane is deliberately a client-side substring match — the server-side aggregate cannot answer it, so while a search is active the tiles fall back to loaded-rows scope and say so.

## 3. Roles — read/write per seat

The `RoleGuard` above gates the whole page, but the read/write split still matters for which lane a seat can act on:

* **Read lanes** (history rows, the per-row drawer, the fraud-score and fraud-gate dialogs, the analytics pane, CSV export) evaluate identically for the three allowed roles.
* **Write lanes** are uniformly open across owner/admin/developer: the send dialog, bulk send, check-verification, fraud-score dip, and fraud-gate dip are the same writes to the same backend gates. The role split is *read+send vs. read-only*, not *owner can send, admin cannot*. (Tenant-owned controls here: the fraud gate fires only when the profile configures it; an unconfigured profile skips the dips, and the whole scoring path degrades fail-open when a network-signal source is unreachable.)
* **Outbound termination.** The **Send Test OTP** lane runs a paid carrier send; outbound MT SMS (and voice OTP delivery) exits exclusively through Devotel's wholesale softswitch, as everywhere else in the platform.

The same guard discipline sits on the **configuration** tab's profile editing and the **voice biometrics** tab's enrollment management: a `viewer` can still GET `/verify` rows via the API if another gate explicitly allows it — the frontend guard is the page's gate, not the API's gate, so read-side API exposure is unchanged. (Verify profile creation, template edits, and test sends on the API are `owner`/`admin`/`developer` routes too.)

## 4. Underlying data — profile, template, verdict, log

The rows the page renders fold a few underlying records into one view:

* **Passcode / verification profile** (`config_name` on the row) — the profile that drove the send: ordered channel chain, code length, expiry, max attempts, velocity caps. The dashboard `configuration` tab edits these; the history row names which profile a send ran under.
* **Template** — the OTP body when the tenant configured one; the send pipeline's passcode templates surface as structured template objects rather than flat text on the history row. (See also the [template lifecycle concept](/concepts/template-lifecycle) for how template rows are governed across channels.)
* **Risk verdict** — when fraud scoring ran, the row's context carries the composite score/verdict (`allow` / `step_up` / `block`) and the per-hop attempt log; the masked code-attempts log row and the fallback execution timeline live in the verification-detail drawer.
* **Event ledger.** `log` events — the `verification.check` / `verified` / `expired` / `failed` family — are the analytical source under both the history list and the analytics window (see [verification lifecycle](/concepts/verification-lifecycle)); they write to the tenant's audit stream even when the dashboard row is still in-flight.

## 5. Where the cross-links go

This page sits between two sibling readers:

* [Verify console guide](/guides/verify-console) — the click-path walkthrough for the same screen; use this concept page to predict the surface, then open the guide for per-control interaction detail.
* [Verify fraud gate: score, verdict, fallback chain](/concepts/verify-fraud-gate-timeline-model) — the composite model the fraud-score and fraud-gate dialogs render, plus the per-hop attempts log the verification-detail drawer surfaces.
* [Verification lifecycle](/concepts/verification-lifecycle) — the session state machine and fallback advance rules the history row's status flips over.
* [Roles, teams, permissions](/concepts/roles-teams-permissions) — the built-in role vocabulary this page's guard names.
* [Verify overview](/verify/overview) — the API-side reference for the recipient shape, endpoint pre-flights, and rate limits the Test-OTP lane inherits.

## Scope of the guard

The guard here is strictly the **dashboard surface** gate. The `POST /verify/send` and `/check` routes guard their own seats server-side; a dashboard render-blocked role can still hold an API key with verify privileges. Use the page to operate; use the API to integrate. The page's on-screen labels tell the operator which role the current session resolved as, so a mis-provisioned seat does not silently decode as `viewer`.
