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

{
  "sessionId": "9f1c8e42-7c3e-4f2b-9d7a-2b1e5c8f4a10",
  "userRef": "user-123",
  "status": "completed",
  "decision": "approved",
  "document": {
    "type": "passport",
    "number": "A12345678",
    "personalNumber": null,
    "issuingState": null,
    "issuingCountry": "USA",
    "issuingCountryName": "United States",
    "expirationDate": "2033-09-30",
    "dateOfIssue": null
  },
  "person": {
    "firstName": "ALEX",
    "lastName": "MORGAN",
    "fullName": "ALEX MORGAN",
    "dateOfBirth": "1988-03-04",
    "gender": "F",
    "nationality": "USA",
    "address": null,
    "city": null,
    "region": null,
    "postalCode": null
  },
  "checks": [
    { "name": "document_scene_clear",  "status": "passed", "group": "document", "severity": "low", "detail": "no screen-recapture signals" },
    { "name": "document_readable",     "status": "passed", "group": "document", "severity": "critical", "detail": "all core fields extracted" },
    { "name": "document_authentic",    "status": "passed", "group": "document", "severity": "high", "detail": "genuine — laminate sheen, crisp typography, natural depth" },
    { "name": "document_not_expired",  "status": "passed", "group": "document", "severity": "critical", "detail": "expirationDate 2033-09-30" },
    { "name": "document_number_valid", "status": "passed", "group": "document", "severity": "medium", "detail": "passport:USA — document number matches the USA passport format" },
    { "name": "document_mrz_valid",    "status": "passed", "group": "document", "severity": "low", "detail": "all check digits valid" },
    { "name": "face_detected",         "status": "passed", "group": "selfie",   "severity": "medium", "detail": "One face, fully visible and facing the camera." },
    { "name": "liveness",              "status": "passed", "group": "selfie",   "severity": "high", "detail": "heuristic (selfie mode): face confidence + quality vs threshold 80.0" },
    { "name": "face_match",            "status": "passed", "group": "selfie",   "severity": "high", "detail": "portrait-to-selfie similarity vs threshold 90.0" }
  ],
  "prefillMismatches": [],
  "prefillComparison": [],
  "provider": "inyo",
  "trustIndex": 100
}

Top-Level Fields

FieldDescription
sessionIdThe identifier. Read a widget session with GET /v1/sessions/{sessionId}, a verification with GET /v1/verifications/{verificationId}
userRefYour identifier, echoed back exactly as you supplied it
statusWhere the session is in its lifecycle — see Status and decision
decisionWhat Inyo concluded. Present only under managed decisioning, and only for a session that reached an assessment — the key is absent otherwise, not null. See Status and decision
documentExtracted document data
personExtracted identity data
checksThe individual checks that produced the decision — see Reading checks[]
prefillMismatchesNames of prefill fields that disagreed with the document. Empty unless you sent prefill
prefillComparisonPer-field comparison detail — see Prefill comparison
selfieDescriptionStructured appearance description. Present only when enabled for your tenant — the key is absent otherwise, not null
screeningSanctions-listing matches behind a failed screen. Present only when there is something to adjudicate — the key is absent on a clear screen, and absent entirely unless screening is enabled for your tenant. See Sanctions Screening
trustIndexThe verification's score, 0-100
providerinyo for a real verification, inyo-simulated for one a reserved document number dictated — see Sandbox
notifiedAtPresent only on delivered notifications, not on the stored result. Use it to order concurrent deliveries — see Receiving Results

document

FieldDescription
typepassport, drivers_license, or identity_card — the same vocabulary you send in prefill.documentType
numberDocument number as read from the document
personalNumberSecondary national identifier where the document carries one
issuingStateThe subdivision as printed — a US state for licenses. null on a passport, which carries no subdivision; read issuingCountry for the issuer
issuingCountryResolved ISO 3166-1 alpha-3 country
issuingCountryNameDisplay name for issuingCountry (for example United States). Falls back to the code itself when it is not a recognized country, and is null only when issuingCountry is null
expirationDate, dateOfIssueYYYY-MM-DD, or null when the document does not carry it

person

firstName, lastName, fullName, dateOfBirth, gender, nationality, address, city, region, postalCode.

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.


Status and Decision

These are two different fields. status says where the session is; decision says what Inyo concluded, and only exists if you asked us to conclude anything.

status — always present

