piidetectionapi.com
Home
Solutions - Fundamentals
What Is PII Detection? NER vs Regex vs Rules Accuracy, Precision & Recall PII in Test Data
Solutions - Compliance
GDPR Personal Data HIPAA PHI Detection CCPA / CPRA PCI DSS Card Data
Solutions - AI & LLM Safety
LLM Guardrails Chatbot PII Filtering RAG Pipelines
Solutions - Data Discovery & DLP
Data Loss Prevention Log File Scanning Support Tickets Email Scanning Documents & PDFs Database Discovery ETL & Streaming Pipelines
Industries - Financial
Banking Fintech Insurance
Industries - Healthcare
Healthcare Pharma & Clinical Trials Telehealth
Industries - Public Sector & Legal
Government & FOIA Law Enforcement Law Firms & eDiscovery Education (FERPA)
Industries - Technology
SaaS Platforms Cybersecurity & IR Telecommunications Gaming & Platforms
Industries - Other
HR & Recruiting Retail & E-commerce Call Centers & BPO Real Estate Travel & Hospitality Marketing & AdTech
How-to Guides - Identity & Contact
Detect Names Detect Email Addresses Detect Phone Numbers Detect Physical Addresses Detect Dates of Birth
How-to Guides - IDs & Financial
Detect SSNs Detect Passport Numbers Detect Drivers Licenses Detect Credit Card Numbers Detect Bank Accounts & IBAN
How-to Guides - Technical & Health
Detect IP & Device IDs Detect Medical Records & PHI
Resources
Pricing API Docs Supported Entities Languages About Contact Sign In Try the Live Demo Get Started
How-To Guide

How to Detect Passport Numbers

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.

12 min read
cURL, Python & JavaScript examples
Updated Aug 2026

Overview

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.

Input Text
Traveler: Anna Keller, passport C01X00T47 (DE), DOB 14.02.1991. Please reissue the boarding pass.
Detected & Masked Output
Traveler: [PERSON_NAME], passport [PASSPORT_NUMBER] (DE), DOB [DATE_OF_BIRTH]. Please reissue the boarding pass.
Country-Aware
Knows issuing formats from the US and EU to India, China, and beyond
MRZ Detection
Recognizes TD3 machine-readable zone lines in OCR output and pasted text
Context-Driven
Separates passport numbers from booking codes and invoice IDs

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.

Why Passport Numbers Matter

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.

A premium target for identity fraud

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.

Regulatory classification

  • GDPR: a passport number is personal data of a directly identifying kind. Supervisory authorities have repeatedly treated passport data exposure as a high-severity breach requiring individual notification under Article 34, and storing it longer than the verification purpose requires violates data minimization.
  • US state breach statutes: most states amended their notification laws in recent years to add passport numbers explicitly to the definition of breach-triggering personal information, alongside driver's license and account numbers.
  • KYC/AML regimes: banks and fintechs are required to collect passport data — which makes controlled handling, not avoidance, the compliance objective. Knowing exactly where that data ends up after collection is the hard part.
  • APPI, PIPL, DPDP and other national laws: Japan, China, and India all treat government identity numbers as sensitive or specially regulated categories with their own handling rules.

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.

PASSPORT_NUMBER vs NATIONAL_ID

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.

Quick Start

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.

Passport Number Formats by Country

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 States9 digits; next-generation books: 1 letter + 8 digits530124897, A12345678Letter-prefixed numbers rolled out with the Next Generation Passport from 2021
United Kingdom9 digits925076473Purely numeric; collides easily with other 9-digit IDs
Germany9–10 alphanumericC01X00T47Uses a restricted letter set (C, F, G, H, J, K …) chosen to avoid OCR confusion with digits
France2 digits + 2 letters + 5 digits12AB34567First two digits encode the issuing year
India1 letter + 7 digitsL8987654Letter indicates series; diplomatic/official books use different prefixes
ChinaE or G + 8 digitsE12345678E-prefix for ordinary electronic passports, G for the earlier series
Canada2 letters + 6 digitsAB123456Consistent scheme across regular and official documents
Australia1–2 letters + 7 digitsPA1234567Letter prefixes advance by series over time
Netherlands2 letters + 6 alphanumeric + 1 digitNW1B2C34Excludes the letter O throughout to prevent 0/O ambiguity
Japan2 letters + 7 digitsTK1234567First letters historically tied to issuing series

Why regex-only detection fails here

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.

Machine-Readable Zones (MRZ)

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 MRZ as it appears in OCR output
P<GBRKELLER<<ANNA<MARIE<<<<<<<<<<<<<<<<<<<<<< 9250764735GBR9102145F3401017<<<<<<<<<<<<<<06
After detection with mask_mode "replace"
P<GBR[PERSON_NAME] [PASSPORT_NUMBER]GBR[DATE_OF_BIRTH]F[PASSPORT_NUMBER]<<<<<<<<<<<<<<06
TD3 line 2 positions Field
1–9Document (passport) number
10Check digit over the document number
11–13Nationality (ICAO three-letter code)
14–19Date of birth (YYMMDD)
20Check digit over date of birth
21Sex (M/F/<)
22–27Expiry date (YYMMDD)
28Check digit over expiry date
29–42Personal number / optional data
43–44Check 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.

Where Passport Data Hides

Understanding where passport numbers accumulate tells you where to point your first scans. Six workflows account for the overwhelming majority of findings.

Travel booking and airlines

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.

Hotels and cross-border check-in

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.

KYC and AML onboarding

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.

Visa and immigration processing

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.

HR right-to-work checks

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.

Customer support with photographed documents

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.

More Code Examples

KYC pipeline with consistent hashing

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"]

Scanning OCR output for MRZ leakage

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;
}

Suppressing booking references with custom_instruction

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.

Best Practices

1. Always pair document numbers with the identity fields around them

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.

2. Put detection at the OCR and ingestion boundaries

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.

3. Choose mask_mode per destination

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.

4. Tune threshold by channel, not globally

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.

5. Mind retention as well as exposure

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.

Edge Cases

Collisions with other identifier families

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.

Old and new series in circulation together

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.

Spacing, hyphens, and transcription habits

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.

Multilingual labels

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.

Partial and self-redacted numbers

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.

Frequently Asked Questions

Can the API tell which country issued a detected passport number?

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.

Does it verify that a passport number is genuine?

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.

How are MRZ lines reported in the response?

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.

Will booking references and ticket numbers trigger false positives?

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.

What about national ID cards used instead of passports?

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).

Can I run this on-premise for immigration or government workloads?

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.

How do I evaluate accuracy on my own documents?

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.

Find Passport Data Before It Leaks

Test passport and national ID detection on your own KYC notes, tickets, or OCR output in seconds.

Try the Live Demo View Pricing