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 server decodes the PDF417 from the submitted image (zxing-cpp), then parses (AAMVA CDS v01–v12) and validates it. The client never sends barcode text.

Request body

FieldTypeDescription
siderequired"front" | "back"Which side of the document this payload describes.
image_b64requiredstring (base64)JPEG image bytes, base64. On front, a photo with no portrait or no ID text returns 422.
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..."}'
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 }),
});
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},
)
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_matchThe barcode was decoded server-side from the submitted back image. passed: null (0 points) when the server could not decode it.
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.