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": "approved",
  "autoStatus": "approved",
  "document": {
    "type": "passport",
    "number": "A12345678",
    "personalNumber": null,
    "issuingState": "USA",
    "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", "detail": "no screen-recapture signals" },
    { "name": "document_readable",     "status": "passed", "group": "document", "detail": "all core fields extracted" },
    { "name": "document_number_valid", "status": "passed", "group": "document", "detail": "passport:USA — document number matches the USA passport format" },
    { "name": "document_mrz_valid",    "status": "passed", "group": "document", "detail": "all check digits valid" },
    { "name": "document_not_expired",  "status": "passed", "group": "document", "detail": "expirationDate 2033-09-30" },
    { "name": "document_authentic",    "status": "passed", "group": "document", "detail": "genuine — laminate sheen, crisp typography, natural depth" },
    { "name": "face_detected",         "status": "passed", "group": "selfie",   "detail": "eyes_open=True sunglasses=False frontal=True" },
    { "name": "liveness",              "status": "passed", "group": "selfie",   "detail": "heuristic (selfie mode): face confidence + quality vs threshold 80.0" },
    { "name": "face_match",            "status": "passed", "group": "selfie",   "detail": "CompareFaces similarity vs threshold 90.0" }
  ],
  "prefillMismatches": [],
  "prefillComparison": [],
  "provider": "inyo"
}

Top-Level Fields

FieldDescription
sessionIdThe verification's identifier. Use it with GET /v1/sessions/{sessionId}
userRefYour identifier, echoed back exactly as you supplied it
statusWhere the verification landed — see Statuses
autoStatusWhat 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[]
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
providerinyo for a real verification, inyo-mock for a simulated one — see Sandbox
manualReviewPresent only after a human decision. See Manual Review
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
issuingStateIssuing authority as printed — a US state for licenses, a country code on passports
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.


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, status, group, detail.

RuleDetail
status is authoritative"passed", "failed", or "indeterminate" — the check's outcome. Do not re-derive it — detail explains the outcome, it does not define it. A failed hard check declines the verification; a failed soft check — and any indeterminate — routes it to in_review
group says where the check sitsdocument, selfie, screening, or policy. A name's group never changes, so filter on group rather than maintaining your own name lists
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: document_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

Today sanctions_clear is the only check that publishes indeterminate — an unconfirmed sanctions name match, see Sanctions Screening — but handle the status generically rather than special-casing the name: any indeterminate check means a human decides.

document_authentic merges every reviewed side into one row. It is a hard check — a failed row declines the verification outright. On a card, the front and a submitted back are both reviewed and merged into this one row: any failing side fails it, and detail carries each side's verdict as classification — what the examiner saw, side-prefixed when more than one side was reviewed ("front: genuine — …; back: photocopy — …") and unprefixed when only one was. The review is fail-open per side — a side that could not be judged simply drops out, and if no side produced a verdict the row is absent.

age_match only publishes when appearance description is enabled for your tenant and the document yielded a date of birth. It passes when the document-derived age at capture falls inside the selfie description's estimated age range, widened by a tolerance Inyo configures for your tenant (5 years by default, and quoted in detail as ±5), and fails otherwise. The tolerance absorbs the imprecision of an age estimate; set to 0 it compares against the estimated range exactly. Because an age estimate is a signal rather than proof, a failure never declines — it is a soft check that routes to in_review. Absent whenever appearance description is off, the document carries no date of birth, or the description itself failed open (an outage never blocks the verification) — none of which is itself a signal.


The Checks

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

CheckGroupFails whenPresent
document_scene_cleardocumentA phone, monitor, or screen is visible in frame — the document is being photographed off a displayAlways
document_authenticdocumentThe forensic document review judges any reviewed side — the front, plus a submitted card back — a recapture, print, photocopy, or forgeryWhen enabled for your tenant (default on), and at least one side could be reviewed
document_readabledocumentThe core identity fields (number, name, date of birth) could not all be extractedAlways
document_not_expireddocumentThe expiration date is in the pastWhen the document carries an expiration date
jurisdiction_acceptedpolicyThe document's issuing country is outside the jurisdictions you accept for that document typeWhen you have configured accepted jurisdictions
sanctions_clearscreeningThe name on the document matches an OFAC SDN listing and a matching listing's date of birth agrees. A name match that could not be confirmed publishes this same check as indeterminate instead, routing to in_review — see Sanctions ScreeningWhen sanctions screening is enabled for your tenant (default off)
face_detectedselfieNot exactly one usable face — eyes closed, sunglasses, or a heavily turned headWhen a selfie was captured
livenessselfieLiveness confidence is below your thresholdWhen a selfie was captured
face_matchselfieSimilarity 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.

CheckGroupFails whenPresent
document_mrz_validdocumentAn MRZ check digit does not validate — an OCR error or tamperingWhen the document has a machine-readable zone
document_number_validdocumentThe 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_matchscreeningExtracted US license data disagrees with the issuing DMV's recordWhen DMV verification is enabled for your tenant (default off)
document_data_matchdocumentData you sent disagrees with the documentWhen you set dataCheck: true
age_matchselfieThe document-derived age at capture falls outside the selfie description's estimated age range widened by your tenant's tolerance (5 years by default)When appearance description is enabled for your tenant (default off) and the document yielded a date of birth

One further check appears only in the widget flow: capture_attempts (group policy) 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. 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
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 dataCheck: true, which adds the document_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:

"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_clearstatus
No listing at or above your threshold"passed"unaffected
A listing matches and its date of birth agrees"failed" — hard, declinesdeclined
A listing matches but no candidate's date of birth agrees"indeterminate"in_review

Nothing ambiguous resolves toward approval: an unconfirmable hit goes to a human rather than through. 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 indeterminate (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