idcheck.now API Docs

Hosted flow

The hosted flow is the capture experience your user sees: consent, document photos, selfie, submit. It talks to the /v1/session/* endpoints with a client token — never an API key. Most integrations let idcheck.now's hosted page (or the SDK) drive these calls; they are documented here so you can build a fully custom capture UI if you need to.

How the hosted flow works

POST /v1/verifications returns a client_token and a hosted url of the form https://idcheck.now/verify/{id}?token={client_token}. The hosted page sends the token as X-Client-Token on every session call. The token is valid for 30 minutes and authorizes exactly one verification. All session endpoints are relative to https://idcheck.now.

Get session state

GET/v1/session/statescope X-Client-Token

To recover after a reload, GET /v1/session/state with the client token. It returns the current status, any decision, and boolean flags describing what has been captured so far.

curl
curl https://idcheck.now/v1/session/state -H "X-Client-Token: $CLIENT_TOKEN"
JavaScript
const res = await fetch("https://idcheck.now/v1/session/state", {
  headers: { "X-Client-Token": clientToken },
});
const state = await res.json(); // {status, has_front, has_back, has_selfie, ...}
Python
import requests

res = requests.get(
    "https://idcheck.now/v1/session/state",
    headers={"X-Client-Token": client_token},
)
state = res.json()
JSON200 OK
{
  "id": "b3f1c2a4-7d5e-4f6a-9c8b-1e2d3a4b5c6d",
  "verificationId": "b3f1c2a4-7d5e-4f6a-9c8b-1e2d3a4b5c6d",
  "status": "doc_front",
  "decision": null,
  "score": null,
  "expires_at": "2026-07-27T14:03:11.204512",
  "has_consent": true,
  "has_front": true,
  "has_back": false,
  "has_selfie": false
}
POST/v1/session/consentscope X-Client-Token

To record consent, POST /v1/session/consent. Both bipa (biometric consent) and recording must be true — anything else returns 422 consent is required to proceed. The server stores the policy version, timestamp, IP and user agent, then transitions the verification to consented.

Request body

FieldTypeDescription
biparequiredbooleanUser accepted the biometric-information (BIPA) notice.
recordingrequiredbooleanUser accepted image/video recording.
policy_versionrequiredstringVersion string of the policy text shown, e.g. 2026-07-01.
curl
curl -X POST https://idcheck.now/v1/session/consent -H "X-Client-Token: $CLIENT_TOKEN" -H "Content-Type: application/json" -d '{"bipa": true, "recording": true, "policy_version": "2026-07-01"}'
JavaScript
await fetch("https://idcheck.now/v1/session/consent", {
  method: "POST",
  headers: { "X-Client-Token": clientToken, "Content-Type": "application/json" },
  body: JSON.stringify({ bipa: true, recording: true, policy_version: "2026-07-01" }),
});
Python
requests.post(
    "https://idcheck.now/v1/session/consent",
    headers={"X-Client-Token": client_token},
    json={"bipa": True, "recording": True, "policy_version": "2026-07-01"},
)
JSON200 OK
{
  "status": "consented"
}

Submit a document image

POST/v1/session/documentscope X-Client-Token

To submit a driver's-license image, POST /v1/session/document once per side. The front call stores the image and moves the state to doc_front. The back call runs the barcode pipeline: the raw PDF417 payload is parsed server-side (AAMVA CDS v01–v12), validated, and — when an image is supplied — independently re-decoded on the server and compared against the client-supplied barcode. A mismatch is recorded as a tamper signal.

Request body

FieldTypeDescription
siderequired"front" | "back"Which side of the document this payload describes.
image_b64optionalstring (base64) | nullJPEG image bytes, base64. Required on front; on back it may be omitted only when barcode_raw is pasted manually (weaker assurance — the server re-decode is skipped and no_server_image is recorded).
barcode_rawoptionalstring | nullRaw PDF417 barcode text as scanned (AAMVA CDS). Parsed and validated server-side; unparseable payloads return 422 barcode could not be parsed.
barcode_parsedoptionalobject | nullClient-side parse of the barcode, stored for cross-comparison.
curl
curl -X POST https://idcheck.now/v1/session/document -H "X-Client-Token: $CLIENT_TOKEN" -H "Content-Type: application/json" -d '{"side": "back", "image_b64": "/9j/4AAQSkZJRg...", "barcode_raw": "@<RS>ANSI 636014090002DL00410278ZV03190008DLDAQD1234567..."}'
JavaScript
const res = await fetch("https://idcheck.now/v1/session/document", {
  method: "POST",
  headers: { "X-Client-Token": clientToken, "Content-Type": "application/json" },
  body: JSON.stringify({ side: "back", image_b64, barcode_raw, barcode_parsed }),
});
const { status, sha256, parsed, checks } = await res.json();
Python
requests.post(
    "https://idcheck.now/v1/session/document",
    headers={"X-Client-Token": client_token},
    json={"side": "back", "image_b64": image_b64, "barcode_raw": barcode_raw},
)
JSON200 OK (back side, truncated)
{
  "status": "doc_back",
  "sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
  "parsed": {"DAQ": "D1234567", "DCS": "DOE", "DAC": "JANE", "DBB": "06151990", "DBA": "04172029", "DAJ": "PA"},
  "checks": {
    "mandatory_elements": {"passed": true, "missing": []},
    "not_expired": {"passed": true, "expiry": "2029-04-17"},
    "server_client_barcode_match": {"passed": true}
  }
}

Submit a selfie

POST/v1/session/selfiescope X-Client-Token

To submit the liveness capture, POST /v1/session/selfie with a short video and/or up to 8 still frames. The server runs face matching (license portrait vs selfie, threshold 0.6), passive liveness (threshold 0.5), and the 1:N duplicate-face search against previously verified faces. At least one of video_b64 or frames_b64 is required (422 otherwise).

Request body

FieldTypeDescription
video_b64optionalstring (base64) | nullShort MP4 capture. Undecodable blobs degrade gracefully to frames-only.
frames_b64optionalstring[] (base64)Still JPEG frames; first 8 are stored. Used for face match and liveness.
curl
curl -X POST https://idcheck.now/v1/session/selfie -H "X-Client-Token: $CLIENT_TOKEN" -H "Content-Type: application/json" -d '{"frames_b64": ["/9j/4AAQSkZJRg...", "/9j/4B1SkZKms..."]}'
JavaScript
const res = await fetch("https://idcheck.now/v1/session/selfie", {
  method: "POST",
  headers: { "X-Client-Token": clientToken, "Content-Type": "application/json" },
  body: JSON.stringify({ frames_b64: frames }),
});
const { face_match_score, liveness_score } = await res.json();
Python
requests.post(
    "https://idcheck.now/v1/session/selfie",
    headers={"X-Client-Token": client_token},
    json={"frames_b64": frames},
)
JSON200 OK
{
  "status": "selfie",
  "face_match_score": 0.91,
  "liveness_score": 0.88
}

Submit for decision

POST/v1/session/submitscope X-Client-Token

To finalize, POST /v1/session/submit (empty body) once at least the back document has been processed. Submit is only allowed from status selfie or doc_back — anything else returns 409 cannot submit from status <status>. The server runs OFAC screening on the document identity, applies the duplicate-face impact, computes the decision, dispatches the verification.decided webhook, and enrolls the face embeddings for future 1:N checks — all synchronously.

curl
curl -X POST https://idcheck.now/v1/session/submit -H "X-Client-Token: $CLIENT_TOKEN"
JavaScript
const res = await fetch("https://idcheck.now/v1/session/submit", {
  method: "POST", headers: { "X-Client-Token": clientToken },
});
const { decision, score, reasons } = await res.json();
Python
res = requests.post(
    "https://idcheck.now/v1/session/submit",
    headers={"X-Client-Token": client_token},
)
result = res.json()  # {"status": "decided", "decision": ..., "score": ..., "reasons": [...]}
JSON200 OK
{
  "status": "decided",
  "decision": "approve",
  "score": 96.2,
  "reasons": []
}

State machine

TransitionTrigger
created → consentedPOST /v1/session/consent accepted
consented → doc_frontPOST /v1/session/document with side=front
doc_front → doc_backPOST /v1/session/document with side=back
doc_back → selfiePOST /v1/session/selfie
selfie|doc_back → submitted → processing → decidedPOST /v1/session/submit (decision computed synchronously)

The decision object

The decision engine sums weighted signals into a 0–100 score, then applies hard compliance and fraud overrides. The score is always visible; overrides can push the decision below what the score alone implies.

SignalMax pointsHow it's scored
barcode_parse_ok10Pass/fail — the PDF417 payload parsed as AAMVA CDS.
mandatory_elements15Pass/fail — all mandatory AAMVA data elements present.
not_expired15Pass/fail — document expiry date is in the future.
license_recompute10Pass/fail — license number recomputes correctly (FL/IL/WI; auto-pass elsewhere).
server_client_barcode_match10Pass/fail — independent server re-decode equals the client barcode. Neutral (full points, noted) when server decode is unavailable.
face_match25face_match_score × 25. Below 0.6 adds face_match_below_threshold.
liveness15liveness_score × 15. Below 0.5 adds liveness_below_threshold.

Overrides (applied after scoring; none can raise the decision):

  • Degraded ML (face match or liveness unavailable) caps an approve at review (ml_degraded_capped_at_review).
  • OFAC strong match → decline (ofac_match); possible caps at review (ofac_possible_match); screening unavailable caps at review (ofac_screen_unavailable) — a compliance check never fails open.
  • Duplicate face under a different identity → decline (face_duplicate_different_identity); face seen before → caps at review (face_seen_before); dedupe unavailable caps at review.

Document checks (doc_checks)

Every check that ran is stored on the verification under checks.doc_checks (see retrieve a verification). The complete check catalog:

Check idMeaning
mandatory_elementsAll mandatory AAMVA elements (name, DOB, expiry, license number, address…) are present. {passed, missing[]}.
dob_parseable / expiry_parseableThe DBB / DBA date elements parsed to real dates.
not_expiredExpiry date is today or later.
dob_in_pastDate of birth is before today.
date_orderingDOB is before expiry.
age_over_18Computed age is ≥ 18 (includes the computed age).
license_recomputeLicense-number forensics for FL/IL/WI: Soundex surname prefix, encoded DOB/sex day-code, birth-year digits. Skipped (auto-pass) for other states.
server_client_barcode_matchServer-side re-decode of the back image equals the client-supplied barcode. passed: null when the server decode was unavailable (degraded) or no image was supplied (manual paste).
tamper_signalPresent when a mismatch was detected, e.g. {"barcode_mismatch": true}.
no_server_imageManual-paste path: barcode data is unverified client-supplied input (weaker assurance).
ofacOFAC screening result for the document identity: confidence, score, matched entity, dob_corroborated. See Screening.
face_dedupe1:N duplicate-face result: matches[], impact (decline/review/null/unavailable) and reason (face_duplicate_different_identity, face_seen_before, returning_customer, below_threshold, no_matches).
deviceDevice intelligence linked from /v1/signals/collect: visitor id, IP, user agent and bot signals (webdriver, headless_hints, no_languages, incognito).
passedRoll-up: true only if every validation check passed.

All reason strings the engine can emit are listed in the error & reason reference.