Issue a new verification token
Mints a verification the tenant’s end-user redeems by uploading photos against the returned capture_url. The configuration (capture spec, required fraud signals, optional tenant context, optional webhook_url, optional notify_email) is immutable after issue — to change anything, issue a new verification. When the request omits notify_email, the tenant’s account-level default (configured from the dashboard’s Integrations screen) is stamped into the config at mint time. An explicit request value always wins: a string targets that address, notify_email: null opts this verification out of email entirely. Changing the account default only affects future tokens.
Capture spec: send EITHER max_captures: N (N anonymous captures — sugar for one step {key: "capture_1", min: N, max: N}) OR steps: [...] (guided slots, each with key, description, min/max; min: 0 marks a slot optional). Never both. Each uploaded capture is labelled with the step_key it fills, making results and webhook payloads self-describing.
Side effects: emits verification.created on the internal EventBridge bus. The webhook-deliverer worker consumes that and POSTs the public verification.created webhook event to the tenant’s configured webhook_url (HMAC-signed with their per-tenant webhook secret).
Auth: three legitimate principals — operator dashboard session (cookieSession), tenant backend API key (bearerApiKey), or the WhatsApp bot (botHmac). For the bot path, the body MUST carry a tenant_id field naming the target tenant.
Idempotency: no idempotency key today — a retry mints a duplicate. SDK callers should generate one client-side and dedupe on metadata.idempotency_key.
Authorizations
Dashboard / operator session cookie. Set by /v1/auth/google/callback after Google OAuth, JWT signed with the rotating rial-platform/session-signing HMAC. Read on every request the SPA makes with credentials: include. Browser-only — server-to-server callers use bearerApiKey instead.
Body
Body for POST /v1/verifications. All fields optional — sensible defaults: max_captures: 1, expires_in_seconds: 3600, signals_required: ["screen", "ai"]. Capture quantity comes as EITHER max_captures (N anonymous captures) OR steps (guided slots with key/description/min/max) — never both. context is what the analysis pipeline cross-references against the capture; metadata is opaque pass-through echoed back on every webhook event.
1 <= x <= 201- Option 1
- Option 2
- Option 3
60 <= x <= 864001screen, ai, reverse, context, depth verification, audit 2541 - 3001 - 2001 - 6 elements1 - 402 - 40^[a-z0-9]+(?:-[a-z0-9]+)*$Response
Verification minted. Returns the canonical wire shape including the capture_url the tenant sends to the end-user.
Canonical wire shape for a verification. narrative is present only after POST /v1/verifications/:id/finalize ran. verdict is present only after analysis completed. case_analysis is the structured Gemini multi-image rubric, embedded inline by the sync finalize path when analysis succeeded.
^vfy_[0-9A-HJKMNP-TV-Z]{26}$Lifecycle state. pending → first capture flips to partially_captured → analysis sets the verdict and transitions to completed. Terminal: completed, expired, failed. abandoned is a side-branch off pending/partially_captured, reported by the capture screen via POST /v1/verifications/:token/progress when the end user leaves before finishing — NOT terminal: a later capture resurrects the row to partially_captured like any other.
pending, partially_captured, completed, expired, failed, abandoned x >= 0Tenant-supplied configuration baked into the token at issue time. Immutable after creation — changing config means issuing a new verification. steps is the normalized capture spec: requests that sent max_captures: N read back as a single anonymous step { key: "capture_1", min: N, max: N }.
Integrity seal over the verification record including answers. Present only when answers exist and sealing is on (seal !== false). The hash proves the stored answers have not changed — it does NOT claim they are true or sensor-attested; answers_provenance carries that distinction explicitly.
Aggregate verdict produced by the per-capture analysis pipeline. label is the human-readable bucket; score is the confidence in the verdict.
2000Structured output from the multi-image case-analysis worker (Gemini batch reasoning). Per-dimension scores 0..1 where 0 = nothing suspicious on that dimension and 1 = that dimension alone is grounds to reject. overall_risk_score aggregates with cross-dimensional reasoning, not a simple average.
Object-check result. Present only when the verification was created with expected_object. Independent of the fraud verdict.
Condition assessment. Present only when the verification was created with condition_aspects — the free-form tenant-defined aspect names (any language, any domain). score is the one-decimal average of aspect scores; label buckets it (>=7.5 good, >=5 fair, else poor). Independent of the fraud verdict.
Latest funnel-progress snapshot reported via POST /v1/verifications/:token/progress. A snapshot, not an event log — each call overwrites the previous one. Operator-only; never present on the public projection.