Learn how to find medical record numbers, health insurance IDs, diagnoses, and prescriptions in clinical notes, EHR exports, and healthcare communications with PII Detection API — and how these entity types map onto HIPAA's 18 Safe Harbor identifiers.
A medical record number (MRN) is the key that unlocks a patient's entire clinical history. It appears at the top of every chart, in the subject line of referral faxes, inside discharge summaries, in billing disputes, in patient-portal support tickets — everywhere healthcare data flows. Because a single MRN links to diagnoses, medications, lab results, and demographics, HIPAA treats it as protected health information (PHI), and its accidental exposure is a reportable breach in most scenarios.
Detecting MRNs by hand is a losing battle. Unlike Social Security numbers, MRNs have no universal format: one health system issues 7-digit numbers, another prefixes them with facility codes ("MRN: SJH-0042371"), a third embeds check digits or leading zeros. And clinical free text surrounds them with other numeric identifiers — encounter numbers, accession numbers, order IDs, insurance member IDs — that look nearly identical. A regex that matches your MRNs will also match half the other numbers in the note.
PII Detection API approaches the problem the way a trained medical-records clerk would: by reading the context. The transformer-based model recognizes the labels, section headers, and phrasing that surround record numbers in real clinical text, and returns each match with its exact character offsets and a confidence score. Alongside MEDICAL_RECORD_NUMBER, the API detects the wider clinical family — HEALTH_INSURANCE_ID, DIAGNOSIS, PRESCRIPTION, plus MEDICAL_DATA, TREATMENT, and BLOOD_TYPE — so one call covers a full PHI sweep.
Understanding why MRN detection is difficult explains why context-aware AI outperforms pattern matching for this entity type more dramatically than for almost any other.
MRNs are assigned by each provider organization independently. Common shapes include plain 6–10 digit integers (often with meaningful leading zeros), alphanumeric composites with facility prefixes (SJH-0042371, E12345678), and enterprise master patient index (EMPI) identifiers that differ from the facility-level number for the same patient. After a hospital merger, a single chart may carry two or three legacy MRNs cross-referenced in the header. A detection system tuned to one issuer's format silently misses every other issuer's numbers.
Clinical documents are full of numbers that are not MRNs: encounter and visit numbers, lab accession numbers, order and claim IDs, NPI numbers for providers, CPT and ICD codes, dosages, vital signs, and dates. The only reliable way to tell "0042371" the MRN from "0042371" the claim number is the surrounding text — the label before it, the section it appears in, the other entities near it. That is exactly the signal a transformer NER model consumes and a regex cannot.
Real documents label record numbers as "MRN", "MR#", "Med Rec No", "Chart #", "Record:", or nothing at all — in tabular chart headers the number may sit under an implicit column. The model has seen these variants across millions of documents and weights partial evidence: an unlabeled 7-digit number in a chart header next to a patient name and DOB scores far higher than the same digits in a shipping notice.
Tip: If your organization uses a known MRN scheme, you can raise recall further with a custom_instruction such as "treat 7-digit numbers beginning with 00 that appear near patient names as medical record numbers". Natural-language instructions tune the detector without deploying custom models.
The fastest way to see PHI detection on your own text is the interactive demo — paste a de-identified sample note and watch the entities light up. In code, a full PHI sweep is one request:
import requests resp = requests.post( "https://piidetectionapi.com/api/moderate.php", json={ "api_key": "YOUR_API_KEY", "api_type": "pii_detection", "text": "Pt Jane Rowe, MRN 0042371, BCBS member ZGP882440116. " "Dx: hypertension. Rx: lisinopril 10mg daily.", "entities": ["MEDICAL_RECORD_NUMBER", "HEALTH_INSURANCE_ID", "DIAGNOSIS", "PRESCRIPTION", "PERSON_NAME"], "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"]) print(data["anonymized_text"])
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: "Pt Jane Rowe, MRN 0042371, BCBS member ZGP882440116. Dx: hypertension. Rx: lisinopril 10mg daily.", entities: ["MEDICAL_RECORD_NUMBER", "HEALTH_INSURANCE_ID", "DIAGNOSIS", "PRESCRIPTION", "PERSON_NAME"], 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) ); console.log(data.anonymized_text);
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": "Pt Jane Rowe, MRN 0042371, BCBS member ZGP882440116. Dx: hypertension. Rx: lisinopril 10mg daily.", "entities": ["MEDICAL_RECORD_NUMBER", "HEALTH_INSURANCE_ID", "DIAGNOSIS", "PRESCRIPTION", "PERSON_NAME"], "mask_mode": "replace" }'
The response pinpoints each PHI element and returns the masked note in the same round trip:
{
"detected_entities": [
{"type": "PERSON_NAME", "text": "Jane Rowe", "start": 3, "end": 12, "confidence": 0.97},
{"type": "MEDICAL_RECORD_NUMBER", "text": "0042371", "start": 18, "end": 25, "confidence": 0.96},
{"type": "HEALTH_INSURANCE_ID", "text": "ZGP882440116", "start": 39, "end": 51, "confidence": 0.95},
{"type": "DIAGNOSIS", "text": "hypertension", "start": 57, "end": 69, "confidence": 0.93},
{"type": "PRESCRIPTION", "text": "lisinopril 10mg daily", "start": 75, "end": 96, "confidence": 0.94}
],
"anonymized_text": "Pt [NAME], MRN [MEDICAL_RECORD_NUMBER], BCBS member [HEALTH_INSURANCE_ID]. Dx: [DIAGNOSIS]. Rx: [PRESCRIPTION].",
"entities_detected": 5,
"processing_time_ms": 168,
"mask_mode_used": "replace",
"status": 200
}
The four entity types this guide focuses on cover the identifiers and clinical facts that appear most often in healthcare free text. The table shows what each type captures and where you will encounter it; the full taxonomy is on the entities page.
| Entity Type | Example Match | What It Captures | Typical Sources |
|---|---|---|---|
MEDICAL_RECORD_NUMBER |
MRN 0042371, Chart # SJH-88231 | Facility and EMPI patient record identifiers in any issuer format | Chart headers, referral letters, discharge summaries, billing queries |
HEALTH_INSURANCE_ID |
Member ZGP882440116, Medicare MBI 1EG4-TE5-MK73 | Payer member/subscriber IDs, Medicare MBIs, Medicaid numbers, group numbers | Claims, EOBs, prior-auth requests, eligibility checks |
DIAGNOSIS |
"type 2 diabetes", "stage II breast cancer", ICD-10 E11.9 in context | Named conditions and diagnostic statements tied to a patient | SOAP notes, problem lists, referral reasons, patient emails |
PRESCRIPTION |
"metformin 500mg BID", "Rx: lisinopril 10mg daily" | Drug names with dosing tied to a patient | Medication lists, pharmacy tickets, telehealth transcripts |
MEDICAL_DATA / TREATMENT |
Lab values, procedures, therapy plans | Broader clinical facts beyond named diagnoses and drugs | Lab reports, operative notes, care plans |
A diagnosis is not an "identifier" in the way a number is, but in combination it can be devastatingly identifying: a rare condition plus a ZIP code plus an age band can single out one person in a county. That is why de-identification standards treat clinical facts as sensitive attributes to be evaluated, and why the API lets you detect them as first-class entities. For a support-ticket pipeline you might mask DIAGNOSIS entirely; for an approved research dataset you might keep diagnoses but strip every direct identifier around them. The entities and exclude_entities parameters make either policy a one-line change.
HIPAA's Privacy Rule offers a concrete, checkable de-identification standard: the Safe Harbor method (§164.514(b)(2)) requires removing 18 categories of identifiers of the patient and of their relatives, household members, and employers. Medical record numbers are identifier #8 on that list; health plan beneficiary numbers are #9. Names, dates, contact details, SSNs, device identifiers, biometrics, and photographs fill out the rest.
Two properties of Safe Harbor matter for detection engineering. First, it is all-or-nothing: a dataset that removes 17 of the 18 categories but leaves MRNs intact is simply not de-identified, and disclosing it is a HIPAA violation. Recall — the fraction of true identifiers you actually catch — is therefore the metric that dominates. Second, the list includes a catch-all: "any other unique identifying number, characteristic, or code," which is why scanning only for the named categories is not enough and why a broad entity taxonomy helps.
A typical Safe Harbor sweep with this API requests the clinical identifiers together with the general identifier families:
MEDICAL_RECORD_NUMBER, HEALTH_INSURANCE_ID, and — depending on your risk posture — DIAGNOSIS, PRESCRIPTION, MEDICAL_DATAPERSON_NAME, DATE_OF_BIRTH, AGE, ADDRESS, ZIP_CODEEMAIL_ADDRESS, PHONE_NUMBER, SSN, FINANCIAL_ACCOUNT_NUMBERIP_ADDRESS, DEVICE_ID, URL, BIOMETRIC_DATAThe complete identifier-by-identifier mapping — all 18 categories against API entity types, plus the Safe Harbor vs Expert Determination decision — is covered in depth in our HIPAA PHI detection guide.
Compliance note: Automated detection is a tool inside a HIPAA compliance program, not a substitute for one. Safe Harbor also requires that the covered entity has no actual knowledge that residual information could identify the individual, and disclosures to vendors processing PHI require a business associate agreement. Review your workflow with your privacy officer.
Electronic health record systems generate several distinct text shapes, and each stresses a detector differently. Knowing where MRNs and their neighbors appear helps you scope the right scanning jobs.
HL7 v2 messages, FHIR bundles, and CCD documents are structured — the MRN lives in a known field (PID-3, Patient.identifier). The danger is the free-text islands inside them: OBX note segments, DocumentReference attachments, discharge narrative sections. Teams de-identify the structured fields and forget that clinicians restate the identifiers in prose ("Discussed results with pt, MRN 0042371, will follow up"). Scan every narrative field even when the structured fields are already handled.
Progress notes, SOAP notes, and discharge summaries are the richest PHI habitat: telegraphic grammar, dense abbreviation ("Pt c/o CP; hx HTN, DM2"), copy-pasted chart headers, and identifiers of other people — family members, referring physicians — that Safe Harbor also covers. The model's clinical training data covers this register; still, validate on a sample of your own notes and tune threshold before production. Our guide on measuring detection accuracy shows how to run that evaluation properly.
The least-governed PHI lives outside the EHR proper: patient-portal messages, telehealth chat transcripts, call-center notes, emails between clinics, and — increasingly — prompts sent to clinical AI assistants. These flows rarely inherit the EHR's access controls. Placing detection at these boundaries (a masking proxy in front of the ticketing system, a scan step before the LLM call) closes the gap; see PII detection in support tickets and LLM guardrails for those architectures, and the healthcare industry page for the full deployment picture.
This example de-identifies a list of clinical notes with a Safe-Harbor-oriented entity set and flags low-confidence notes for human review — the pattern most healthcare teams deploy first:
import requests SAFE_HARBOR_ENTITIES = [ "PERSON_NAME", "DATE_OF_BIRTH", "AGE", "ADDRESS", "ZIP_CODE", "PHONE_NUMBER", "EMAIL_ADDRESS", "SSN", "MEDICAL_RECORD_NUMBER", "HEALTH_INSURANCE_ID", "FINANCIAL_ACCOUNT_NUMBER", "URL", "IP_ADDRESS", "DEVICE_ID", "BIOMETRIC_DATA", ] def deidentify_note(note: str) -> dict: resp = requests.post( "https://piidetectionapi.com/api/moderate.php", json={ "api_key": "YOUR_API_KEY", "api_type": "pii_detection", "text": note, "entities": SAFE_HARBOR_ENTITIES, "mask_mode": "replace", "threshold": 0.4, # favor recall: missing PHI is worse than over-masking }, timeout=60, ) resp.raise_for_status() return resp.json() for note in notes: result = deidentify_note(note) borderline = [e for e in result["detected_entities"] if e["confidence"] < 0.6] if borderline: queue_for_human_review(note, borderline) else: store_deidentified(result["anonymized_text"])
Research datasets often need the clinical content intact. Use exclude_entities to strip direct identifiers while preserving diagnoses and medications, and hash mode so the same patient's MRN maps to a stable research token across documents:
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: clinicalNote, // detect everything EXCEPT the clinical facts the study needs exclude_entities: ["DIAGNOSIS", "PRESCRIPTION", "TREATMENT", "MEDICAL_TERM"], mask_mode: "hash", // MRN 0042371 -> same token in every note }), }); const data = await resp.json(); saveToResearchStore(data.anonymized_text);
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": "Referring pt chart 88231 to cardiology. Member ID GRP-449021, dx atrial fibrillation.", "entities": ["MEDICAL_RECORD_NUMBER", "HEALTH_INSURANCE_ID", "DIAGNOSIS", "PRESCRIPTION"], "mask_mode": "redact", "threshold": 0.4 }'
For PHI, a false positive costs a little readability; a false negative can cost a breach report. Run clinical text at a lower threshold (0.3–0.45) than you would use for marketing copy, and accept the extra masking. Measure both error types on a labeled sample before choosing — the precision/recall guide walks through the methodology.
MRNs concentrate in headers, footers, fax cover sheets, and subject lines — the parts pipelines most often skip. When you extract text from PDFs or scanned faxes for scanning, keep header and footer regions in the extraction; see document and PDF PII scanning for the OCR pipeline.
Safe Harbor covers identifiers of relatives, household members, and employers of the patient. A note that says "daughter Maria (DOB 3/2/2011) is primary contact" contains PHI about a third party. Detection with the full entity set handles this automatically because it does not care whose name or date it finds — a genuine advantage over field-level de-identification, which only knows about the patient's own fields.
Store the detection metadata — entity types, counts, confidences, processing time — alongside each processed document ID, but not the matched text itself. This gives you evidence of systematic de-identification for auditors and your Art. 30/§164.312 records without creating a new PHI store.
If policy prevents clinical text from leaving your environment even for de-identification, use the on-premise deployment of the detection engine so the entire workflow stays inside your VPC or data center. Contact us for deployment options, and see pricing for volume tiers.
Clinical documents interleave many numeric identifiers. The model disambiguates by label and position — "Acc#" before a number signals a lab accession, "MRN"/"Chart" signals a record number — but hospital-specific conventions can blur the line. In a Safe Harbor context this rarely matters operationally: encounter and accession numbers fall under the catch-all "any other unique identifying number" and should be masked anyway, so over-detection into a neighboring identifier class is usually the safe direction.
NPI numbers, DEA numbers, and physician names identify clinicians, not patients. HIPAA's de-identification standard targets the patient (and their relatives/employer), so many workflows keep provider names. If yours does, add custom_instruction: "do not mask physician and provider names or NPI numbers" rather than post-filtering.
"57F c/o SOB, hx CHF, MRN 88213-A" packs an age, gender, symptom, history, and record number into 40 characters. The model handles common clinical shorthand, but extremely local abbreviations (unit nicknames, homegrown codes) may need a custom_instruction hint or a slightly lower threshold. Test on your dirtiest notes, not your cleanest.
EHR copy-forward means the same MRN appears a dozen times in one long note, sometimes with typos introduced by manual re-entry ("0042371" vs "004237l"). Every clean instance is detected independently; genuinely corrupted instances with letter-for-digit typos may score lower — another reason recall-biased thresholds are the right default for clinical text.
Tip: The character-typo case is where mask_mode: "hash" shows its value in audits — a note where one MRN token differs from the others is a signal that a mistyped identifier is present and worth a manual look.
Because detection is driven by context rather than format. The model learns the labels ("MRN", "Chart #", "Med Rec"), document positions (chart headers, referral blocks), and co-occurring entities (patient name, DOB) that signal a record number, so it generalizes across issuers without per-facility configuration. For unusual house formats, a one-line custom_instruction closes the gap.
Masking the 18 Safe Harbor identifier categories is the core technical step of Safe Harbor de-identification, and the API automates it. Full compliance also requires the "no actual knowledge" condition and correct handling of dates and ZIP codes (years only; ZIP truncated to three digits for small areas). The HIPAA guide details the complete workflow, and your privacy officer should sign off on the process.
Yes — processing identifiable clinical text through any vendor requires a business associate agreement, and we support BAAs on healthcare plans, along with an on-premise option where PHI never leaves your infrastructure at all. Contact us to set one up.
Yes. Detection and masking are independent: the response always lists detected entities with offsets, and anonymized_text is optional to use. You can also run two passes — one broad scan for reporting, one masking pass with exclude_entities keeping DIAGNOSIS and PRESCRIPTION intact for research use.
Medicare Beneficiary Identifiers (11-character alphanumerics like 1EG4-TE5-MK73) and state Medicaid IDs are detected under HEALTH_INSURANCE_ID, alongside commercial member IDs and group numbers. MBIs have a defined structure that the validator layer checks, which keeps false positives low even in claims text dense with other codes.
Typical latency is well under half a second for message-sized text, which supports synchronous masking in chat and portal flows. For bulk historical archives, batch documents up to 50,000 characters per request. Try latency yourself in the live demo.
Yes — the model supports 60+ languages, and clinical entity detection works in the major European and Asian languages, which matters for multinational trials and cross-border care. See supported languages for the current list.
Test the detector on a sample note in the live demo, then pick a plan that fits your document volume.
Try the Live Demo View Pricing