Inyo

Checks & Decisions

Every verification — widget or server-to-server — resolves to the same normalized result. This page is the reference for reading it.


The Result Payload

{
  "session_id": "9f1c8e42-7c3e-4f2b-9d7a-2b1e5c8f4a10",
  "user_ref": "user-123",
  "status": "approved",
  "auto_status": "approved",
  "document": {
    "type": "Passport",
    "number": "A12345678",
    "personal_number": null,
    "issuing_state": "USA",
    "issuing_country": "USA",
    "issuing_country_name": "United States",
    "expiration_date": "2033-09-30",
    "date_of_issue": null
  },
  "person": {
    "first_name": "ALEX",
    "last_name": "MORGAN",
    "full_name": "ALEX MORGAN",
    "date_of_birth": "1988-03-04",
    "gender": "F",
    "nationality": "USA",
    "address": null,
    "city": null,
    "region": null,
    "postal_code": null
  },
  "checks": [
    { "name": "document_authentic",   "passed": true, "score": null, "detail": "no screen-recapture signals" },
    { "name": "document_readable",    "passed": true, "score": null, "detail": "all core fields extracted" },
    { "name": "mrz_valid",            "passed": true, "score": null, "detail": "all check digits valid" },
    { "name": "document_not_expired", "passed": true, "score": null, "detail": "expiration_date 2033-09-30" },
    { "name": "document_number_format", "passed": true, "score": null, "detail": "passport:USA — document number matches the USA passport format" },
    { "name": "screen_pattern",       "passed": true, "score": null, "detail": "screen/moiré score 0.42 vs threshold 0.8" },
    { "name": "ai_authenticity",      "passed": true, "score": 95.0, "detail": "genuine document — physical document, consistent printing" },
    { "name": "face_detected",        "passed": true, "score": null, "detail": "eyes_open=True sunglasses=False frontal=True" },
    { "name": "liveness",             "passed": true, "score": 93.2, "detail": "heuristic (selfie mode): face confidence + quality vs threshold 80.0" },
    { "name": "face_match",           "passed": true, "score": 98.7, "detail": "CompareFaces similarity vs threshold 90.0" }
  ],
  "prefill_mismatches": [],
  "prefill_comparison": [],
  "selfie_description": null,
  "provider": "inyo"
}

Top-Level Fields

FieldDescription
session_idThe verification's identifier. Use it with GET /v1/sessions/{session_id}
user_refYour identifier, echoed back exactly as you supplied it
statusWhere the verification landed — see Statuses
auto_statusWhat the pipeline decided before any review routing or human decision — see Manual Review
documentExtracted document data
personExtracted identity data
checksThe individual checks that produced the decision — see Reading checks[]
prefill_mismatchesNames of prefill fields that disagreed with the document. Empty unless you sent prefill
prefill_comparisonPer-field comparison detail — see Prefill comparison
selfie_descriptionStructured appearance description, or null unless enabled for your tenant
providerinyo for a real verification, inyo-mock for a simulated one — see Sandbox
manual_reviewPresent only after a human decision. See Manual Review
notified_atPresent only on delivered notifications, not on the stored result. Use it to order concurrent deliveries — see Receiving Results

document

FieldDescription
typePassport, Driver's License, or Identity Card
numberDocument number as read from the document
personal_numberSecondary national identifier where the document carries one
issuing_stateIssuing authority as printed — a US state for licenses, a country code on passports
issuing_countryResolved ISO 3166-1 alpha-3 country
issuing_country_nameDisplay name for issuing_country (for example United States). Falls back to the code itself when it is not a recognized country, and is null only when issuing_country is null
expiration_date, date_of_issueYYYY-MM-DD, or null when the document does not carry it

person

first_name, last_name, full_name, date_of_birth, gender, nationality, address, city, region, postal_code.

Any field the document does not carry — or that could not be read — is null. Address fields are commonly null on passports and populated from driver's licenses. Names arrive as printed on the document, which for passports means uppercase.


Statuses

statusMeaningWhat to do
pendingCreated, not yet completedWait. No decision exists yet
approvedEvery check passedProceed with onboarding
declinedAt least one hard check failedDo not proceed. The failing check's detail explains why
in_reviewA human decides — not finalTreat as pending. See Manual Review
expiredThe customer never completed it within the link's lifetimeCreate a new session if you still need the verification

Reading checks[]

Every check is exactly four fields: name, passed, score, detail.

RuleDetail
passed is authoritativeIt is the check's outcome. Do not re-derive it from scorescore and detail explain the outcome, they do not define it
score is 0-100, higher is betterIt expresses confidence that the check is satisfied. It is null whenever the check is a plain pass/fail with no comparable scalar, which is most of them — only liveness, face_match, and ai_authenticity carry one
detail quotes the barWhere a check has a threshold, detail names it. The liveness and face_match thresholds are per tenant, so read them here rather than assuming the platform defaults
The set variesChecks appear only when applicable: mrz_valid needs a machine-readable zone, face checks need a selfie, and several are enabled per tenant. Iterate the array; do not index it positionally or assume a fixed length

