Learn how to automatically find, classify, and locate dates of birth and age references in text, documents, and data streams using PII Detection API. Handle ambiguous date formats, separate birth dates from ordinary dates with context-aware AI, and meet HIPAA Safe Harbor date requirements.
A date of birth is one of the most consequential pieces of personally identifiable information (PII) an organization can hold. On its own it looks harmless — just a calendar date — but in combination with a name, a ZIP code, or a gender marker it becomes a precise key that can single out an individual from millions of records. That is why birth dates appear on virtually every regulatory list of protected identifiers, from GDPR's definition of personal data to HIPAA's eighteen Safe Harbor identifiers and the data elements that trigger US state breach-notification laws.
Detecting dates of birth in free text is deceptively hard. Dates are everywhere in business data — invoice dates, appointment dates, contract effective dates, shipping dates — and only a small fraction of them are birth dates. A regex can find things that look like dates; it cannot tell you which of those dates reveal when a person was born. PII Detection API solves this with transformer-based named entity recognition (NER) that reads the surrounding context: phrases like "born on", "DOB:", "date of birth", "d.o.b.", age references, and document structure all inform whether a date is classified as DATE_OF_BIRTH or ignored as an ordinary date.
The API is detection-first: it returns each finding as a structured entity with its type, the exact matched text, character offsets (start/end), and a confidence score, so you always know precisely what was found and where. If you also want a scrubbed copy of the input, the optional mask_mode parameter returns a masked version in the same response — but masking is a follow-on step, never a requirement. You can explore both behaviors interactively in the live demo.
Notice what happened above: two dates with identical day and month appear in the same sentence, and only the one introduced by "born" was flagged. The appointment date survived untouched because context — not pattern shape — drove the classification. That distinction is the core of this guide.
Understanding why birth dates deserve special handling helps you decide where in your pipeline detection belongs and how aggressively to configure it. The risks fall into three broad categories: re-identification, fraud, and regulatory exposure.
Privacy researchers have shown repeatedly that a full date of birth combined with a five-digit ZIP code and a gender marker uniquely identifies the majority of the US population — the classic finding from Latanya Sweeney's re-identification work put the figure at roughly 87%. None of those three attributes is a direct identifier on its own; together they act like a fingerprint. This is why "we removed the names" is never a sufficient de-identification story. If your analytics exports, support tickets, or ML training sets still carry birth dates, they very likely still carry identifiable people.
Date of birth is a near-universal ingredient in identity verification. Banks, telecoms, government agencies, and healthcare providers all use it as a knowledge-based authentication factor. A leaked DOB is permanent — unlike a password, a person cannot rotate their birthday — so every exposure compounds lifetime fraud risk. Breach-notification statutes in many US states explicitly list date of birth among the data elements that, combined with a name, trigger mandatory disclosure.
In practice, DOBs surface in far more places than registration forms: intake notes ("pt is a 62 yo F, DOB 4/12/1963"), support transcripts where an agent asks for date of birth to verify identity, scanned KYC documents, HR onboarding emails, insurance claims, exported CRM fields concatenated into free text, and application logs that serialize whole user objects. A detection pass over these streams — before they reach data lakes, LLM prompts, or third-party tools — is the practical way to find what manual review will miss. Pricing for exactly this kind of continuous scanning is on our pricing page.
Tip: Treat DOB detection as a discovery problem first. Run the API in detection-only mode (no mask_mode) across a sample of each data source to learn where birth dates actually live, then decide per-source whether to mask, block, or alert.
The fastest way to start detecting birth dates is a single call to the REST API. Send your text with the DATE_OF_BIRTH and AGE entity types, and the API returns every match with offsets and confidence scores, plus an optional masked 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": "Applicant: Maria Keller, DOB 03/07/1985, interview on 09/02/2026.", "entities": ["DATE_OF_BIRTH", "AGE"], "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": "Applicant: Maria Keller, DOB 03/07/1985, interview on 09/02/2026.", "entities": ["DATE_OF_BIRTH", "AGE"], "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: "Applicant: Maria Keller, DOB 03/07/1985, interview on 09/02/2026.", entities: ["DATE_OF_BIRTH", "AGE"], 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 the birth date — and only the birth date. The interview date is left alone:
{
"detected_entities": [
{
"type": "DATE_OF_BIRTH",
"text": "03/07/1985",
"start": 29,
"end": 39,
"confidence": 0.97
}
],
"anonymized_text": "Applicant: Maria Keller, DOB [DATE_OF_BIRTH], interview on 09/02/2026.",
"entities_detected": 1,
"processing_time_ms": 142,
"mask_mode_used": "replace",
"status": 200
}
A few notes on the request shape: api_type is always "pii_detection"; text accepts up to 50,000 characters per request; and if you omit the entities array, the API scans for all 150+ supported types — the full catalog is on the entities page. Because the example above passed only DATE_OF_BIRTH and AGE, the person's name was deliberately not flagged; in production you would usually scan for names too.
The first obstacle in DOB detection is that humanity has never agreed on how to write a date. The string 03/07/1985 means March 7th to an American and 3 July to nearly everyone else. A detector that assumes one convention will silently mis-parse a large share of international data — and a detector that only knows numeric formats will miss written-out dates entirely.
PII Detection API recognizes birth dates across numeric, written, abbreviated, and mixed formats, in more than 60 languages. The model does not need the format declared in advance; it infers likely conventions from language, surrounding text, and internal consistency (a "13" in the first position cannot be a month, for example). The table below shows the major format families you should expect in real-world data.
| Format | Example | Common Regions | Ambiguity Notes |
|---|---|---|---|
MM/DD/YYYY |
03/07/1985 |
United States, Philippines | Collides with DD/MM when day ≤ 12; context or locale needed to disambiguate. |
DD/MM/YYYY |
07/03/1985 |
UK, EU, India, Australia, most of the world | Same collision in reverse; separators vary (/, ., -). |
YYYY-MM-DD (ISO 8601) |
1985-03-07 |
Databases, APIs, East Asia (with . or 年月日) |
Unambiguous; common in exported records and logs. |
DD.MM.YYYY |
07.03.1985 |
Germany, Austria, Russia, Central/Eastern Europe | Dot separator; German records often prefix with geb. ("born"). |
| Written out (long) | March 7, 1985 / 7 March 1985 |
Formal documents, letters, legal text | Month-name order differs US vs. UK; multilingual month names required. |
| Abbreviated | 7 Mar 85 / Mar-07-85 |
Forms, tickets, legacy systems | Two-digit years need pivot logic — 85 is 1985, but 05 is probably 2005. |
| Compact numeric | 19850307 / 030785 |
MRZ lines, legacy mainframe exports, national IDs | No separators at all; frequently embedded inside longer identifiers. |
| Partial | born in 1985 / birthday: March 7 |
Social media, bios, casual text | Year-only or day-month-only still narrows identity; see edge cases. |
Two-digit years deserve special mention because they are common precisely where birth dates are common: old forms and legacy exports. Interpreting 02/04/56 requires a century pivot, and a birth-date-aware model applies different priors than a generic date parser — a person born in 2056 does not exist, so 56 in a DOB context almost certainly means 1956. Ordinary date parsers get this wrong constantly; a purpose-built PII model does not.
Warning: If you pre-normalize dates before scanning (for instance, reformatting everything to ISO 8601 in an ETL step), you may destroy the contextual cues — "DOB:", "geb.", "né le" — that identify a date as a birth date. Always run detection on the original text, not on a transformed copy.
Format recognition finds dates; context decides which ones matter. This is the step where regex-based tools fail hardest, because a pattern like \d{2}/\d{2}/\d{4} matches invoice dates, due dates, expiry dates, and birth dates with perfect indifference. Flag them all and your masked output becomes useless — every timestamp in a support log would vanish. Flag none and you leak PII. The only workable answer is classification by context.
PII Detection API's transformer NER weighs many overlapping cues when deciding whether a date is a DATE_OF_BIRTH:
03/07/2031 cannot be one (outside neonatal contexts the model treats future dates as non-DOB).Equally important is what does not get flagged when you request DATE_OF_BIRTH: appointment and admission dates, order and invoice dates, contract effective dates, card expiration dates (those belong to CREDIT_CARD_EXPIRATION_DATE), document issue/expiry dates on passports and licenses, and historical dates in narrative text ("the company was founded in 1985"). Keeping these intact preserves the analytical value of the text — a support ticket with its timeline destroyed is much harder to work with than one where only the customer's birth date is masked.
Real datasets have quirks the defaults cannot anticipate. The custom_instruction parameter accepts a natural-language rule (up to 500 characters) that adjusts behavior for your domain — for example, treating all dates in a pediatric-intake field as birth dates, or explicitly ignoring dates inside quoted email headers:
resp = requests.post( "https://piidetectionapi.com/api/moderate.php", json={ "api_key": "YOUR_API_KEY", "api_type": "pii_detection", "text": ticket_text, "entities": ["DATE_OF_BIRTH", "AGE"], "custom_instruction": "Keep appointment and delivery dates. Only flag dates that reveal when a person was born.", "mask_mode": "replace", }, timeout=30, )
If you work with US health data, birth dates come with the strictest and most specific rules anywhere in privacy law. HIPAA's Safe Harbor de-identification method (45 CFR §164.514(b)(2)) lists eighteen identifier categories that must be removed, and category three is dates: all elements of dates (except year) directly related to an individual — birth date, admission date, discharge date, date of death — plus a special rule for advanced age.
Read carefully, that means Safe Harbor does not simply say "remove the DOB." It says the month and day must go while the year may stay, and it adds an aggregation rule at the top of the age range because extreme ages are themselves identifying: there are few enough 97-year-olds in any dataset that age alone can single them out.
| Element | Safe Harbor Rule | Example Transformation |
|---|---|---|
| Birth date (month/day) | Must be removed | 03/07/1985 → 1985 or [DATE_OF_BIRTH] |
| Birth year | May be retained (if age ≤ 89) | born 1985 → unchanged |
| Admission / discharge / death dates | Month and day must be removed | admitted 06/14/2026 → admitted 2026 |
| Age 90 or over | Aggregate into a single "90+" category | a 94-year-old patient → a 90+ year-old patient |
| Birth years implying age > 89 | Aggregate (year alone reveals 90+) | born 1933 → born before 1937 / masked |
For a Safe Harbor pipeline you rarely scan for dates alone. A realistic pass combines DATE_OF_BIRTH and AGE with the other identifiers that appear alongside them in clinical text — names, medical record numbers, and contact details — and uses mask_mode: "redact" when the downstream consumer must never see even a placeholder hint:
// HIPAA-oriented scan of a clinical note 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: clinicalNote, entities: [ "DATE_OF_BIRTH", "AGE", "PERSON_NAME", "MEDICAL_RECORD_NUMBER", "HEALTH_INSURANCE_ID", "PHONE_NUMBER", "ADDRESS" ], mask_mode: "redact", // remove matches entirely, no placeholders threshold: 0.4 // recall-first for compliance scans }), }); const data = await resp.json(); console.log(data.entities_detected, "identifiers found");
Note: Safe Harbor is one of two HIPAA de-identification paths (the other is Expert Determination). Detection output — entity counts, types, and confidence distributions — is also exactly the evidence an expert-determination review wants to see. Our HIPAA PHI detection guide walks through all eighteen identifiers in depth.
Birth dates and ages are two representations of the same fact, which is why this guide treats the AGE entity as a first-class citizen alongside DATE_OF_BIRTH. Masking the DOB while leaving "the patient is a 41-year-old teacher from Springfield" in place removes very little privacy risk: anyone can subtract.
The AGE entity type covers the many ways age appears in text: "41 years old", "aged 41", "41 yo", "41 y/o", clinical shorthand like "41M", age ranges ("in her early forties"), and milestone phrasing ("turns 18 next month"). Each is returned with offsets and confidence like any other entity, so you can decide per use case whether an age is sensitive — an exact age in a medical record usually is, while "adults over 18" in marketing copy is not, and the model's context awareness reflects that difference.
Age and date information combine in ways that reconstruct a hidden DOB. Consider what a determined reader can infer:
This is why a defensible policy scans for both entity types together, and why HIPAA's rules cover ages over 89 and not just dates. When your threat model includes deliberate re-identification, mask AGE wherever you mask DATE_OF_BIRTH; when it only includes casual exposure, masking DOB alone may be acceptable. The point is to make that choice explicitly rather than by omission.
For data-discovery and audit jobs you often want findings without altering the text at all. Omit mask_mode semantics by simply consuming only the detected_entities array — the structured findings are the product, and the offsets let you build heat maps of where DOBs concentrate across your systems:
import requests def audit_dob(records): findings = [] for rec_id, text in records: resp = requests.post( "https://piidetectionapi.com/api/moderate.php", json={ "api_key": "YOUR_API_KEY", "api_type": "pii_detection", "text": text, "entities": ["DATE_OF_BIRTH", "AGE"], "threshold": 0.4, # favor recall in audits }, timeout=30, ) data = resp.json() for e in data["detected_entities"]: findings.append({ "record": rec_id, "type": e["type"], "span": (e["start"], e["end"]), "confidence": e["confidence"], }) return findings
Sometimes you need to remove birth dates but still group records by them — cohort analysis, duplicate detection, householding. mask_mode: "hash" replaces each value with a consistent hash, so identical DOBs produce identical tokens without revealing the date itself:
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": "Twins: Ana, DOB 04/02/2001 and Bea, DOB 04/02/2001.", "entities": ["DATE_OF_BIRTH"], "mask_mode": "hash" }' # Both identical DOBs map to the same hash token, # so the twin relationship survives de-identification.
The threshold parameter (0–1, default 0.5) sets the minimum confidence a detection needs to be returned. Lower it for compliance scans where a missed DOB is expensive; raise it for user-facing redaction where a false positive visibly damages the text:
# Recall-first: catch borderline dates, review them manually audit = requests.post(API_URL, json={**base, "threshold": 0.35}, timeout=30) # Precision-first: only mask what the model is sure about display = requests.post(API_URL, json={**base, "threshold": 0.8}, timeout=30) # Route mid-confidence findings to a human queue for e in audit.json()["detected_entities"]: if e["confidence"] < 0.6: send_to_review_queue(e)
As covered in the age section, a masked birth date next to an intact exact age is barely masked at all. Unless you have a specific reason to keep ages, request both entity types in every DOB-focused scan.
Run detection against the raw input — before normalization, translation, or templating strips the contextual labels that identify a date as a birth date. You can always apply masking later using the returned offsets; you cannot recover context that an upstream transformation destroyed.
Use a low threshold (0.3–0.45) when scanning data lakes, logs, or exports where a leaked DOB creates regulatory exposure, and accept that reviewers will discard some false positives. Use a high threshold (0.7+) for inline chat or document display where over-masking degrades the user experience. There is no single correct value — there is a correct value per pipeline.
Resist the temptation to mask every date "to be safe." Timelines are often the operationally important part of a ticket, claim, or note, and destroying them pushes teams to work from unmasked copies — the worst possible outcome. Context-aware detection exists precisely so you do not have to make that trade.
When you build monitoring around DOB detection, store entity types, offsets, confidence scores, and counts — never the matched text itself. Your audit trail should prove that a birth date was found and handled without becoming a new copy of the birth date.
Every organization has house styles: a claims system that writes D.O.B: 07-MAR-85, a chatbot that asks "and your birthday?", a legacy export with 19850307 in column 12. Paste real (or realistically synthetic) samples into the interactive demo before going live, and encode anything unusual in a custom_instruction.
"Born in 1985" and "her birthday is March 7th" each disclose only part of a DOB, but partial disclosure is still disclosure — a birth year narrows candidates enormously, and a day-month pair combined with an age elsewhere completes the picture. The model flags partial birth dates as DATE_OF_BIRTH when context marks them as birth-related; if your policy allows retaining birth years (as HIPAA Safe Harbor does for most ages), handle that at the masking layer rather than by weakening detection.
Family records, beneficiary lists, and household insurance policies contain several DOBs in close proximity, often in a table-like layout flattened into text. Because each detection carries its own offsets, downstream code can associate each birth date with the nearest name rather than treating the paragraph as one blob.
Many national ID schemes embed birth dates: South African ID numbers begin with YYMMDD, Swedish personnummer with the full birth date, and machine-readable passport lines carry a six-digit DOB. When such composite identifiers appear, the appropriate entity is usually NATIONAL_ID or PASSPORT_NUMBER — scanning for those alongside DATE_OF_BIRTH ensures the embedded date does not slip through inside a longer token.
Transcribed audio produces dates a regex will never see: "I was born on the third of July, nineteen eighty-five" or "she'll be forty next spring." The NER model handles written-out and spelled-number forms; for call-center transcripts, pair this guide with age detection since callers state ages more often than full dates.
"Napoleon was born on 15 August 1769" is a birth date but not PII — no living, identifiable person is at risk. The model uses context to lower confidence on clearly historical or fictional references, and the threshold parameter gives you the final say on where to draw the line.
Note: Highly compressed formats like 030785 with no label and no nearby person reference are genuinely ambiguous — that string could be a part number. Expect lower confidence scores there, and use custom_instruction to declare field semantics when you know them ("column after the name is always a DOB in DDMMYY").
Through context rather than pattern shape. The transformer model reads the words around each date — labels like "DOB" or "born on", nearby names, corroborating age mentions, and document structure — and only classifies a date as DATE_OF_BIRTH when the context supports it. Appointment, invoice, and expiry dates in the same text are left untouched.
Yes. Language, locale cues, and internal consistency (values over 12 can only be days) drive the interpretation. Note that for detection purposes the exact interpretation often does not matter: 03/07/1985 is flagged as a birth date either way, and the offsets tell you exactly which characters to mask.
Yes — the entities array gives you independent control. Request ["AGE"] alone, ["DATE_OF_BIRTH"] alone, or both. You can also use exclude_entities to scan for everything except a type. For most privacy purposes we recommend scanning both together, since each can be inferred from the other.
Under the Safe Harbor method, month and day of birth must be removed while the year may remain, and all ages over 89 (or birth years implying them) must be aggregated into a single 90+ category. The API's detections give you the spans to implement whichever transformation your compliance team specifies. See our HIPAA PHI guide for the full identifier list.
The model detects birth dates in over 60 languages, including written-out month names, language-specific labels ("Geburtsdatum", "fecha de nacimiento", "生年月日"), and locale-specific numeric conventions. See the supported languages page for the current list.
With mask_mode: "replace" the age becomes [AGE], preserving readability ("the patient is [AGE]"). redact removes the span entirely, and hash substitutes a consistent token. Choose per use case; the detection itself is identical in all three modes.
Typical requests return in well under a second — the processing_time_ms field reports exact latency per call. Each request accepts up to 50,000 characters; split longer documents on natural boundaries and batch the calls. Volume pricing is listed on the pricing page.
Try the live demo with your own text, or get an API key and scan your first documents in minutes.
Try the Live Demo View Pricing