Skip to main content

Get Verification Detail

A single endpoint that returns the full lifecycle of a verification request — channel-by-channel send attempts, the fallback chain’s append-only history, the frozen config snapshot that drove the chain, the live expiration countdown, and a masked code-attempt log. The endpoint powers the verification-request drawer in the dashboard. It is also the canonical surface for self-serve dispute-resolution: when an end user claims they “never received the OTP”, an operator can reconstruct exactly what was tried, when, on which channel, with which provider, and what error came back. Endpoint
GET /api/v1/verify/{id}/detail
Authentication: Clerk session (Authorization: Bearer <token>) or API key (X-API-Key). Any authenticated caller for the tenant may read this endpoint — no additional scope is required. Rate limit: 60 requests/minute per API key.

Path parameter

id
string
required
Verification id (prefixed, e.g. vrf_3f1c0b2a8e4d4f7a9c2b1e6d5a4c3b2a). Cross-tenant ids return 404 (the tenant boundary is enforced by schema isolation; the controller cannot leak existence of another tenant’s verification).

Response

200 OK

Field reference

Top-level

channelAttempts[] — per-channel summary

One row per channel touched. Latest outcome wins when the same channel was used twice (e.g. an explicit /resend).

fallbackChain[] — append-only history

One row per fallback engine tick. This is the full timeline, not just the latest. Useful for support flows that need to reconstruct exactly what happened.

fallbackConfigSnapshot

Frozen JSON snapshot of the fallback config that was active at send time. Null when the verification didn’t use a profile (direct send). Reading the snapshot is the only reliable way to know what config drove this specific verification (the underlying profile may have been edited or deleted since).

codeAttempts[] — masked code-attempt log

Projected from audit_logs where action='verification.checked' AND resource='verification' AND resource_id=:id AND organization_id=:orgId, limited to the most recent 50 entries.
The OTP code column (a SHA-256 hash on disk) is never returned by this endpoint. The submittedDigits mask is a defense-in-depth control — defending the operator-facing detail view even if upstream code paths drift.

Errors


Performance

P95 ≤ 200ms. The row read is a single PK lookup on the tenant schema; the audit_logs join is bounded to 50 rows and uses the (organization_id, resource, resource_id) composite index.

Edge-case handling

  • Empty history. Just-sent verifications return channelAttempts: [] and fallbackChain: []. The drawer renders an empty-state copy.
  • Deleted profile. profileName: null with profileId still populated. The snapshot still hydrates the dashboard rendering.
  • Expired row. expirationCountdownSec: 0 — the FE renders the “expired” badge unconditionally.
  • No audit rows. codeAttempts: []. Older verifications recorded before the verification.checked audit action shipped may legitimately have no entries.

See also