Verifications
A verification is one identity-check session for one end user. Create it on your
server, hand the client token or hosted URL to the user's browser, then read the decision. All four
endpoints are merchant-authenticated with X-Api-Key.
Create a verification
To create a verification, POST /v1/verifications with an optional
external_ref (your user's id), an optional free-form user object, and an
optional redirect_url the hosted flow returns to when finished. The API creates the
verification in status created (expires in 24 hours) and issues a 30-minute
client_token for the hosted flow.
Request body
| Field | Type | Description |
|---|---|---|
| external_refoptional | string | null | Your idempotency/reconciliation handle. Stored verbatim and returned on every read. |
| useroptional | object | Free-form metadata about your user (e.g. email, internal id). Defaults to {}. |
| redirect_urloptional | string | null | URL the hosted flow navigates to after completion (used by redirect-style integrations). |
curl -X POST https://idcheck.now/v1/verifications -H "X-Api-Key: $IDCHECK_API_KEY" -H "Content-Type: application/json" -d '{"external_ref": "user_4821", "user": {"email": "jane@example.com"},
"redirect_url": "https://yourapp.example/kyc/done"}'const res = await fetch("https://idcheck.now/v1/verifications", {
method: "POST",
headers: { "X-Api-Key": process.env.IDCHECK_API_KEY, "Content-Type": "application/json" },
body: JSON.stringify({
external_ref: "user_4821",
user: { email: "jane@example.com" },
redirect_url: "https://yourapp.example/kyc/done",
}),
});
const { id, client_token, url } = await res.json();import os, requests
res = requests.post(
"https://idcheck.now/v1/verifications",
headers={"X-Api-Key": os.environ["IDCHECK_API_KEY"]},
json={
"external_ref": "user_4821",
"user": {"email": "jane@example.com"},
"redirect_url": "https://yourapp.example/kyc/done",
},
)
data = res.json() # {"id": ..., "client_token": ..., "url": ...}201 Created — response body
| Field | Type | Description |
|---|---|---|
| idrequired | uuid | Verification id. Use it for GET calls and to reconcile webhooks. |
| client_tokenrequired | string | 30-minute token for the hosted flow (X-Client-Token). Deliver it to the browser. |
| urlrequired | string | Fully-built hosted-flow URL ({HOSTED_BASE_URL}/verify/{id}?token=…). Redirect or link the user here. |
{
"id": "b3f1c2a4-7d5e-4f6a-9c8b-1e2d3a4b5c6d",
"client_token": "x7Qp9m2LwR4tY8uZcVbN1sDfGhJkL0oP3eA5qW6rT2y",
"url": "https://idcheck.now/verify/b3f1c2a4-7d5e-4f6a-9c8b-1e2d3a4b5c6d?token=x7Qp9m2LwR4tY8uZcVbN1sDfGhJkL0oP3eA5qW6rT2y"
}The client token expires after 30 minutes while the verification itself lives 24 hours. If a user abandons the flow and returns later, create a new verification — do not try to reuse a stale token.
List verifications
To list your verifications, GET /v1/verifications. Results are scoped to the
calling API key, newest first. Use status to filter by lifecycle state.
Query parameters
| Field | Type | Description |
|---|---|---|
| limitoptional | integer (query) | Page size, 1–200. Default 50. |
| offsetoptional | integer (query) | Rows to skip. Default 0. |
| statusoptional | string (query) | Filter by exact status, e.g. decided. |
curl "https://idcheck.now/v1/verifications?status=decided&limit=25" -H "X-Api-Key: $IDCHECK_API_KEY"const res = await fetch("https://idcheck.now/v1/verifications?status=decided&limit=25", {
headers: { "X-Api-Key": process.env.IDCHECK_API_KEY },
});
const { total, items } = await res.json();import os, requests
res = requests.get(
"https://idcheck.now/v1/verifications",
headers={"X-Api-Key": os.environ["IDCHECK_API_KEY"]},
params={"status": "decided", "limit": 25},
)
page = res.json() # {"total", "limit", "offset", "items": [...]}{
"total": 137,
"limit": 25,
"offset": 0,
"items": [
{
"id": "b3f1c2a4-7d5e-4f6a-9c8b-1e2d3a4b5c6d",
"external_ref": "user_4821",
"status": "decided",
"decision": "approve",
"score": 96.2,
"created_at": "2026-07-26T14:03:11.204512",
"expires_at": "2026-07-27T14:03:11.204512"
}
]
}Retrieve a verification
To retrieve a verification — including the full decision, every document check, the consent
record and the hash-chained audit log — GET /v1/verifications/{verification_id}.
Verifications belonging to a different API key return 404 verification not found
(never 403 — existence is not leaked).
curl https://idcheck.now/v1/verifications/b3f1c2a4-7d5e-4f6a-9c8b-1e2d3a4b5c6d -H "X-Api-Key: $IDCHECK_API_KEY"const res = await fetch(
"https://idcheck.now/v1/verifications/b3f1c2a4-7d5e-4f6a-9c8b-1e2d3a4b5c6d",
{ headers: { "X-Api-Key": process.env.IDCHECK_API_KEY } }
);
const verification = await res.json();import os, requests
res = requests.get(
"https://idcheck.now/v1/verifications/b3f1c2a4-7d5e-4f6a-9c8b-1e2d3a4b5c6d",
headers={"X-Api-Key": os.environ["IDCHECK_API_KEY"]},
)
v = res.json()
print(v["decision"], v["score"], v["decision_reasons"]){
"id": "b3f1c2a4-7d5e-4f6a-9c8b-1e2d3a4b5c6d",
"external_ref": "user_4821",
"status": "decided",
"decision": "approve",
"score": 96.2,
"created_at": "2026-07-26T14:03:11.204512",
"expires_at": "2026-07-27T14:03:11.204512",
"decision_reasons": [],
"checks": {
"doc_checks": {
"mandatory_elements": {"passed": true, "missing": []},
"not_expired": {"passed": true, "expiry": "2029-04-17"},
"license_recompute": {"passed": true, "skipped": "state_not_supported_or_no_dob"},
"server_client_barcode_match": {"passed": true},
"ofac": {"matched": false, "confidence": "none", "score": 0.412},
"face_dedupe": {"matches": [], "impact": null, "reason": "no_matches"}
},
"face_match_score": 0.91,
"liveness_score": 0.88,
"license_fields": {
"last_name": "DOE",
"first_name": "JANE",
"middle_name": null,
"date_of_birth": "1990-06-15",
"age": 36,
"expiry": "2029-04-17",
"license_number": "D1234567",
"street": "123 MAIN ST",
"city": "PITTSBURGH",
"state": "PA",
"zip": "15201",
"sex": "female"
}
},
"consent": {
"bipa": true,
"recording": true,
"policy_version": "2026-07-01",
"captured_at": "2026-07-26T14:04:02.117300",
"ip": "203.0.113.44",
"user_agent": "Mozilla/5.0 (iPhone; CPU iPhone OS 19_0 like Mac OS X)"
},
"events": [
{
"seq": 1,
"actor": "merchant",
"action": "session.created",
"payload": {"external_ref": "user_4821"},
"ip": "198.51.100.9",
"event_hash": "b7f2a1c9d4e3f6a5b8c7d0e1f2a3b4c5d6e7f8a9b0c1d2e3f4a5b6c7d8e9f0a1",
"created_at": "2026-07-26T14:03:11.210044"
}
]
}Beyond the summary fields, the response adds decision_reasons, a
checks object (doc_checks, face_match_score,
liveness_score, license_fields), the consent record, and
events — the ordered, hash-chained audit trail. Every check id in
doc_checks is explained in the decision
object reference.
Get verification media
To download the captured images, GET /v1/verifications/{id}/media. The media
bucket is private — the only access path is the short-lived presigned URLs returned here. Links
expire after 3600 seconds (1 hour); call the endpoint again for fresh links.
Every issuance is audit-logged as media.links_issued. In local-fallback deployments
without S3, url and expires_in are null.
curl https://idcheck.now/v1/verifications/b3f1c2a4-7d5e-4f6a-9c8b-1e2d3a4b5c6d/media -H "X-Api-Key: $IDCHECK_API_KEY"const res = await fetch(
"https://idcheck.now/v1/verifications/b3f1c2a4-7d5e-4f6a-9c8b-1e2d3a4b5c6d/media",
{ headers: { "X-Api-Key": process.env.IDCHECK_API_KEY } }
);
const { media } = await res.json();
for (const [side, info] of Object.entries(media)) console.log(side, info.url);import os, requests
res = requests.get(
"https://idcheck.now/v1/verifications/b3f1c2a4-7d5e-4f6a-9c8b-1e2d3a4b5c6d/media",
headers={"X-Api-Key": os.environ["IDCHECK_API_KEY"]},
)
for side, info in res.json()["media"].items():
print(side, info["url"], info["sha256"])200 OK — response body
| Field | Type | Description |
|---|---|---|
| mediarequired | object | Map of media side → object. Keys: front, back, selfie, selfie_frame_0…selfie_frame_7 (whichever were captured). |
| media.<side>.keyrequired | string | Storage object key. |
| media.<side>.sha256required | string | SHA-256 of the stored bytes — verify downloads against it. |
| media.<side>.urlrequired | string | null | Presigned GET URL (SigV4). Null when storage is in local-fallback mode. |
| media.<side>.expires_inrequired | integer | null | Seconds the URL remains valid (3600). |
{
"media": {
"front": {
"key": "b3f1c2a4-7d5e-4f6a-9c8b-1e2d3a4b5c6d/front-9ab3c4d5e6f70819.jpg",
"sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
"url": "https://s3.us-east-1.amazonaws.com/idcheck-media/b3f1c2a4-.../front-9ab3c4d5....jpg?X-Amz-Signature=…",
"expires_in": 3600
},
"selfie": {
"key": "b3f1c2a4-7d5e-4f6a-9c8b-1e2d3a4b5c6d/selfie-1a2b3c4d5e6f7788.mp4",
"sha256": "2c26b46b68ffc68ff99b453c1d30413413422d706483bfa0f98a5e886266e7ae",
"url": "https://s3.us-east-1.amazonaws.com/idcheck-media/b3f1c2a4-.../selfie-1a2b3c4d....mp4?X-Amz-Signature=…",
"expires_in": 3600
}
}
}The verification object
Summary shape returned by the list endpoint and embedded in every verification response:
Verification summary fields
| Field | Type | Description |
|---|---|---|
| idrequired | uuid | Verification identifier. |
| external_refoptional | string | null | Your own reference, echoed back. |
| statusrequired | string | Lifecycle state: created, consented, doc_front, doc_back, selfie, submitted, processing, decided. |
| decisionrequired | string | null | approve | review | decline; null until decided. |
| scorerequired | number | null | Composite score 0–100. Bands: ≥ 80 approve, 50–79 review, < 50 decline. |
| created_atrequired | ISO 8601 | Creation timestamp. |
| expires_atrequired | ISO 8601 | Creation + 24 hours. |
The decision fields (decision, score, decision_reasons)
are populated when the user submits and the decision engine runs — see
the decision object for scoring, bands and every
reason string.