Skip to content

Identity · Document verification

Read the document the way the issuer wrote it.

KYCVerify locates the machine-readable zone, parses it to the ICAO 9303 layout and recomputes every check digit. Indian cards without an MRZ are read by OCR. Image quality, expiry, country and type are checked before a single field is trusted.

kycverify · productdocument
MRZ formats: TD1, TD2, TD3, MRV-A, MRV-B, French ID
6
document types accepted by a workflow
7
attempts per step by default
3
ICAO 9303 · 7-3-1

What it checks

Document verification, check by check.

Each rule below is what the engine actually runs. The result is written as a document check with its score and warnings.

  • Check digits

    Document number, date of birth, expiry, optional data and the composite digit, each recomputed with ICAO's 7-3-1 weights. Long TD1/TD2 document numbers that continue into the optional field are handled.

  • OCR correction you can see

    Common confusions (O and 0, I and 1) are corrected only in positions whose alphabet is known, and every correction is reported, so a reviewer can tell a clean read from a repaired one.

  • Image quality

    Resolution, sharpness (variance of the Laplacian), exposure and glare are measured first. A blurry or glare-washed capture is sent back to the person with a reason instead of being guessed at.

  • Expiry, type and country

    An expired document is declined when the workflow says so. The declared type and issuing country must match what the MRZ says, and the country must be on the workflow's allow-list.

  • Indian cards

    Aadhaar, PAN, voter ID and Indian driving licences are read by OCR from the bilingual card. Nothing is invented: a field that cannot be located stays empty and is listed as an issue.

  • Portrait and fingerprint

    The portrait is detected and embedded for face match. The document number is hashed into a fingerprint so a second session with the same document can be flagged.

Try it

Recompute a check digit yourself.

The same 7-3-1 arithmetic the engine runs, on ICAO's own specimens. Edit a character and watch the digit that guards it stop verifying.

  • The rules are ported line for line from the Rust engine, and the page names the file.
  • Everything runs locally in this tab. No request is made while you type.
  • Reset puts the example back; nothing is saved.

MRZ check-digit calculator

Runs in your browser
Specimens

One line per row: 2 x 44 (passport, MRV-A visa), 2 x 36 (TD2, MRV-B) or 3 x 30 (ID card). The specimens are ICAO's own.

P<UTOERIKSSON<<ANNA<MARIA<<<<<<<<<<<<<<<<<<< L898902C36UTO7408122F1204159ZE184226B<<<<<10

TD3: every check digit verifies

The engine would record mrz.valid = true and move on to expiry, type and country.

Check digits

Working: document number

Each character, its value, the 7-3-1 weight and the product
charL898902C3
value21898902123
weight731731731
product1472495627014363

sum = 316 · 316 mod 10 = 6 · printed 6

Parsed fields

Document code
P
Issuing state
UTO
Surname
ERIKSSON
Given names
ANNA MARIA
Document number
L898902C3
Nationality
UTO
Date of birth
1974-08-12
Sex
F
Date of expiry
2012-04-15

Logic ported from backend/crates/kyc-mrz/src/lib.rs. Nothing you type leaves this page.

How it works

What happens, in order.

  1. 1

    Capture

    The hosted flow asks for the document type and issuing country, then the front (and back where it matters). Uploads are capped at 10 MB, MIME-sniffed and encrypted at rest.

  2. 2

    Quality gate

    Sharpness, exposure and glare are measured. A capture that fails is rejected with a retry, without spending the decision.

  3. 3

    Read

    OCR runs on the image, the MRZ is found among the noise lines and parsed, or the Indian-card extractor reads the printed fields.

  4. 4

    Verify

    Check digits, expiry, type and country are verified, the identity is merged with its source recorded per field, and a document check is written.

Configuration

The workflow keys and their defaults.

Workflow · json
"document": {
  "enabled": true,
  "allowed_types": ["passport", "id_card", "driving_licence",
                    "residence_permit", "aadhaar", "pan", "voter_id"],
  "allowed_countries": [],
  "reject_expired": true,
  "max_attempts": 3
}
KeyDefaultMeaning
allowed_typesall 7Which document types the person may choose.
allowed_countries[] (any)ISO 3166-1 alpha-3 issuing countries, e.g. IND, GBR.
reject_expiredtrueDecline a document past its expiry date.
max_attempts3Captures allowed before the step fails.

Reference

Six MRZ layouts, one parser.

The layout is chosen from the line count and length. Positions are fixed by ICAO Doc 9303, so every field and its check digit sit in a known place.

