Learn how to automatically detect, classify, and locate phone numbers in any format — North American, international, E.164, extensions, and vanity numbers — using PII Detection API's context-aware AI. Find every number without drowning in false positives.
Phone numbers are among the most frequently occurring pieces of personally identifiable information (PII) in real-world data. They show up in customer support tickets, CRM notes, call center transcripts, chat logs, contact forms, email signatures, invoices, and application logs. Unlike many identifiers, a phone number is directly actionable: anyone who obtains it can call, text, or use it as a lookup key to link records across data sets. That makes reliable phone number detection a foundational requirement for any privacy, compliance, or data loss prevention program.
The challenge is that phone numbers are not one format — they are hundreds. A number can be written as (555) 123-4567, 555.123.4567, +1 555 123 4567, 555-123-4567 ext. 22, or even 1-800-FLOWERS. International numbers introduce country codes, trunk prefixes, and national grouping conventions that vary from country to country. At the same time, plenty of digit strings that look like phone numbers are actually order IDs, invoice numbers, tracking codes, or account references. A naive regular expression either misses valid numbers or floods you with false positives — usually both.
PII Detection API solves this with context-aware AI. Instead of relying only on digit patterns, its transformer-based models read the surrounding text to determine whether a candidate string is genuinely a phone number. The API returns each detected PHONE_NUMBER entity with its exact character offsets and a confidence score, and can optionally return a masked version of the input in the same call. It is one of the 150+ entity types the API detects across 60+ languages.
Phone numbers sit squarely inside the definition of personal data in every major privacy framework, and in several of them they are called out explicitly. Knowing where phone numbers live in your systems — and being able to prove it — is a prerequisite for compliance, breach response, and safe data sharing.
Tip: A phone number is a powerful linkage key. Even in an otherwise anonymized data set, one remaining mobile number can re-identify a person by joining against marketing databases or breach dumps. Treat phone detection as a baseline control, not an optional extra.
The reason phone number detection is genuinely hard is the sheer diversity of legitimate formats. The International Telecommunication Union's E.164 standard defines a number as up to fifteen digits including a country code — but almost nobody writes numbers in pure E.164. People write numbers the way their country, keyboard habits, and mood dictate. A detection system has to recognize all of the following as the same kind of entity:
The US, Canada, and much of the Caribbean share a ten-digit plan, but the same number appears in many typographic disguises: (555) 123-4567, 555-123-4567, 555.123.4567, 555 123 4567, 5551234567, and with the country code as +1 (555) 123-4567 or 1-555-123-4567. Parentheses, dots, hyphens, and spaces are all in everyday use, and OCR or transcription noise adds further variants.
Outside the NANP, each country has its own grouping habits. A London number may be written +44 20 7946 0958 internationally but 020 7946 0958 domestically — note the leading trunk zero that disappears when the country code is used. French numbers are conventionally written in five pairs: 01 23 45 67 89. German numbers have variable-length area codes, so 030 901820 (Berlin) and 089 12345678 (Munich) have different shapes. Japanese numbers appear as +81-3-1234-5678 or 03-1234-5678. UK mobiles start with 07; in many countries the mobile prefix itself signals number type. A detector trained only on US patterns will miss most of the world's phone numbers.
Business numbers frequently carry extensions written as x1234, ext. 1234, extension 22, or #1234. The extension is part of the PII — it identifies a specific desk or person — so detection should capture it together with the base number. Vanity numbers such as 1-800-FLOWERS or 1-866-CALL-NOW replace digits with letters using the keypad mapping (2=ABC, 3=DEF, and so on); they contain few literal digits yet are fully dialable numbers. Short codes (five- or six-digit SMS numbers like 72345) are shared infrastructure rather than personal identifiers, and a good detector treats them differently from subscriber numbers.
Transcripts and user-generated content add another layer: numbers written out in words ("call me at five five five, one two three, four five six seven"), partially worded hybrids ("555 one two three four"), and deliberately obfuscated forms ("five5five-123-4567") used to evade platform filters. Because PII Detection API reads meaning rather than matching characters, it recognizes these forms that no practical regex can cover.
| Format Category | Example | Detection Notes |
|---|---|---|
| NANP standard | (555) 123-4567 | All delimiter variants (dots, dashes, spaces, none) detected |
| E.164 international | +442079460958 | Country code parsed; up to 15 digits per ITU E.164 |
| UK domestic | 020 7946 0958 | Trunk-prefix form recognized alongside +44 form |
| French paired | 01 23 45 67 89 | Five-pair national convention supported |
| German variable area code | 030 901820 | Variable-length area codes handled by context, not fixed masks |
| Japanese | +81-3-1234-5678 | Hyphenated and domestic 0-prefixed forms both detected |
| With extension | 555-123-4567 ext. 22 | Extension captured within the same entity span |
| Vanity | 1-800-FLOWERS | Keypad letter mapping recognized as a dialable number |
| Spoken / transcribed | five five five one two three four | Worded-out digits detected in transcripts |
| Obfuscated | five5five.123.4567 | Mixed word/digit evasion forms flagged by the AI model |
Note: Format coverage interacts with language coverage. A German customer email will contain German number conventions and German context words ("Rufnummer", "erreichbar unter"). PII Detection API pairs both — see the full list of supported languages.
Detecting phone numbers takes a single request. Send your text to the API endpoint with the PHONE_NUMBER entity selected, and you get back every detected number with its position, the matched text, a confidence score, and an optional masked version 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": "Call me at (312) 555-0184 or on my UK line +44 20 7946 0958.", "entities": ["PHONE_NUMBER"], "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": "Call me at (312) 555-0184 or on my UK line +44 20 7946 0958.", "entities": ["PHONE_NUMBER"], "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: "Call me at (312) 555-0184 or on my UK line +44 20 7946 0958.", entities: ["PHONE_NUMBER"], 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 contains the structured entity list plus the masked text:
{
"detected_entities": [
{"type": "PHONE_NUMBER", "text": "(312) 555-0184", "start": 11, "end": 25, "confidence": 0.99},
{"type": "PHONE_NUMBER", "text": "+44 20 7946 0958", "start": 43, "end": 59, "confidence": 0.98}
],
"anonymized_text": "Call me at [PHONE] or on my UK line [PHONE].",
"entities_detected": 2,
"processing_time_ms": 142,
"mask_mode_used": "replace",
"status": 200
}
Three fields do most of the work downstream. The start and end offsets let you highlight, mask, or extract numbers yourself with exact precision. The confidence score lets you route borderline matches to review instead of making a binary keep-or-drop decision. And anonymized_text gives you a ready-to-store masked version when detection and masking should happen in one step. You can try all of this without writing code in the interactive demo.
The hardest part of phone detection is not finding digits — it is deciding which digit strings are not phone numbers. Business text is saturated with numeric identifiers that overlap in length and shape with phone numbers: order IDs, invoice numbers, tracking codes, customer account numbers, ticket references, timestamps, and IP addresses. A pattern-only approach cannot tell them apart, which is why regex-based DLP tools are notorious for flagging every ten-digit invoice number as a phone number.
Context resolves the ambiguity. Consider the string 555-12-3456 versus 555-123-4567: the first groups its digits 3-2-4 like a US Social Security number, the second 3-3-4 like a NANP phone number. Now consider "your confirmation number is 3125550184" versus "you can reach me on 3125550184" — identical strings, different meanings, and only the surrounding words distinguish them. PII Detection API's transformer models weigh grouping, prefixes such as + and trunk zeros, and above all the semantic context: verbs like "call", "text", "reach", labels like "Tel:", "Mobile:", "Fax:", and the discourse role the number plays in the sentence.
The difference matters operationally: over-flagging destroys the utility of masked data (analysts lose order IDs they need) and erodes trust in the pipeline, while under-flagging leaks PII. For a deeper comparison of detection techniques and their error profiles, read NER vs Regex vs Rules and our guide to measuring precision and recall.
This example feeds the API a support-queue snippet containing NANP, UK, French, and extension formats in one pass. No format hints are needed — the model recognizes each national convention automatically:
import requests text = ( "US office: 555.123.4567 x22. Paris desk: 01 23 45 67 89. " "London: 020 7946 0958. Toll-free: 1-800-FLOWERS." ) resp = requests.post( "https://piidetectionapi.com/api/moderate.php", json={ "api_key": "YOUR_API_KEY", "api_type": "pii_detection", "text": text, "entities": ["PHONE_NUMBER"], "threshold": 0.6, }, timeout=30, ) for e in resp.json()["detected_entities"]: print(f"{e['text']!r} ({e['confidence']:.2f})") # '555.123.4567 x22' (0.98) # '01 23 45 67 89' (0.96) # '020 7946 0958' (0.97) # '1-800-FLOWERS' (0.93)
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": "US office: 555.123.4567 x22. Paris desk: 01 23 45 67 89. London: 020 7946 0958. Toll-free: 1-800-FLOWERS.", "entities": ["PHONE_NUMBER"], "threshold": 0.6 }'
const text = "US office: 555.123.4567 x22. Paris desk: 01 23 45 67 89. " + "London: 020 7946 0958. Toll-free: 1-800-FLOWERS."; 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, entities: ["PHONE_NUMBER"], threshold: 0.6 }) }); const { detected_entities } = await resp.json(); console.table(detected_entities);
Phone numbers rarely travel alone — they appear next to names and email addresses in signatures and contact blocks. Sweeping all three entity types in one request is the standard pattern for sanitizing tickets, emails, and CRM notes:
import requests resp = requests.post( "https://piidetectionapi.com/api/moderate.php", json={ "api_key": "YOUR_API_KEY", "api_type": "pii_detection", "text": "Contact John Doe at [email protected] or 555-123-4567.", "entities": ["PERSON_NAME", "EMAIL_ADDRESS", "PHONE_NUMBER"], "mask_mode": "replace", }, timeout=30, ) data = resp.json() print(data["anonymized_text"]) # Contact [NAME] at [EMAIL] or [PHONE].
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": "Contact John Doe at [email protected] or 555-123-4567.", "entities": ["PERSON_NAME", "EMAIL_ADDRESS", "PHONE_NUMBER"], "mask_mode": "replace" }'
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: "Contact John Doe at [email protected] or 555-123-4567.", entities: ["PERSON_NAME", "EMAIL_ADDRESS", "PHONE_NUMBER"], mask_mode: "replace" }) }); const data = await resp.json(); console.log(data.anonymized_text); // Contact [NAME] at [EMAIL] or [PHONE].
Company hotlines and public support numbers are not personal data, and masking them makes documents confusing. The custom_instruction field accepts plain-language exclusions so your published numbers pass through untouched while customer numbers are still caught:
import requests resp = requests.post( "https://piidetectionapi.com/api/moderate.php", json={ "api_key": "YOUR_API_KEY", "api_type": "pii_detection", "text": "Our hotline is 1-800-555-0199. The customer's cell is (312) 555-0184.", "entities": ["PHONE_NUMBER"], "mask_mode": "replace", "custom_instruction": "Do not flag our public support hotline 1-800-555-0199.", }, timeout=30, ) print(resp.json()["anonymized_text"]) # Our hotline is 1-800-555-0199. The customer's cell is [PHONE].
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": "Our hotline is 1-800-555-0199. The customer'\''s cell is (312) 555-0184.", "entities": ["PHONE_NUMBER"], "mask_mode": "replace", "custom_instruction": "Do not flag our public support hotline 1-800-555-0199." }'
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: "Our hotline is 1-800-555-0199. The customer's cell is (312) 555-0184.", entities: ["PHONE_NUMBER"], mask_mode: "replace", custom_instruction: "Do not flag our public support hotline 1-800-555-0199." }) }); console.log((await resp.json()).anonymized_text);
Detection tells you where a phone number is; normalization tells you which phone number it is. E.164 is the ITU standard for globally unambiguous numbers: a leading +, the country code, and the national number, with no spaces or punctuation and at most fifteen digits — for example +13125550184 or +442079460958. It is the canonical form used by telephony APIs, SMS gateways, and CRM deduplication.
A common production pattern is to combine the two steps: use PII Detection API to find each number's exact span and text, then normalize the extracted string to E.164 in your own code (libraries such as libphonenumber handle the country-specific rules, including dropping trunk zeros when a country code is added). The API's precise start/end offsets make the extraction step trivial, and the confidence score tells you which candidates are safe to normalize automatically.
Normalization is also where detection quality pays off. If your detector returns an order ID as a "phone number", normalization produces a garbage E.164 string that pollutes downstream systems — another reason context-aware detection beats pattern matching. Conversely, when the detector correctly captures an extension ("555-123-4567 ext. 22"), you can split the base number for E.164 while preserving the extension separately, rather than losing it or corrupting the number.
Tip: If you only need masked output, skip normalization entirely — mask_mode ("replace", "redact", or "hash") gives you a sanitized string in the same API call. Use "hash" when you need to count or join on distinct numbers without storing them: the same number always produces the same hash within your account.
Out of the box, PII Detection API applies a confidence threshold of 0.5, which balances precision and recall for general text. Two knobs let you adapt this to your corpus.
The threshold parameter (0–1) sets the minimum confidence for a match to be returned. Raise it toward 0.8 when your text is dense with non-phone digit strings (e-commerce logs, financial exports) and false positives are costly. Lower it toward 0.3 for compliance-critical masking of transcripts, where a missed number is worse than an occasional over-mask. Because every entity carries its own confidence score, you can also run at a low threshold and implement a two-tier policy in your code: auto-mask above 0.85, queue 0.5–0.85 for human review.
The custom_instruction field (up to 500 characters) accepts natural-language guidance: exclude your published hotlines, ignore numbers inside code samples, or treat five-digit strings in a given column as store IDs. This replaces the brittle allow-list regexes that traditional DLP tools require, and it travels with the request, so different pipelines can apply different policies against the same API key. Pricing is per request regardless of options — see pricing for volume tiers.
Structured phone columns are the easy part. The numbers that cause incidents live in comment fields, ticket bodies, transcripts, and log payloads. Route every free-text field through detection before storage or export.
Request PERSON_NAME, EMAIL_ADDRESS, and ADDRESS together with PHONE_NUMBER. A masked phone number next to an unmasked name and email is only marginal progress; the cluster is what identifies a person.
Store the entity metadata (type, offsets, confidence) alongside masked documents. When an auditor or a data subject asks what was found and removed, you can answer precisely — and you can re-mask historical data if your policy tightens.
Every business has house styles: "Cust ph 3125550184", numbers pasted with copy artifacts, or region-specific conventions from your markets. Before production, run a sample through the live demo or the API and review the hits and misses, then adjust threshold and custom_instruction accordingly.
Typical processing time is well under 200 ms per request, which fits synchronous chat and form-submission paths. For bulk backfills, batch texts up to the 50,000-character request limit and parallelize requests rather than concatenating unrelated documents.
Note: Masking numbers in new data does not clean up numbers already sitting in old logs and backups. Pair real-time detection with periodic scans of data at rest — see scanning application logs for PII.
Detection covers the numbering plans of 200+ countries and territories, including NANP (US/Canada), all European national conventions, and Asia-Pacific formats, in both international (+country code) and domestic (trunk-prefixed) forms. Because the models also read context in 60+ languages, a Japanese number in Japanese text is detected just as reliably as a US number in English text.
Yes. Extensions written as "x1234", "ext. 1234", "extension 22", or "#1234" are included in the detected entity span, so masking removes the full dialable identity, not just the base number. The extension is part of what identifies a person, so treating it as PII is the safe default.
Yes. Vanity numbers that substitute keypad letters for digits are recognized as phone numbers even though they contain few literal digits. Note that toll-free and published business numbers are often not personal data; use custom_instruction to exclude your own public numbers from masking.
The models classify by context, not just pattern: labels ("Order #", "Tel:"), surrounding verbs ("call me at" versus "your invoice"), and digit grouping all feed the decision. This is the main advantage over regex-based tools, which cannot distinguish a ten-digit order ID from a ten-digit phone number. If a specific ID scheme in your data still collides, a one-line custom instruction resolves it.
Yes. Numbers spoken aloud and transcribed as words ("five five five, one two three four"), hybrid word/digit forms, and transcription artifacts (extra spaces, repeated digits from disfluencies) are handled by the AI model. This makes the API suitable for call center recordings, voicemail transcription, and meeting notes.
The API returns each number exactly as it appears in your text, with precise character offsets, so masking and highlighting stay faithful to the original. For canonicalization, extract the matched text using the offsets and normalize it to E.164 in your code with a library such as libphonenumber — the detection step guarantees you are only normalizing genuine phone numbers.
Test the API on your own text in the live demo, or get an API key and integrate in minutes.
Try the Live Demo View Pricing