A real run, checked digit by digit
The data page below uses the specimen that ICAO itself publishes in Doc 9303, the passport standard: a passport from the fictional state of Utopia issued to Anna Maria Eriksson. Nobody's real document was used. We ran it through this page on September 25, 2026.


The model's job is only to read: the printed fields and the two MRZ lines, character for character. Whether the passport number and dates can be trusted is then decided in code, by the five check digits the MRZ carries. Here all five match, so the number and both dates are taken from the MRZ, where they're printed in a fixed format.
The five checks on line 2 of the specimen, recomputed independently of the extractor.
| Check | Data | Digit on the passport | Computed | Result |
|---|---|---|---|---|
| Document number | L898902C3 | 6 | 6 | Valid |
| Date of birth | 740812 | 2 | 2 | Valid |
| Date of expiry | 120415 | 9 | 9 | Valid |
| Personal number | ZE184226B<<<<< | 1 | 1 | Valid |
| Composite (number, birth, expiry, personal) | — | 0 | 0 | Valid |
When a check digit fails
To show what happens when something doesn't add up, we changed one character of the document number, C3 to C8, in both the printed field and the MRZ, and left the check digit alone. That's what a mistyped or tampered document looks like.

{
"passport_number": "L898902C8",
"mrz_line_2": "L898902C86UTO7408122F1204159ZE184226B<<<<<10",
"mrz_check_digits": {
"document_number": "invalid",
"date_of_birth": "valid",
"date_of_expiry": "valid",
"personal_number": "valid",
"composite": "invalid"
},
"uncertain_fields": [
"passport_number"
]
}- The model read both lines exactly as printed, including the wrong character.
- The document number check failed (the digit for L898902C8 is 1, not 6), and so did the composite check that covers it.
- A failed check gets one more read with a stronger model, in case the first read was the problem. It read the same thing, because the document really says C8.
- The passport number is returned as printed but listed in uncertain_fields. The dates still passed their own checks, so they stand.
In a KYC flow that list is the useful part: anything in uncertain_fields goes to a person, everything else doesn't have to.
The passport data page and its MRZ
The data page is the page with the photo. It has two zones: the visual inspection zone, the printed fields a person reads (name, nationality, dates, place of birth, authority), and the machine readable zone, two lines of 44 characters at the bottom meant for scanners. Passports use the TD3 format; ID cards use shorter three-line formats and belong on the ID document extractor.
TD3 layout, from ICAO Doc 9303 Part 4, with the specimen's values.
| Line | Positions | Field | Specimen |
|---|---|---|---|
| 1 | 1 | Document type | P |
| 1 | 2 | Subtype (or <) | < |
| 1 | 3–5 | Issuing state | UTO |
| 1 | 6–44 | Surname << given names, < for spaces | ERIKSSON<<ANNA<MARIA<<<… |
| 2 | 1–9 | Document number | L898902C3 |
| 2 | 10 | Check digit | 6 |
| 2 | 11–13 | Nationality | UTO |
| 2 | 14–19 · 20 | Date of birth YYMMDD · check | 740812 · 2 |
| 2 | 21 | Sex (F, M or <) | F |
| 2 | 22–27 · 28 | Date of expiry YYMMDD · check | 120415 · 9 |
| 2 | 29–42 · 43 | Personal number · check | ZE184226B<<<<< · 1 |
| 2 | 44 | Composite check | 0 |
How a check digit is computed
Each character gets a value (0–9 as themselves, A=10 through Z=35, < = 0), is multiplied by 7, 3, 1, 7, 3, 1… in turn, and the digit is the sum modulo 10. For the specimen's document number:
| L | 8 | 9 | 8 | 9 | 0 | 2 | C | 3 | |
|---|---|---|---|---|---|---|---|---|---|
| Value | 21 | 8 | 9 | 8 | 9 | 0 | 2 | 12 | 3 |
| Weight | 7 | 3 | 1 | 7 | 3 | 1 | 7 | 3 | 1 |
| Product | 147 | 24 | 9 | 56 | 27 | 0 | 14 | 36 | 3 |
147 + 24 + 9 + 56 + 27 + 0 + 14 + 36 + 3 = 316, and 316 mod 10 = 6, the digit printed after the number. Dates in the MRZ have two-digit years; a birth year later than the current year belongs to the previous century, and expiry years are read as 20xx.
Extract data from a passport
- Photograph or scan the data page flat, with the whole MRZ in frame. Glare across the MRZ is the most common reason a check fails.
- Drop it above. The default returns every printed field, both MRZ lines, the check results and uncertain_fields.
- Download JSON, Excel or CSV. For onboarding at volume, send each image to the REST API; the output schema is the same.
- Send anything in uncertain_fields, or any passport where a line couldn't be read, to manual review.
What happens to the file
The image is sent to Google's Gemini API for the reading step and isn't used for training. Small files are processed in memory; larger ones sit in private temporary storage only while they're processed and are then deleted. The extracted fields are kept briefly so the page can show them. Details are in the privacy policy.
Sources
- ICAO Doc 9303 Part 4: Specifications for Machine Readable Passports (TD3 layout, check digits, specimen data)
- ICAO Doc 9303 Part 3: Specifications common to all MRTDs
- Runs above: this page, September 25, 2026; see how we test