statusMeaningWhat to do
pendingCreated, not yet completedWait
completedThe customer finished and the session was assessedRead checks, trustIndex, and decision if you have it
failedThe capture was never usable — on the widget, the customer ran out of attemptsCreate a new session. There is no decision on a failed session
erroredA platform fault stopped the assessment. Reachable only on server-to-server verificationsRetry the call with a new Idempotency-Key. There is no decision, and nothing reached a reviewer

status never names a verdict.

decision — managed decisioning only

decisionMeaningWhat to do
approvedThe verification clearedProceed with onboarding
declinedThe verification did not clearDo not proceed. The failed rows in checks say why
inconclusiveThe evidence did not settle it either way — finalDo not proceed. It is not a decision against the customer, so a fresh, clearer capture may settle it
in_reviewA human is deciding — not finalTreat as pending. See Manual Review

Two decisioning modes. Under non-managed decisioning — the default — Inyo publishes the assessment and you decide: checks and trustIndex are there, and decision is absent from the payload entirely. Under managed decisioning Inyo also reaches a verdict, which is the decision field, and may route a session to a human reviewer first.

The key is absent, not null. "decision" in payload distinguishes "you were never going to be told one" from a decision we could not reach — there is no such thing as the latter.


Reading checks[]

Every check is exactly six fields: name, status, group, severity, reason, detail.

FieldWhat to do with it
status"passed", "failed", "undetermined", or "not_evaluated". Authoritative — do not re-derive it from detail
severity"critical", "high", "medium" or "low", heaviest first — how much weight this check carries on this verification. It is a weight, never a destination, and it is configurable, so read decision for the outcome rather than deriving one from it
groupdocument, selfie, screening, or policy. A name's group never changes, so filter on this rather than keeping your own name lists
reasonA stable token on undetermined and not_evaluated rows — provider_unavailable, no_mrz, and so on. null elsewhere
detailProse for a human, including the bar where a check has one. Never branch on it — the wording may change, the status will not

The set varies. Checks appear only when applicable — a machine-readable zone needs a document that carries one, face checks need a selfie, several are enabled per account. Iterate the array; never index it positionally.

Two ways a check reaches no verdict, and neither is a finding against the customer. undetermined means it ran and could not conclude; not_evaluated means it never got a verdict at all — a provider we could not reach, input the document did not carry, a capture rejected before its turn. Both are charged a fraction of the check's weight rather than ignored, and reason names which. An absent row is a third thing again: the check was never applicable.


The Checks

One row per check. severity is configurable, so what any one failure costs your verifications is set for your account.

CheckGroupWhat it checksPresent
document_readabledocumentThe core identity fields — number, name, date of birth — all came off the documentAlways
document_not_expireddocumentThe expiration date is not in the pastWhen the document carries one
document_authenticdocumentA forensic review of every submitted side finds no recapture, print, photocopy or forgery. Sides are merged into this one row, and detail names each side's verdict. A rejection is read twice before it standsWhen enabled for your account (default on)
document_scene_cleardocumentNo phone, monitor or screen is visible anywhere in the frame. It reads the whole image, so a display behind a genuine document trips it tooAlways
document_mrz_validdocumentThe machine-readable zone's check digits validateWhen the document has an MRZ
document_barcode_validdocumentThe barcode on the back carries a well-formed identity record. A back too soft to read publishes not_evaluated with reason no_barcode and costs nothingWhen the document is a US driver's license or state identification card
document_sources_agreedocumentThe document's machine-readable data — a passport's zone, a US license's barcode — agrees with what its face prints. A disagreement names the fields that differ, never their valuesWhen the document yielded machine-readable data and its face yielded fields to compare
document_number_validdocumentThe number matches the known format for its jurisdiction. Advisory — the format tables are published-but-imperfectWhen a rule exists for that jurisdiction
document_data_matchdocumentThe data you sent agrees with the documentWhen you set dataCheck: true and the document yielded an identity
document_type_matchdocumentThe document is the type you declared. The type is read from the images and compared against prefill.documentType; with no declared type the check publishes not_evaluated rather than a verdictAlways
face_detectedselfieExactly one usable face — not eyes closed, sunglasses, or a heavily turned headWhen a selfie was captured
livenessselfieThe selfie is a live person rather than a reproductionWhen a selfie was captured
face_matchselfieThe selfie is the same person as the document portraitWhen a selfie was captured
age_matchselfieThe document's date of birth agrees with the age estimated from the selfie, within a tolerance set for your account. An estimate is a signal, not proofWhen appearance description is enabled (default off), the selfie yielded a usable face, and the document yielded a date of birth
sanctions_clearscreeningThe name does not match an OFAC SDN listing whose date of birth also agrees. A match it cannot confirm publishes undetermined — see Sanctions ScreeningWhen screening is enabled for your account (default off) and the document yielded an identity
dmv_record_matchscreeningExtracted US license data agrees with the issuing DMV's recordWhen DMV verification is enabled (default off) and the document yielded an identity
jurisdiction_acceptedpolicyThe issuing country is one you accept for that document typeWhen you have configured accepted jurisdictions
capture_attemptspolicyThe customer did not exhaust the retry limit — see Widget DeliveryWidget flow only