Two checks need explicit handling:

screen_pattern carries score: null deliberately. Its underlying measurement runs on an inverted 0-1 scale where higher means more screen-like, which would read as a near-failure beside the 0-100 biometrics. Publishing it as score would mislead more than inform, so the raw value and its threshold live in detail instead. Read passed.

ai_authenticity carries a score you must not gate on. The score is the model's certainty in its own verdict, oriented so higher means "more likely genuine". It is diagnostic only — Inyo deliberately excludes it from review routing. That does not make the check inert: it is a hard check, and passed: false declines the verification outright whatever the score says.


The Checks

Hard checks fail the verification — any one of them failing yields declined.

CheckFails whenPresent
document_authenticA phone, monitor, or screen is visible in frame — the document is being photographed off a displayAlways
screen_patternScreen/moiré patterns indicate a recapture even with no device in frameWhen at least one captured side could be analyzed
ai_authenticityDocument review identifies a recapture, print, photocopy, or forgeryWhen enabled for your tenant (default on)
document_readableThe core identity fields (number, name, date of birth) could not all be extractedAlways
document_not_expiredThe expiration date is in the pastWhen the document carries an expiration date
jurisdiction_acceptedThe document's issuing country is outside the jurisdictions you accept for that document typeWhen you have configured accepted jurisdictions
face_detectedNot exactly one usable face — eyes closed, sunglasses, or a heavily turned headWhen a selfie was captured
livenessLiveness confidence is below your thresholdWhen a selfie was captured
face_matchSimilarity between the document portrait and the selfie is below your thresholdWhen a selfie was captured

Soft checks route the verification to in_review rather than declining it.

CheckFails whenPresent
mrz_validAn MRZ check digit does not validate — an OCR error or tamperingWhen the document has a machine-readable zone
document_number_formatThe number does not match the known format for its jurisdiction. Advisory — the format tables are published-but-imperfect, so this never auto-declinesWhen a rule exists for that jurisdiction
dmv_record_matchExtracted US license data disagrees with the issuing DMV's recordWhen DMV verification is enabled for your tenant (default off)
data_matchData you sent disagrees with the documentWhen you set data_check: true

One further check appears only in the widget flow: capture_attempts fails when the customer exhausted the retry limit, which declines the session — see Widget Delivery.


Prefill Comparison

When you send prefill, the result reports how each comparable field lined up. prefill_mismatches is the short list of field names that disagreed; prefill_comparison carries the detail:

"prefill_comparison": [
  { "field": "last_name",     "sent": "Silva",      "received": "SILVA SANTOS", "match": true,  "method": "subset",     "score": 0.95, "threshold": 0.85 },
  { "field": "date_of_birth", "sent": "1988-03-04", "received": "1988-03-04",   "match": true,  "method": "exact",      "score": null, "threshold": null },
  { "field": "issuing_state", "sent": "Florida",    "received": "FL",           "match": true,  "method": "normalized", "score": null, "threshold": null }
]
FieldDescription
fieldOne of first_name, last_name, date_of_birth, document_type, document_number, nationality, issuing_state
sent / receivedYour value, and the value read from the document
matchWhether they are considered equal
methodFor names: exact, subset (a multi-part surname containing yours, scored 0.95), fuzzy (similarity scoring), or empty. For states and document types: normalized. Otherwise exact
score / thresholdName similarity and the acceptance bar, null for non-name fields

Fields absent on either side are not compared, so they never appear. Names use fuzzy matching — accents stripped, multi-part surnames accepted as subset matches, OCR near-misses tolerated up to the threshold — so ordinary spelling variance does not create false mismatches. States and document types are normalized first, which is why Florida matches FL.

A mismatch only affects the decision when you set data_check: true, which adds the data_match soft check. Without it, the comparison is informational.


Selfie Description

null unless the appearance description is enabled for your tenant. When present, it carries observable attributes of the selfie for later re-verification comparison:

"selfie_description": {
  "estimated_age_low": 30,
  "estimated_age_high": 40,
  "skin_tone": "light",
  "eye_color": "brown",
  "hair_color": "dark brown",
  "hair_style": "shoulder length, straight",
  "facial_hair": "none",
  "eyewear": "none",
  "headwear": "none",
  "distinguishing_features": ["small scar above left eyebrow"],
  "notes": null
}

Any attribute that cannot be observed is null. These are sensitive personal data — see the compliance note under Tenant Configuration before enabling it.


Score Convention Cutover

Results created before the current score convention shipped differ in two ways: screen_pattern.score carried a raw 0-1 moiré likelihood (where higher meant worse), and ai_authenticity.score used an undefined scale. Post-cutover rows are identifiable without an external reference — screen_pattern.score is null, and no check carries any field beyond the four documented above. If you store historical results, key your reading on that.


Next Steps