Six MRZ layouts, one parser.
FormatLinesUsed onCheck digits
TD32 x 44PassportsNumber, birth, expiry, personal number, composite
TD22 x 36ID cards, some residence permitsNumber (long form), birth, expiry, composite
TD13 x 30ID cards, residence permitsNumber (long form), birth, expiry, composite
MRV-A2 x 44Visas, full-pageNumber, birth, expiry
MRV-B2 x 36Visas, smaller formatNumber, birth, expiry
French ID2 x 36French national ID, 1988-2021Number, birth, composite (no expiry in the MRZ)

Reasons and warnings

Exact codes, as they appear in the check's data and warnings, so you can branch on them.

Document verification codes
CodeOutcomeWhen
document_expiredfailedExpiry date is in the past and reject_expired is on (a warning only when it is off).
document_country_not_allowedfailedThe MRZ issuing state is not on the workflow's allowed_countries list.
document_mrz_invalidreviewOne or more check digits do not verify (warning mrz_check_digits_failed).
document_mrz_unreadablereviewNo machine-readable zone could be read from the image.
document_type_mismatchreviewThe MRZ document code does not match the type the person chose.
document_country_mismatchreviewThe declared issuing country differs from the MRZ.
document_number_invalidreviewAn Aadhaar number read from a card fails its Verhoeff checksum, or a PAN fails its format.
document_unreadableretakeToo small, blurred, too dark or glare-washed: the person is asked to capture again, without spending the decision.

API

Parse an MRZ from your backend.

POST /v1/checks/mrz takes the 2 or 3 lines (or up to 20 noisy OCR lines, from which the MRZ is found) and returns every field, each check digit and any OCR correction.

In a session

A document check on the ICAO specimen passport

document check · json
{
  "kind": "document",
  "status": "failed",
  "score": 1.0,
  "data": {
    "document_type": "passport",
    "country": "UTO",
    "attempt": 1,
    "issuing_country": "UTO",
    "verification_level": "mrz_check_digits",
    "mrz": {
      "format": "td3", "document_code": "P", "issuing_state": "UTO", "valid": true,
      "check_digits": { "document_number": true, "birth_date": true, "expiry_date": true,
                        "optional_data": true, "composite": true },
      "corrections": 0
    },
    "reason": "document_expired"
  },
  "warnings": [
    { "code": "document_expired", "message": "The document expired on 2012-04-15", "severity": "high" }
  ]
}

Parse an MRZ from your backend.

curl -X POST https://kycverify.me/api/v1/checks/mrz \
  -H "x-api-key: $KYC_API_KEY" \
  -H "content-type: application/json" \
  -d '{
    "lines": [
      "P<UTOERIKSSON<<ANNA<MARIA<<<<<<<<<<<<<<<<<<<",
      "L898902C36UTO7408122F1204159ZE184226B<<<<<10"
    ]
  }'

200 OK (abridged)

{
  "format": "td3",
  "document_code": "P",
  "issuing_state": "UTO",
  "surname": "ERIKSSON",
  "given_names": "ANNA MARIA",
  "document_number": "L898902C3",
  "nationality": "UTO",
  "birth_date": "1974-08-12",
  "sex": "F",
  "expiry_date": "2012-04-15",
  "check_digits": {
    "document_number": true, "birth_date": true, "expiry_date": true,
    "optional_data": true, "composite": true
  },
  "valid": true,
  "corrections": []
}

Limits

What it does not do.

Stated up front, so you can decide what to pair it with.

  • No NFC chip reading: the e-passport chip is not read, so the check relies on the printed MRZ and the image.
  • No forensic template matching against a library of every issuer's security features. Authenticity rests on MRZ integrity, consistency and quality.
  • Indian cards have no MRZ, so their fields come from OCR and carry lower certainty than a check-digit-verified MRZ.

FAQ

Document verification: questions.

Which documents are supported?

Anything with an ICAO 9303 MRZ (passports, most national ID cards, residence permits, visas) from any country, plus Aadhaar, PAN, voter ID and Indian driving licences read by OCR. A workflow can narrow both the types and the issuing countries.

What happens when a check digit fails?

The parsed fields are kept with valid: false, a mrz_check_digits_failed warning is raised and the document check goes to review as document_mrz_invalid, so a person looks at the image before anything is decided.

Is the full document number stored?

No. The identity keeps a masked number (L8989****). Duplicate detection compares a keyed HMAC-SHA256 fingerprint that is kept internally and never appears in the identity, decisions or webhooks.

Try it in the sandbox today.

Every check is available from the first sign-up, with test keys and a default workflow. Talk to us when you are ready to verify real people.