Learn how to automatically find, classify, and locate street addresses, cities, postal codes, and GPS coordinates in unstructured text using PII Detection API. Get precise character offsets and confidence scores for every location entity — across 60+ languages and international address formats.
Physical addresses are among the most consequential categories of personally identifiable information you can find in text. A street address does something few other identifiers do: it ties a data subject to a real, physical place — a home, a workplace, a hospital, a school. Where an email address exposes someone's inbox, a home address exposes their front door. That is why every major privacy framework treats location data as personal data, and why address detection is a core requirement in data loss prevention, compliance scanning, and safe data sharing.
The problem is that addresses are extraordinarily messy. They hide inside support tickets ("my package was left at 742 Evergreen Terrace instead of my flat"), delivery notes, CRM records, chat transcripts, complaint emails, property listings, and application logs. They come in dozens of national formats, with the house number sometimes first and sometimes last, with postal codes that are five digits in one country and alphanumeric in another, and with abbreviations ("St.", "Ave", "Blvd", "Hwy") that overlap with ordinary words. A regex for "number + street name" will drown you in false positives and still miss half of the real addresses.
PII Detection API solves this with context-aware transformer models rather than pattern matching alone. The API reads the surrounding text, recognizes that "10 Downing Street" in a sentence about deliveries is a location while "Wall Street analysts" is not, and returns each detected entity with its type, matched text, character offsets, and a confidence score. You decide what happens next: flag it, route it for review, or receive a masked copy of the text in the same response using the optional mask_mode parameter.
This guide walks through the address-related entity types, international format coverage, the surprisingly high re-identification risk of partial addresses, GPS coordinate handling, and production-ready code in cURL, Python, and JavaScript against the PII Detection API.
Address detection is rarely optional. If your systems accept free text from customers, employees, or partners, addresses are almost certainly accumulating in places they should not be — and regulators, auditors, and attackers all know it.
Beyond compliance, addresses create concrete safety risk. A leaked home address enables stalking, doxxing, and physical harm in a way no other common identifier does. Trust-and-safety teams at marketplaces, gaming platforms, and social products routinely scan user-generated content for addresses precisely because users post them — their own and other people's — in reviews, profiles, and chats. Detection also matters upstream of AI: if support transcripts containing customer addresses flow into an LLM prompt or a training corpus, the model can memorize and regurgitate them. Scanning text before it reaches the model is now standard practice for LLM guardrails and RAG pipelines.
Detection first, redaction second. Unlike pure redaction tools, PII Detection API tells you exactly what it found and where — so you can build audit trails, risk dashboards, and data maps, not just blacked-out text. Masking remains one API parameter away when you need it.
PII Detection API models location data at two levels of granularity. You can detect the full address as a single span, or detect its components individually. Which you choose depends on your use case: compliance masking usually wants the whole address gone, while data-mapping and analytics teams often want to know that a document contains a city and ZIP but no street-level detail.
| Entity Type | What It Matches | Example | Re-identification Risk |
|---|---|---|---|
ADDRESS |
Street-level addresses including house/building numbers, street names, units, and PO boxes | 742 Evergreen Terrace, Apt 4B | Very high — identifies a household directly |
CITY |
City, town, and municipality names in context | Springfield | Low alone; high in combination |
STATE |
States, provinces, regions, and their abbreviations | CA, Bavaria, Ontario | Low alone |
ZIP_CODE |
Postal codes in national formats (ZIP, ZIP+4, UK postcodes, etc.) | 94043, SW1A 2AA | High when combined with other quasi-identifiers |
COUNTRY |
Country names and common abbreviations | Germany, USA | Minimal alone |
GPS_COORDINATES |
Latitude/longitude pairs in decimal or degree-minute-second notation | 37.4220, -122.0841 | Very high — can resolve to a single building |
All six types are part of the 150+ entity catalog documented on our entities page. Pass any subset in the entities array of your request; omit the parameter to detect everything.
Granularity matters for HIPAA. Safe Harbor treats city, county, and full ZIP as identifiers that must be removed, while state may remain. If you are de-identifying health data, request ADDRESS, CITY, and ZIP_CODE but deliberately leave STATE out of your entity list — the detection results will then align one-to-one with what the rule requires you to strip.
Address detection is a single POST request. Send your text and the location entity types you care about; the API returns every detected entity with offsets and confidence, plus a masked copy of the input. Try it interactively in the live demo before writing any code.
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": "Courier note: leave parcel at 221B Baker Street, London NW1 6XE if nobody answers.", "entities": ["ADDRESS", "CITY", "STATE", "ZIP_CODE", "COUNTRY", "GPS_COORDINATES"], "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": "Courier note: leave parcel at 221B Baker Street, London NW1 6XE if nobody answers.", "entities": ["ADDRESS", "CITY", "STATE", "ZIP_CODE", "COUNTRY", "GPS_COORDINATES"], "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: "Courier note: leave parcel at 221B Baker Street, London NW1 6XE if nobody answers.", entities: ["ADDRESS", "CITY", "STATE", "ZIP_CODE", "COUNTRY", "GPS_COORDINATES"], 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 each location component separately, with character offsets you can use for highlighting, structured extraction, or your own replacement logic:
{
"detected_entities": [
{"type": "ADDRESS", "text": "221B Baker Street", "start": 30, "end": 47, "confidence": 0.97},
{"type": "CITY", "text": "London", "start": 49, "end": 55, "confidence": 0.96},
{"type": "ZIP_CODE", "text": "NW1 6XE", "start": 56, "end": 63, "confidence": 0.94}
],
"anonymized_text": "Courier note: leave parcel at [ADDRESS], [CITY] [ZIP_CODE] if nobody answers.",
"entities_detected": 3,
"processing_time_ms": 142,
"mask_mode_used": "replace",
"status": 200
}
A single request accepts up to 50,000 characters, so full documents, ticket threads, and transcript chunks fit comfortably. Response times are typically well under a quarter of a second — see pricing for volume tiers and rate limits.
There is no such thing as a universal address format, and this is where rule-based systems collapse. The elements of an address — building number, street, locality, administrative region, postal code — appear in different orders, with different separators, in different scripts, depending on the country. A pattern tuned for "123 Main St, Springfield, IL 62704" will silently miss "Musterstraße 12, 10115 Berlin" because Germany puts the house number after the street and the postal code before the city.
| Country | Typical Order | Postal Code Pattern | Example |
|---|---|---|---|
| United States | Number → Street → City → State → ZIP | 5 digits, optional +4 (94043, 94043-1351) |
1600 Amphitheatre Pkwy, Mountain View, CA 94043 |
| United Kingdom | Number → Street → Town → Postcode | Alphanumeric outward + inward code (SW1A 2AA) |
10 Downing Street, London SW1A 2AA |
| Germany | Street → Number → Postal code → City | 5 digits (10115) |
Musterstraße 12, 10115 Berlin |
| France | Number → Street → Postal code → City | 5 digits, first two = département (75008) |
55 Rue du Faubourg Saint-Honoré, 75008 Paris |
| Japan | Postal code → Prefecture → City → District → Block → Building (largest to smallest) | 3+4 digits (〒100-8111) |
〒100-8111 東京都千代田区千代田1-1 |
| Brazil | Street → Number → Neighborhood → City → State → CEP | 5+3 digits (01310-100) |
Av. Paulista, 1578 – Bela Vista, São Paulo – SP, 01310-100 |
| India | Number → Street → Locality → City → State → PIN | 6 digits (110001) |
24 Janpath Road, Connaught Place, New Delhi, Delhi 110001 |
| Netherlands | Street → Number → Postal code → City | 4 digits + 2 letters (1012 AB) |
Damrak 1, 1012 LG Amsterdam |
Three consequences follow from this diversity. First, postal codes collide across countries: a five-digit German PLZ is indistinguishable from a US ZIP by shape alone, and a rule that treats every five-digit number as a ZIP code will flag order IDs and quantities. The model resolves this from context — surrounding street names, city names, language, and phrasing. Second, ordering cannot be assumed: Japanese addresses run from largest unit to smallest, the reverse of Western convention, and are frequently written in kanji. Third, abbreviations are locale-specific: "str." in German, "Av." in Portuguese and Spanish, "Rue" in French. PII Detection API is trained on address data across 60+ languages (see the full list of supported languages), so a single API call handles multinational content — a critical property for global support desks and marketplaces where one ticket thread may contain a UK shipping address and a Brazilian billing address.
Tip: If your traffic is dominated by one country, you can still leave detection global. The model does not trade accuracy across locales the way per-country regex libraries do — there is no configuration to maintain as your user base expands.
A common and dangerous assumption is that an address only matters when it is complete. In reality, fragments of location data are quasi-identifiers whose combination is often enough to single out an individual. The landmark demonstration came from Latanya Sweeney's research at Carnegie Mellon: 87% of the US population can be uniquely identified by just three attributes — 5-digit ZIP code, date of birth, and gender. None of those three is a "direct" identifier on its own. Together they point at one person.
This is why serious PII programs detect address components, not just full addresses. Consider the fragments that routinely survive naive redaction:
With PII Detection API you control the granularity explicitly. To audit a corpus for component-level leakage, request only the components:
import requests # Component-level audit: where do city/state/ZIP fragments appear? resp = requests.post( "https://piidetectionapi.com/api/moderate.php", json={ "api_key": "YOUR_API_KEY", "api_type": "pii_detection", "text": "Patient is a 42-year-old from Springfield, 62704, referred by the downtown clinic.", "entities": ["CITY", "STATE", "ZIP_CODE"], "threshold": 0.4, # cast a wider net for audit purposes }, timeout=30, ) for e in resp.json()["detected_entities"]: print(f"{e['type']:<10} {e['text']!r:<20} conf={e['confidence']:.2f}")
Note the lowered threshold: for audits and data-discovery sweeps, recall usually matters more than precision, so accepting lower-confidence candidates and reviewing them is the right trade. For automated masking in production, keep the threshold at the default 0.5 or higher.
Quasi-identifiers compound. A document that leaks only a ZIP code may be fine in isolation — but if your pipeline also stores dates of birth (see our guide on detecting dates of birth), the combination crosses the re-identification threshold. Evaluate location fragments against everything else the record contains, not in isolation.
GPS coordinates deserve their own section because they are the most precise location identifier that appears in text — and they appear far more often than teams expect. Mobile apps embed latitude/longitude in analytics events and crash reports. Photos carry EXIF coordinates that end up pasted into tickets. Fleet, delivery, and field-service systems log positions continuously. Fitness apps famously leaked the locations of military bases through published activity heatmaps.
The precision arithmetic is stark. A coordinate pair with four decimal places (e.g. 37.4220, -122.0841) is accurate to roughly 11 meters — a specific building. Five decimals is about one meter. Even truncated three-decimal coordinates land within ~110 meters, a city block. Under CPRA, "precise geolocation" (a radius of 1,850 feet or less) is sensitive personal information; a four-decimal coordinate is orders of magnitude more precise than that line.
Geocoding risk runs in both directions. Forward geocoding turns a detected street address into coordinates; reverse geocoding turns leaked coordinates back into a street address — one free API call away for anyone who obtains your logs. This means coordinates and addresses are interchangeable from an attacker's perspective, and a redaction policy that strips one but not the other protects nothing. The GPS_COORDINATES entity type catches decimal notation, degree-minute-second notation ("37°25'19.2"N 122°05'02.8"W"), and labeled forms ("lat: 37.4220, lon: -122.0841").
// Scan an application log line for coordinates and addresses before storage const logLine = "2026-08-25T14:02:11Z user=8841 checkin lat=51.503396 lon=-0.127640 note='meet at 10 Downing St'"; 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: logLine, entities: ["GPS_COORDINATES", "ADDRESS"], mask_mode: "redact", // remove matches entirely from anonymized_text }), }); const { anonymized_text, detected_entities } = await resp.json(); if (detected_entities.length > 0) { logger.warn(`Location PII found in log line (${detected_entities.length} entities)`); } store.write(anonymized_text); // only the clean version is persisted
For log pipelines, mask_mode: "redact" (removal) or "hash" (consistent hashing, which preserves the ability to correlate repeated locations without revealing them) are usually better fits than placeholder replacement. Hashing is particularly useful for analytics: the same coordinates always produce the same hash, so "how many events came from the same place" remains answerable.
Not every address is personal. Your own office address in an email signature, store locations in a retail FAQ, or public landmarks usually should not be flagged. The custom_instruction parameter accepts a natural-language exclusion rule (up to 500 characters) so you can express policy without post-processing:
resp = requests.post( "https://piidetectionapi.com/api/moderate.php", json={ "api_key": "YOUR_API_KEY", "api_type": "pii_detection", "text": ticket_text, "entities": ["ADDRESS", "CITY", "ZIP_CODE", "GPS_COORDINATES"], "custom_instruction": "Do not flag our company offices at 500 Commerce Way, Austin, or any address that is clearly a business headquarters rather than a person's address.", "mask_mode": "replace", }, timeout=30, )
When you need to group or count records by location without storing the location, use mask_mode: "hash". Identical addresses map to identical hashes, so joins and aggregations still work on the masked output:
rows = [
"Delivery failed at 742 Evergreen Terrace, Springfield",
"Second attempt at 742 Evergreen Terrace, Springfield",
"Delivered to 1600 Amphitheatre Pkwy, Mountain View",
]
for row in rows:
resp = requests.post(
"https://piidetectionapi.com/api/moderate.php",
json={
"api_key": "YOUR_API_KEY",
"api_type": "pii_detection",
"text": row,
"entities": ["ADDRESS", "CITY"],
"mask_mode": "hash", # same address -> same hash, every time
},
timeout=30,
)
print(resp.json()["anonymized_text"])
# The two Evergreen Terrace rows share a hash and remain groupable
const scan = async (text) => { const r = 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, entities: ["ADDRESS", "CITY"], mask_mode: "hash", }), }); return (await r.json()).anonymized_text; };
Because every entity carries start and end offsets, building a human-review interface is straightforward — wrap each span in a highlight element, working backwards so earlier offsets stay valid:
function highlight(text, entities) { const sorted = [...entities].sort((a, b) => b.start - a.start); let html = text; for (const e of sorted) { html = html.slice(0, e.start) + `<mark title="${e.type} (${e.confidence})">` + html.slice(e.start, e.end) + "</mark>" + html.slice(e.end); } return html; }
Full parameter documentation, error codes, and additional language examples live in the API documentation.
Requesting ADDRESS alone leaves cities, postal codes, and coordinates in your text. Because these components substitute for one another in re-identification attacks, treat them as a unit: request all six location types unless you have a documented reason to narrow the list (such as the HIPAA state exemption discussed above).
An address is most dangerous next to a name or contact detail. Production configurations typically scan for the location family plus PERSON_NAME, PHONE_NUMBER, and EMAIL_ADDRESS in one call — the API prices per request, not per entity type, so wider coverage costs nothing extra. See the companion guides on detecting names and detecting phone numbers.
Use a lower threshold (0.3–0.45) for discovery scans and audits where a human reviews the output, and the default or higher (0.5–0.7) for unattended masking where a false positive mangles legitimate text. Log the confidence scores you receive: their distribution over your real traffic tells you exactly where to set the line.
The cheapest place to catch an address is at the boundary — before the ticket is written to the database, before the log line is shipped, before the prompt reaches the LLM. Retrofitting detection onto years of accumulated data is possible (and the API's 50,000-character requests make batch sweeps practical), but inline scanning keeps the problem from growing.
Because the API returns structured detections rather than only masked text, you can retain an audit record — entity type, offsets, confidence, timestamp — without retaining the sensitive value itself. That record is what a GDPR Article 30 data map or a HIPAA de-identification determination actually needs.
"Washington", "Lincoln", and "Victoria" are simultaneously surnames, cities, and street names. Context resolves the ambiguity: in "Victoria said she'd meet us", Victoria is a person; in "14 Victoria Road", it is part of an address. The model classifies by usage, not by dictionary membership — one reason context-aware detection decisively outperforms gazetteer lookups here.
"PO Box 1042", "Apt 4B", "Unit 12, Building C", and "c/o Mrs. Hale" are all address fragments that identify a delivery point. The ADDRESS type covers these forms, including military (APO/FPO) and university mail-stop conventions.
Chat users routinely send an address across multiple messages ("it's 742 Evergreen Terrace" / "Springfield" / "62704"). Scan the joined conversation window rather than individual messages when possible; each fragment is still detectable alone (as ADDRESS, CITY, and ZIP_CODE respectively), but joining the window gives the model full context and yields higher confidence scores.
"221B Baker Street" is fictional; "1600 Pennsylvania Avenue" is public. By default the API flags them — it cannot know your policy. If public landmarks should pass through, say so in custom_instruction ("ignore famous public landmarks and government buildings") rather than raising the threshold, which would also suppress genuine low-confidence detections.
Numbers that look like postal codes. Order numbers, ticket IDs, and prices share shapes with postal codes. The model uses context to separate "your order #62704 has shipped" from "Springfield, IL 62704", but if your domain is dense with five- or six-digit codes, run a sample through the demo and consider a custom_instruction naming your ID formats.
Yes. Address detection works across 60+ languages and scripts, including Japanese, Chinese, Korean, Arabic, Cyrillic, and Devanagari. Japanese addresses written largest-to-smallest in kanji, Arabic addresses written right-to-left, and transliterated forms are all supported without configuration.
Yes — each component is its own entity type (CITY, STATE, ZIP_CODE, COUNTRY), so fragments are detected even when no street-level address is present. This matters because partial location data is a potent quasi-identifier: ZIP plus date of birth plus gender uniquely identifies 87% of the US population.
Use the custom_instruction parameter with a natural-language rule such as "do not flag addresses of our retail stores or headquarters". This is more precise than raising the threshold and keeps genuine customer addresses fully covered.
replace substitutes typed placeholders like [ADDRESS], preserving readability. redact removes the match entirely, which is safest for logs. hash replaces each address with a consistent hash, so identical addresses remain correlatable for analytics without being readable.
Yes. GPS_COORDINATES matches decimal degrees, degree-minute-second notation, and labeled key-value forms like lat=51.5033 lon=-0.1276 that appear in logs, telemetry events, and EXIF metadata dumps pasted into text.
The API detects all geographic identifiers Safe Harbor requires you to remove (street address, city, county-level references, full ZIP codes) while letting you exempt states. As with any automated method, your compliance program should validate performance on a sample of your own data and document the results; our accuracy guide explains how to measure precision and recall properly.
Each request accepts up to 50,000 characters. Volume pricing, free-tier limits, and on-premise options are listed on the pricing page, and you can benchmark accuracy on your own samples for free in the interactive demo.
Scan your first document in minutes — get precise, confidence-scored location detections across 60+ languages.
Try the Live Demo View Pricing