Learn how to find and classify passport numbers, national ID numbers, and machine-readable zone (MRZ) strings buried in booking systems, KYC records, OCR output, and support conversations — with per-country format awareness, character offsets, and confidence scores from PII Detection API.
A passport is the strongest single identity credential most people will ever hold, and its number is the key that unlocks it. Unlike a leaked password, a leaked passport number cannot be rotated with a click — replacing the document takes weeks, costs money, and most victims never learn their number escaped in the first place. Yet passport numbers flow through ordinary business text all day long: an airline agent pastes one into a rebooking note, a compliance analyst quotes one in a KYC escalation, an OCR pipeline dumps a full machine-readable zone into a searchable index, a traveler types theirs into a hotel chat widget at 2 a.m.
PII Detection API locates these identifiers in raw text and returns each one as a structured entity — its type, the exact matched string, start and end character offsets, and a confidence score — so you can alert, quarantine, or mask with precision. Because passport numbers look like almost every other alphanumeric business identifier, the engine leans on two things regex cannot provide: knowledge of per-country issuing formats and transformer-based reading of the surrounding context. "Booking L8987654" and "passport L8987654" contain the same token; only one of them should end up in your findings.
This guide covers the two entity types involved, passport number structures for the ten most commonly processed issuing countries, how machine-readable zones work and why they are a detection priority of their own, the industry workflows where passport data accumulates, and full request examples in cURL, Python, and JavaScript against the moderation endpoint. To watch detection run on a sample itinerary or KYC note right now, paste it into the interactive demo.
Passport numbers occupy an unusual position in the PII hierarchy: they are simultaneously a government identifier, a travel credential, and — combined with the name, date of birth, and expiry date that almost always accompany them — a near-complete identity kit. That combination shapes both the threat model and the regulatory treatment.
On underground markets, a full passport record (number, name, DOB, expiry, nationality) consistently prices above credit card data, because it ages slowly and supports higher-value fraud: opening bank accounts, passing remote KYC checks with synthetic documents, laundering identities across borders. Several of the largest breaches of the past decade — hotel loyalty programs and airlines among them — were notable precisely because passport numbers were in the stolen data, and the breached companies ended up offering passport replacement cost coverage as part of remediation. The lesson for engineering teams: the passport fields in your systems are the ones attackers will go looking for first.
The collection paradox: regulated onboarding forces you to ingest passport data, but nothing forces it to stay in the systems designed for it. Detection exists to find the copies — the ticket where an agent quoted the number, the log line that captured the OCR payload, the spreadsheet a back-office team exported "temporarily" in 2023.
Because remediation nearly always starts with discovery, detection-first tooling pays for itself quickly here: a single scan of a ticketing archive typically surfaces passport data in places nobody's data map mentioned. Pricing for batch scanning workloads is on the pricing page, and the free tier is sized to let you run a meaningful pilot on your own exports.
PII Detection API separates travel documents from domestic identity documents, because the two circulate in different workflows and often demand different retention policies.
| Entity type | What it matches | Typical contexts | Example |
|---|---|---|---|
PASSPORT_NUMBER |
Passport booklet numbers in any issuing country's format, plus numbers embedded in MRZ lines | Travel bookings, border/visa processing, KYC, right-to-work checks | C01X00T47 |
NATIONAL_ID |
Domestic identity card and civil registry numbers: German Personalausweis, French CNI, Spanish DNI/NIE, Indian Aadhaar, Chinese resident ID, and similar | Domestic onboarding, government services, HR records in ID-card countries | DNI 12345678Z |
The distinction earns its keep in international products. In much of continental Europe, Latin America, and Asia, the national identity card — not the passport — is the default verification document, and users type those numbers into the same free-text channels. A KYC scan that only requests PASSPORT_NUMBER will sail past a Spanish DNI or a Chinese resident ID and report a clean bill of health it has not earned. For that reason, every example in this guide requests both types together, usually alongside PERSON_NAME and DATE_OF_BIRTH — the fields that turn a bare document number into a usable stolen identity. The complete entity catalog, including related types like DRIVERS_LICENSE_NUMBER and SSN, is listed on the entities page.
Note that the US Social Security number, though functionally a national identifier, has its own dedicated SSN entity with format-specific validation — see the SSN detection guide for how the two interact.
Detection is one POST request. Send the text and the identity-document entity list; read back structured entities and, if you set mask_mode, a sanitized copy of the input.
curl -X POST https://piidetectionapi.com/api/moderate.php \ -H "Content-Type: application/json" \ -d '{ "api_key": "YOUR_API_KEY", "api_type": "pii_detection", "text": "KYC escalation: applicant Priya Sharma, passport L8987654 issued in India, DOB 03/07/1988. Secondary doc: DNI 12345678Z.", "entities": ["PASSPORT_NUMBER", "NATIONAL_ID", "PERSON_NAME", "DATE_OF_BIRTH"], "mask_mode": "replace" }'
import requests resp = requests.post( "https://piidetectionapi.com/api/moderate.php", json={ "api_key": "YOUR_API_KEY", "api_type": "pii_detection", "text": "KYC escalation: applicant Priya Sharma, passport L8987654 " "issued in India, DOB 03/07/1988. Secondary doc: DNI 12345678Z.", "entities": ["PASSPORT_NUMBER", "NATIONAL_ID", "PERSON_NAME", "DATE_OF_BIRTH"], "mask_mode": "replace", }, timeout=30, ) data = resp.json() for e in data["detected_entities"]: print(e["type"], e["text"], e["start"], e["end"], e["confidence"])
const resp = await fetch("https://piidetectionapi.com/api/moderate.php", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ api_key: "YOUR_API_KEY", api_type: "pii_detection", text: "KYC escalation: applicant Priya Sharma, passport L8987654 " + "issued in India, DOB 03/07/1988. Secondary doc: DNI 12345678Z.", entities: ["PASSPORT_NUMBER", "NATIONAL_ID", "PERSON_NAME", "DATE_OF_BIRTH"], mask_mode: "replace" }) }); const data = await resp.json(); data.detected_entities.forEach(e => console.log(e.type, e.text, e.start, e.end, e.confidence));
The response identifies all four identity fields with byte-accurate offsets into the original string:
{
"detected_entities": [
{"type": "PERSON_NAME", "text": "Priya Sharma", "start": 26, "end": 38, "confidence": 0.98},
{"type": "PASSPORT_NUMBER", "text": "L8987654", "start": 49, "end": 57, "confidence": 0.96},
{"type": "DATE_OF_BIRTH", "text": "03/07/1988", "start": 80, "end": 90, "confidence": 0.94},
{"type": "NATIONAL_ID", "text": "12345678Z", "start": 111, "end": 120, "confidence": 0.92}
],
"anonymized_text": "KYC escalation: applicant [PERSON_NAME], passport [PASSPORT_NUMBER] issued in India, DOB [DATE_OF_BIRTH]. Secondary doc: DNI [NATIONAL_ID].",
"entities_detected": 4,
"processing_time_ms": 156,
"mask_mode_used": "replace",
"status": 200
}
For pure discovery — alerting without rewriting — omit mask_mode and use only detected_entities. Each request accepts up to 50,000 characters, comfortably a full booking record, KYC case note, or OCR page. Request and response semantics are specified in the API documentation.
There is no international standard for passport numbers — ICAO Doc 9303 standardizes the document layout and the machine-readable zone, but each issuing state chooses its own numbering scheme. Lengths run from six to eleven characters; some are all digits, some mix letters and digits in fixed positions, and several countries changed schemes mid-decade so both old and new formats remain in circulation on valid documents.
| Country | Format | Example | Notes |
|---|---|---|---|
| United States | 9 digits; next-generation books: 1 letter + 8 digits | 530124897, A12345678 | Letter-prefixed numbers rolled out with the Next Generation Passport from 2021 |
| United Kingdom | 9 digits | 925076473 | Purely numeric; collides easily with other 9-digit IDs |
| Germany | 9–10 alphanumeric | C01X00T47 | Uses a restricted letter set (C, F, G, H, J, K …) chosen to avoid OCR confusion with digits |
| France | 2 digits + 2 letters + 5 digits | 12AB34567 | First two digits encode the issuing year |
| India | 1 letter + 7 digits | L8987654 | Letter indicates series; diplomatic/official books use different prefixes |
| China | E or G + 8 digits | E12345678 | E-prefix for ordinary electronic passports, G for the earlier series |
| Canada | 2 letters + 6 digits | AB123456 | Consistent scheme across regular and official documents |
| Australia | 1–2 letters + 7 digits | PA1234567 | Letter prefixes advance by series over time |
| Netherlands | 2 letters + 6 alphanumeric + 1 digit | NW1B2C34 | Excludes the letter O throughout to prevent 0/O ambiguity |
| Japan | 2 letters + 7 digits | TK1234567 | First letters historically tied to issuing series |
Look at that table through a pattern-matcher's eyes and the problem is obvious: "one letter plus seven digits" describes an Indian passport, an Australian passport, half the world's invoice numbers, most airline ticket stock, and a good share of warehouse SKUs. "Nine digits" describes a UK passport, a US passport, a US SSN without hyphens, and an ABA routing number. A regex battery over these formats either drowns you in false positives or gets tuned so tight it misses genuine documents.
The detection model resolves what the pattern cannot: it reads the discourse. Words like "passport", "travel document", "Reisepass", "passeport", nationality mentions, expiry dates, visa vocabulary, and co-occurring identity fields all raise the probability mass on PASSPORT_NUMBER; commerce vocabulary ("order", "invoice", "shipment", "PNR") pushes it down. The confidence score in every response reflects exactly this weighing, which is why a bare 925076473 in a sentence about parcels scores low while the same token after "UK passport no." scores near certainty.
Do not filter results down to "known formats". Issuing states add new series without notice, and older valid documents circulate for a decade. Post-filtering model output against a hand-maintained format list quietly reintroduces the brittleness the model was deployed to remove. If precision is the concern, raise threshold instead.
The two lines of angle-bracket-studded capitals at the bottom of a passport's data page are the machine-readable zone, standardized by ICAO Doc 9303 as the TD3 format: two lines of exactly 44 characters each. The first line carries the document type, issuing state, and the holder's name padded with < filler characters. The second line is the dangerous one — it packs the document number, nationality, date of birth, sex, expiry date, and optional personal number into a single string, each field protected by its own check digit:
| TD3 line 2 positions | Field |
|---|---|
1–9 | Document (passport) number |
10 | Check digit over the document number |
11–13 | Nationality (ICAO three-letter code) |
14–19 | Date of birth (YYMMDD) |
20 | Check digit over date of birth |
21 | Sex (M/F/<) |
22–27 | Expiry date (YYMMDD) |
28 | Check digit over expiry date |
29–42 | Personal number / optional data |
43–44 | Check digit over optional data; composite check digit |
Why does this matter for text scanning? Because identity-verification pipelines OCR passport photos at scale, and the raw MRZ string is exactly the kind of payload that leaks into logs, gets indexed by search, or lands verbatim in a support ticket when an agent copies a failed verification record. One MRZ line is not one identifier — it is the holder's passport number, birth date, sex, nationality, and document expiry in a single 44-character package. The detection engine recognizes MRZ structure (the filler pattern, the check-digit arithmetic computed with 7-3-1 weights, the fixed field grid) and decomposes it into its constituent entities so each is reported and masked individually, exactly as in the example above.
Scan your OCR layer first. If your product photographs identity documents anywhere, run detection over the OCR text before it reaches logging, analytics, or vector indexes. A single missed MRZ in a debug log is a five-field breach, and MRZ strings survive copy-paste perfectly because they are plain ASCII.
Understanding where passport numbers accumulate tells you where to point your first scans. Six workflows account for the overwhelming majority of findings.
Advance Passenger Information rules oblige carriers to collect passport details for international itineraries, so reservation systems handle them by design. The leakage happens around the edges: agent notes in the PNR, rebooking emails, group-travel spreadsheets from tour operators, and chat transcripts where passengers volunteer their document numbers unprompted. The travel & hospitality guide maps these flows in detail.
Many jurisdictions legally require hotels to record guests' passport details. Front-desk systems capture them cleanly; the trouble is the WhatsApp message to the concierge, the emailed scan for an early check-in, and the property-management-system notes field.
Banks, brokers, crypto exchanges, and payment institutions verify identity documents as a legal requirement, then discuss edge cases in case-management tools, screenshots, and internal chat. Compliance analysts quoting document numbers into escalation threads is the single most common finding when financial institutions scan their internal text stores — see the banking guide for how review teams contain it.
Law firms, relocation providers, and corporate mobility teams shuttle passport scans and numbers through email for months per case. Matter-management exports are reliably rich in identity documents.
Employment eligibility verification (I-9 in the US, right-to-work in the UK, equivalents elsewhere) puts passport numbers into HR systems, onboarding emails, and — worst — shared drives holding document photocopies whose filenames and OCR text both carry the number.
Any product with an identity-verification step eventually receives passport photos through its support channel, because users send them when verification fails. Ticket attachments get OCR'd for search, and the extracted text lands in the ticket index. Scanning that index is routinely eye-opening.
Compliance analytics often needs to correlate events about the same document without storing the number itself. mask_mode: "hash" replaces each detected value with a stable token — the same passport number always yields the same hash, so joins and duplicate-document checks still work on sanitized data:
import requests def sanitize_kyc_note(note: str) -> str: resp = requests.post( "https://piidetectionapi.com/api/moderate.php", json={ "api_key": "YOUR_API_KEY", "api_type": "pii_detection", "text": note, "entities": ["PASSPORT_NUMBER", "NATIONAL_ID", "PERSON_NAME", "DATE_OF_BIRTH"], "mask_mode": "hash", # same document => same token }, timeout=30, ) data = resp.json() if data["entities_detected"]: print(f"masked {data['entities_detected']} identity fields " f"in {data['processing_time_ms']}ms") return data["anonymized_text"]
Run every OCR result through detection before it touches storage. A high-recall threshold suits this boundary — you would rather review a booking code than index an MRZ:
// Gate between the OCR service and the ticket search index async function gateOcrText(ocrText) { const resp = await fetch("https://piidetectionapi.com/api/moderate.php", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ api_key: process.env.PII_API_KEY, api_type: "pii_detection", text: ocrText, entities: ["PASSPORT_NUMBER", "NATIONAL_ID", "PERSON_NAME", "DATE_OF_BIRTH"], mask_mode: "redact", // strip identity fields entirely threshold: 0.4 // favor recall at the storage boundary }) }); const data = await resp.json(); const passports = data.detected_entities .filter(e => e.type === "PASSPORT_NUMBER"); if (passports.length > 0) { await raiseDlpAlert("passport data in OCR pipeline", passports.length); } return data.anonymized_text; }
Travel text is dense with PNR locators and ticket numbers that share the shape of passport numbers. Rather than raising the threshold globally, describe the exclusion in plain language:
curl -X POST https://piidetectionapi.com/api/moderate.php \ -H "Content-Type: application/json" \ -d '{ "api_key": "YOUR_API_KEY", "api_type": "pii_detection", "text": "PNR X9K2LM, e-ticket 0162345678901. Passenger passport E12345678 (CN) verified at gate.", "entities": ["PASSPORT_NUMBER", "NATIONAL_ID"], "mask_mode": "replace", "threshold": 0.5, "custom_instruction": "Do not flag airline booking references (PNR locators) or 13-digit e-ticket numbers; only flag genuine travel document numbers." }'
Only E12345678 is returned; the PNR and e-ticket number stay untouched in anonymized_text.
A passport number alone is a moderate risk; a passport number with name, birth date, and expiry is a complete stolen identity. Scan for PASSPORT_NUMBER, NATIONAL_ID, PERSON_NAME, and DATE_OF_BIRTH together so your alerts reflect real exposure, and so masking removes the whole kit rather than leaving a name-plus-DOB skeleton behind.
The two chokepoints that matter are where images become text (OCR) and where text becomes stored (tickets, logs, indexes). A synchronous check at those boundaries — typically well under 200ms — prevents accumulation permanently, whereas archive scans only clean up what already leaked.
Use "redact" for logs and analytics stores that have no business holding document numbers at all, "replace" for human-readable transcripts where reviewers need to see that a passport was mentioned, and "hash" where deduplication or correlation must survive sanitization.
Travel-operations text is full of lookalike codes, so inline masking there benefits from a threshold around 0.6; a quarterly audit of an HR document store should drop to 0.35–0.4 and accept human review of the extra candidates. The score exists precisely so each pipeline can make its own precision/recall trade.
Under GDPR's minimization principle, a passport number kept after verification completes is a finding even if it never leaks. Pair detection with retention policy: scan stores that should be clean (marketing databases, product analytics, general support archives) and treat any hit as a process defect, not just a masking task.
Nine-digit passport numbers collide with SSNs and routing numbers; letter-plus-digits formats collide with driver's licenses and order IDs. When multiple labels are plausible, the engine assigns the one the context supports best and prices the ambiguity into the confidence score. If your text mixes families — a KYC note quoting both a passport and a driver's license — request both entity types and each token is classified independently; the driver's license guide covers the mirror-image problem.
Because passports live for ten years, format transitions (the US letter-prefix rollout, China's E-to-G-to-E series history) mean two valid shapes per country coexist for a decade. The model carries both; this is another reason not to bolt a "current formats only" filter onto its output.
People write document numbers the way they read them aloud: L 898 7654, C01-X00-T47, lowercase, or with an O typed for 0. Detection normalizes spacing, casing, and common substitutions before matching, while offsets always point into the original text so masking never misses by a character.
The vocabulary that signals a passport context varies by language — "Reisepass-Nr.", "numéro de passeport", "número de pasaporte", "паспорт", "旅券番号" — and is modeled natively across 60+ languages rather than translated, so a German HR email or a Japanese booking note resolves just as reliably as English. See the supported languages page for coverage.
Users write "passport ending 4T47" and agents paste last-four fragments. Bare fragments are not flagged as full documents by default — masking them usually destroys utility without reducing risk — but a stricter policy can widen the net with custom_instruction if your regulator expects fragments treated as identifiers.
Often, but not always. When the context names the country or an MRZ carries the ICAO nationality code, issuing state is unambiguous. A bare number matching several countries' schemes is still detected and labeled PASSPORT_NUMBER; the format alone rarely pins the issuer, and the API deliberately does not guess.
No — and no text API can. Detection validates structure (including MRZ check digits, which are computable) and context, telling you a string is being used as a passport number. Whether that document exists in an issuing state's registry requires a government verification channel, which is a separate concern from finding the number in your data.
As their constituent entities: the document number as PASSPORT_NUMBER, the birth date as DATE_OF_BIRTH, the name as PERSON_NAME, each with its own offsets inside the MRZ string. This lets you mask the sensitive fields while leaving the structural filler intact, or drop the whole line — your choice, made with full information.
Occasionally, in travel-heavy text where the surrounding vocabulary is genuinely ambiguous. Three remedies, in order: rely on the default context weighing (it handles most cases), raise threshold for that channel, or state the exclusion explicitly in custom_instruction as shown above. Avoid post-filtering by format list.
Request NATIONAL_ID alongside PASSPORT_NUMBER — always, in international products. Spanish DNI/NIE, German Personalausweis numbers, Indian Aadhaar, Chinese resident IDs, and similar civil identifiers are detected under their own type so your handling policies can differ where regulations do (Aadhaar, notably, has India-specific display rules).
Yes. The same models ship in an on-premise deployment for organizations whose identity-document data cannot leave their network boundary — common in government, border services, and some banking contexts. Contact us for licensing; hosted tiers are on the pricing page.
Paste representative samples — booking notes, KYC escalations, OCR output — into the live demo, which runs production models and shows every entity with type and confidence. For a systematic pilot, create a free API key and score a labeled sample of your real traffic before committing thresholds.
Test passport and national ID detection on your own KYC notes, tickets, or OCR output in seconds.
Try the Live Demo View Pricing