Skip to main content
Every verification carries two things: a lifecycle status, and — once analysis finishes — a verdict built from the signals evaluated against the capture.

Status

pending flips to partially_captured on the first capture. Analysis runs once the required captures are in, sets the verdict, and moves the verification to completed. completed, expired, and failed are terminal. A verification can also side-branch to abandoned from pending or partially_captured if the end-user leaves before finishing — that’s not terminal either: a later capture resurrects it back to partially_captured.
If GET /v1/verifications/:id doesn’t contain a verdict object yet, analysis is still processing — poll again, or configure webhook_url at creation time and let rial notify you instead.

The verdict

  • labelverified, suspicious, or failed. The one-word bucket most integrations branch on.
  • score — confidence in the verdict, 01.
  • signals — the per-signal evidence behind the label (table below).
  • reason_code — which signal drove a non-verified label. Absent when label is verified. When more than one signal tripped, reason_code reflects a fixed priority: screen_detected > ai_generated > context_mismatch > reverse_search_match > depth_anomaly.
A simpler, share-safe projection is available at GET /v1/verifications/:id/public (the certificate page) — it exposes only verdict.label, none of the underlying signal detail.

Signals

Each key under signals is either its evaluated shape, or { "status": "not_applicable", "reason": "uploaded_content" } when that slot was never evaluated — because the content behind it was uploaded (an upload-type capture step, or an Audit-mode verification) rather than captured live. A not_applicable signal is never treated as a failure. Signals are requested per verification via signals_required at creation time (screen, ai, reverse, context, depth).

Independent checks

Three more checks can attach to a verification, each activated by an optional field at creation time. None of them affect verdict.label — they answer a different question and are reported separately. object_match — set expected_object (a free-text description, e.g. "blue Ford Focus") to activate it:
inconclusive means the image didn’t allow a reliable determination — treat it as “no evidence either way,” not as a failure. condition — set condition_aspects (1–6 free-form, tenant-defined aspect names, any language) to activate it:
score is the one-decimal average of the aspect scores (0–10); label buckets it (good at ≥7.5, fair at ≥5, otherwise poor). location_match — set expected_location (a street address) to activate presence verification: each capture’s device GPS is compared against the declared address.
verified — the capture happened at the declared address, within GPS tolerance. no_match — the device was somewhere else. ungeocoded — the address couldn’t be resolved to a point, or the capture carried no GPS; no comparison was possible. rejected — the device reported a mock/simulated location.

Deterministic at capture, confidence after the fact

A capture-time verdict is a single decision: one label, computed once a verification’s required signals are in. It doesn’t get walked back later. Case Analysis — the optional deeper pass you trigger with POST /v1/verifications/:id/finalize and a claimant narrative — is a different, explicitly probabilistic layer on top: it scores multiple dimensions from 0 to 1 with supporting evidence, reports its own confidence, lists uncertainty_areas, and buckets a review_priority (low/medium/high). It’s built to help a human reviewer weigh a case, not to re-decide the verdict.

Next: the Kotlin SDK

Capture natively on Android — camera, templates, and the offline queue.