Prefill Comparison

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

"prefillComparison": [
  { "field": "lastName",     "sent": "Silva",      "received": "SILVA SANTOS", "match": true,  "method": "subset",     "score": 0.95, "threshold": 0.85 },
  { "field": "dateOfBirth", "sent": "1988-03-04", "received": "1988-03-04",   "match": true,  "method": "exact",      "score": null, "threshold": null },
  { "field": "issuingState", "sent": "Florida",    "received": "FL",           "match": true,  "method": "normalized", "score": null, "threshold": null }
]
FieldDescription
fieldOne of firstName, lastName, dateOfBirth, documentType, documentNumber, nationality, issuingState, issuingCountry
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 — a passport's issuingState is one of them, since no passport evidences a subdivision. Send the country as issuingCountry, which is compared. 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 dataCheck: true, which adds the check document_data_match. 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:

"selfieDescription": {
  "estimatedAgeLow": 30,
  "estimatedAgeHigh": 40,
  "skinTone": "light",
  "eyeColor": "brown",
  "hairColor": "dark brown",
  "hairStyle": "shoulder length, straight",
  "facialHair": "none",
  "eyewear": "none",
  "headwear": "none",
  "distinguishingFeatures": ["small scar above left eyebrow"],
  "notes": null
}

Any attribute that cannot be observed is null.

Appearance descriptions are sensitive personal data. They carry GDPR and biometric-privacy (for example BIPA) exposure, and the selfie is processed by an external vision service. Confirm you have a lawful basis, a data-processing agreement in place, and a documented retention justification before asking us to enable this.


Sanctions Screening

Off by default, and enabled per tenant by Inyo on request. When on, the name extracted from the document is screened against the OFAC Specially Designated Nationals (SDN) list.

A name match is not a person

Two common name tokens appear in many listed names, so a name hit alone never declines. Confirmation is by date of birth, and the outcome is the status of the one sanctions_clear check:

Outcomesanctions_clearEffect
No listing at or above your threshold"passed"unaffected
A listing matches and its date of birth agrees"failed"charged at this check's full weight
A listing matches but no candidate's date of birth agrees"undetermined"charged less — an unconfirmed hit is not a finding

Nothing ambiguous resolves toward approval. Gate on status. The acceptance threshold is configured per tenant — ask your Inyo contact what yours is set to.

The screening block

Present only when a hit was delivered — that is, alongside a sanctions_clear that is failed (a confirmed match) or undetermined (an unconfirmed one). It is absent on a clear screen, so its presence means "there is something to adjudicate", not "screening ran".

"screening": {
  "matches": [
    {
      "name": "SAMPLE, Ali Hassan",
      "sourceList": "us_ofac",
      "programs": ["SDGT"],
      "entityId": "12345",
      "score": 91.5,
      "dobAgrees": true
    }
  ]
}
FieldMeaning
nameThe listed entity's name as published
sourceListAlways us_ofac today
programsOFAC sanctions programs the listing falls under
entityIdThe listing's identifier, for looking it up at source
scoreName similarity, 0-100
dobAgreestrue, false, or null when undecidable — the document's date of birth could not be read, or the listing publishes none

At most five matches are delivered — enough to tell a plausible single match from an obvious collision cluster. Only candidates at or above your threshold appear.

One quirk worth knowing when you adjudicate: OFAC publishes imprecise dates collapsed to 1 January, so a candidate listed on that day is compared by year alone. For anyone genuinely born on 1 January that loosens matching, in the declining direction.

Your customer is never told that a sanctions match is what rejected them. The declining response is byte-identical to any other unretryable failure.

If screening is unavailable

Screening fails open: a provider outage skips the check rather than blocking or declining the verification. Nothing in the delivered result distinguishes a session that shipped unscreened from one that screened clean — there is no sanctions_clear row at all in that case. If you need positive evidence that every verification was screened, assert on the presence of that check rather than on the absence of a match.


Next Steps