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
label—verified,suspicious, orfailed. The one-word bucket most integrations branch on.score— confidence in the verdict,0–1.signals— the per-signal evidence behind the label (table below).reason_code— which signal drove a non-verifiedlabel. Absent whenlabelisverified. When more than one signal tripped,reason_codereflects a fixed priority:screen_detected>ai_generated>context_mismatch>reverse_search_match>depth_anomaly.
GET /v1/verifications/:id/public (the certificate page) — it exposes only verdict.label, none of the underlying signal detail.
Signals
Each key undersignals 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 affectverdict.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-timeverdict 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.