A passport whose machine-readable zone passes every check digit feels trustworthy. The arithmetic balances, the validator returns true five times, and the onboarding flow moves on. It is worth being exact about what that true means, because it is a smaller promise than it looks.

This primer is for developers who build KYC and onboarding flows. It sorts the usual screening checks into what the MRZ check digits catch, what they cannot catch, and which other check covers each gap. Every zone below is ICAO's own specimen, Anna Maria Eriksson of "Utopia", or a variant of it made up for this post. No real document appears.

This is the blog of doc.cheap, a Passport and ID OCR API. Nothing below needs it.

What the digits are for

The MRZ is the block of capital letters, digits and < fillers at the bottom of a passport photo page or on the back of an ID card. ICAO Doc 9303 gives it check digits: one each for the document number, the date of birth and the date of expiry, one for the optional personal number on passports, and a composite digit over most of the lower line.

Each digit is a weighted sum. Every character becomes a number (0–9 as themselves, A = 10 up to Z = 35, < = 0), the numbers are multiplied by the repeating weights 7, 3, 1, and the sum modulo 10 is the digit. How MRZ check digits work walks through it with a validator you can paste.

The digits were designed to catch reading errors. They were never meant to detect fraud, and everything below follows from that.

What a check digit catches

A single wrong digit. Because 7, 3 and 1 share no factor with 10, changing any one digit of a field to another digit always changes the sum modulo 10. A 3 read as an 8, a 5 read as a 6: the field digit stops matching, and so does the composite.

Most swaps of two neighbours. If two adjacent characters trade places, the sum changes by their difference times the difference of their weights (4, 2 or 6). That is a multiple of 10 only when the two values differ by a multiple of 5. So 98 keyed as 89 is caught, while 16 keyed as 61 slips through, as does 05 keyed as 50.

Damage, glare and retyping. This is where the digits earn their keep. A crease across one character, a reflection on the laminate, a person retyping a zone by hand: these cause most bad MRZ data, and a failed digit is the cheap signal that a field was read wrong. The right response is a new photograph, not a rejection.

Where the arithmetic is blind

The digits have gaps even for honest errors, and a screening flow should know them.

A letter read as a character whose value differs by 10, 20 or 30. Take the specimen document number L898902C3, whose check digit is 6. Replace the C (value 12) with a 2 (value 2): the sum drops by 10 × 3 = 30, from 316 to 286, and the digit is still 6. The composite, which covers the same characters with the same weight, also still passes. Pairs like B and 1, or C and 2, can trade places without a sound.

Fields no digit covers. The name, the issuing state, the nationality and the sex sit outside every field digit, and the composite skips nationality and sex too. A misread name is silent; only a comparison with the printed page shows it.

What it cannot catch at all

Everything in this section passes the MRZ check digits, by construction.

A forged document with an internally consistent zone. The algorithm is public and takes twenty lines of code. Anyone who produces a fake document computes the digits correctly, because a wrong digit is the one mistake every reader catches for free. A passing zone says the zone was read correctly. It says nothing about whether the document is genuine.

Data copied from a real person. A zone built from someone else's real name, birth date and document number has valid digits, because the data is real. Whether the person in front of the camera is that person is a different question: a face match against the document photo, a liveness check, or a check against an issuer's lost-and-stolen list where you have access to one.

An expired document. The expiry date 120415 passes its check digit, and it is the 15th of April 2012. Expiry is a rule about dates, not a checksum: compare the date with today. Remember that YYMMDD has no century, so decide it from context; the YYMMDD date format covers how. The MRZ check digit calculator on this site marks a past expiry date as expired next to the digits, so you can see both answers side by side.

A substituted photo. The MRZ carries no picture and no picture hash. A photo swapped on the data page leaves the zone untouched.

A cloned or altered chip. On a passport with a chip, the data groups are signed by the issuing state, and verifying that signature (Passive Authentication, ICAO Doc 9303 Part 11) is how you learn the data came from the issuer. That needs an NFC read and the issuer's certificates. A photo of the page, however good, cannot answer it. The MRZ only supplies the access key used to open the chip.

The one check people skip: the zone against the page

A document says most things twice: once in the MRZ and once in the visual inspection zone (VIZ), the printed page a person reads. The two were produced together by one issuer, so on a genuine, undamaged document they agree.

That makes a mismatch more interesting than a failed digit. A failed digit usually means a bad photograph. Passing digits beside a printed surname, birth date or document number that says something else mean one side was misread or changed after printing. Either way it is a document for a person to look at.

The comparison has to be done on purpose. Read both zones, normalise the printed values (dates to the same form, names reduced to bare capital letters), and compare field by field. Allow for transliteration: a printed Müller can be MUELLER or MULLER in the zone, depending on the issuer. The MRZ formats page shows which fields each layout carries, and therefore which ones can be compared.

A screening order that holds up

  1. Shape. Line count and length decide TD1, TD2 or TD3. A wrong shape is a bad read.
  2. Check digits. A failure means re-photograph, not reject.
  3. Zone against page. A mismatch with passing digits goes to a person.
  4. Dates. Expiry against today; birth date against your age rules.
  5. Identity. Face match and liveness, against the document photo.
  6. Authenticity. Chip verification over NFC, or physical security features checked by a service built for that.

Steps 1 to 4 are cheap and run on any image. Steps 5 and 6 are separate products, and no amount of MRZ arithmetic replaces them.

Where doc.cheap fits

The doc.cheap passport OCR API covers steps 1 to 4 and reports honestly on the rest. Its response carries mrz.status: passed, failed or absent. On failed, mrz.reason names what did not check out, either a digit (Check digit failed for: document number) or a disagreement with the printed page (MRZ does not match the visual zone for: surname), because both zones are read and compared by default. The zone itself comes back verbatim in mrz.lines and mrz.text, so you can re-run the digits in your own code. document.expiry_date, document.is_expired and document.days_remaining answer step 4.

It is a recognition service, not a verification one. Its authenticity block reads not_checked rather than implying a pass, and photo and chip checks stay with the tools built for them. To try a zone without any code, paste it into the MRZ check digit calculator; it runs in the browser and shows each computed digit beside the printed one. If you are choosing a hosted service, the passport OCR API comparison sets the published prices side by side.

A question for you: which of these gaps has your onboarding flow met in practice? Tell us in the comments below.