Learn how to automatically find IPv4 and IPv6 addresses, MAC addresses, device IDs, IMEI numbers, cookies, and user-agent strings in text, logs, and telemetry using PII Detection API — and understand when network identifiers count as personal data under GDPR.
IP addresses and device identifiers are among the most pervasive forms of personally identifiable information in modern systems — and among the most frequently overlooked. Unlike a name or an email address, an IP address rarely looks "personal" to the engineer scrolling past it in an application log. Yet regulators in Europe and several US states have repeatedly confirmed that network and device identifiers can single out an individual, which places them squarely inside the scope of GDPR, CCPA/CPRA, and the ePrivacy rules that govern cookies and tracking technologies.
The problem is scale. A single production web server can write hundreds of thousands of client IPs to its access logs every day. Mobile backends collect device IDs and IMEI numbers with every crash report. Support agents paste router configurations containing MAC addresses into tickets. Analytics pipelines ingest raw user-agent strings that fingerprint browsers with surprising precision. Finding all of these identifiers by hand — or with brittle homegrown regex — is not realistic once data volume grows.
PII Detection API solves this with a single endpoint that scans free text for 150+ entity types, including the full family of network and device identifiers: IP_ADDRESS, MAC_ADDRESS, DEVICE_ID, IMEI, COOKIE, and USER_AGENT. The API returns each match with its exact character offsets and a confidence score, and can optionally hand back a masked version of the input in the same call, so you can log, store, or forward the sanitized text immediately.
The instinct to treat IP addresses as harmless infrastructure data is understandable — they identify machines, not people. But in practice a network identifier almost always maps back to a person or a small household, and both courts and regulators have caught up with that reality. Detecting these identifiers is therefore not an academic exercise; it is a compliance requirement for most organizations that operate at scale.
Network and device identifiers accumulate in places that traditional data-inventory exercises rarely reach:
ipconfig/ifconfig output, router pages, and traceroutes containing IPs and MAC addresses.Tip: A user-agent string alone is rarely identifying, but combined with an IP address and a timestamp it often is. Detection policies should treat these identifiers as a family, not as isolated types — which is why the API lets you request all six in a single call.
The example below scans a log excerpt for the complete set of network and device identifiers. You need an API key — get one free from the get started page or explore the interactive demo first, then send a single POST request:
import requests resp = requests.post( "https://piidetectionapi.com/api/moderate.php", json={ "api_key": "YOUR_API_KEY", "api_type": "pii_detection", "text": "Login from 203.0.113.45 (fe80::1ff:fe23:4567:890a), " "MAC 00:1B:44:11:3A:B7, IMEI 490154203237518.", "entities": ["IP_ADDRESS", "MAC_ADDRESS", "DEVICE_ID", "IMEI", "COOKIE", "USER_AGENT"], "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: "Login from 203.0.113.45 (fe80::1ff:fe23:4567:890a), MAC 00:1B:44:11:3A:B7, IMEI 490154203237518.", entities: ["IP_ADDRESS", "MAC_ADDRESS", "DEVICE_ID", "IMEI", "COOKIE", "USER_AGENT"], 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) );
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": "Login from 203.0.113.45 (fe80::1ff:fe23:4567:890a), MAC 00:1B:44:11:3A:B7, IMEI 490154203237518.", "entities": ["IP_ADDRESS", "MAC_ADDRESS", "DEVICE_ID", "IMEI", "COOKIE", "USER_AGENT"], "mask_mode": "replace" }'
The response lists every match with its type, exact position, and confidence, plus the masked text:
{
"detected_entities": [
{"type": "IP_ADDRESS", "text": "203.0.113.45", "start": 11, "end": 23, "confidence": 0.99},
{"type": "IP_ADDRESS", "text": "fe80::1ff:fe23:4567:890a", "start": 25, "end": 49, "confidence": 0.98},
{"type": "MAC_ADDRESS", "text": "00:1B:44:11:3A:B7", "start": 56, "end": 73, "confidence": 0.99},
{"type": "IMEI", "text": "490154203237518", "start": 80, "end": 95, "confidence": 0.97}
],
"anonymized_text": "Login from [IP_ADDRESS] ([IP_ADDRESS]), MAC [MAC_ADDRESS], IMEI [IMEI].",
"entities_detected": 4,
"processing_time_ms": 142,
"mask_mode_used": "replace",
"status": 200
}
Both address families are returned under the single IP_ADDRESS entity type, but they present very different detection challenges, and it is worth understanding what the model handles for you.
An IPv4 address is four decimal octets separated by dots — 203.0.113.45 — which sounds trivial to match. In real text, however, naive patterns collapse quickly. Version strings like 2.4.1.100, section numbers, decimal-separated timestamps, and SNMP OIDs all look like dotted quads. A pure regex approach either misses valid addresses or floods you with false positives. PII Detection API validates each octet range (0–255) and, more importantly, weighs the surrounding context: a dotted quad following "connected from" or inside an nginx log line scores high; the same pattern inside "upgraded to release 2.4.1.100" scores low and is suppressed by the default 0.5 threshold.
IPv6 addresses are eight groups of hexadecimal digits with aggressive abbreviation rules: leading zeros drop, one run of zero groups can collapse to ::, IPv4-mapped forms embed a dotted quad (::ffff:192.0.2.128), and link-local addresses may carry a zone index (fe80::1%eth0). The API's detector normalizes all of these forms, including bracketed addresses with ports ([2001:db8::1]:443) as they appear in URLs and log lines. Because IPv6 addresses are frequently unique per device — and privacy extensions notwithstanding, often stable per session — they are at least as identifying as IPv4 and should never be excluded from a scanning policy.
Log data rarely contains bare addresses. You will see 203.0.113.45:52114 (address plus ephemeral port), 10.0.0.0/8 (CIDR blocks in firewall rules), and ranges in blocklists. The detector isolates the address component so offsets point at exactly the identifying substring, which matters when you redact: masking the port or the prefix length would corrupt otherwise useful network documentation.
Note on private ranges: Addresses in RFC 1918 space (10.x, 172.16–31.x, 192.168.x) and IPv6 unique-local space identify machines inside your own network rather than external individuals. They are still detected — internal IPs can identify employees — but if your policy deliberately excludes them, use custom_instruction (for example, "do not flag private RFC 1918 IP ranges") to suppress them without writing any post-processing code.
IPs are only one branch of the device-identifier family. The API detects five further types that, individually or in combination, can single out a device and therefore its user. The table below summarizes each entity type, its typical shape, and where it usually appears.
| Entity Type | Example | What It Identifies | Common Sources |
|---|---|---|---|
IP_ADDRESS |
203.0.113.45 2001:db8::8a2e:370:7334 |
Network endpoint; maps to a subscriber via the ISP | Web/app logs, firewalls, email headers, VPN records |
MAC_ADDRESS |
00:1B:44:11:3A:B7 | Physical network interface; globally unique per NIC | DHCP logs, Wi-Fi analytics, router configs, support tickets |
DEVICE_ID |
IDFA/AAID GUIDs, e.g. 6D92078A-8246-4BA4-AE85-1BC39E6EAAD7 | A specific phone, tablet, or installation | Mobile SDK telemetry, ad-tech payloads, crash reports |
IMEI |
490154203237518 | Cellular handset hardware; survives factory resets | Carrier records, MDM inventories, theft reports, repair tickets |
COOKIE |
session_id=a3fWx91b2c… | A browser profile across visits | HTTP request dumps, analytics exports, HAR files |
USER_AGENT |
Mozilla/5.0 (iPhone; CPU iPhone OS 17_4)… | Browser/OS fingerprint; identifying in combination | Access logs, bug reports, bot-detection systems |
A MAC address is burned into the network interface at manufacture, making it one of the most persistent identifiers that exists — it survives reinstalls, IP changes, and network moves. The detector recognizes colon-, hyphen-, and dot-separated notations (00:1B:44:11:3A:B7, 00-1B-44-11-3A-B7, Cisco-style 001B.4411.3AB7) and uses context to separate MACs from other hex strings such as commit hashes or UUID fragments.
The 15-digit IMEI uniquely identifies a cellular handset and is validated with the Luhn check digit, which eliminates most random 15-digit false positives. IMEIs turn up in places engineers forget: MDM exports, insurance claims, "find my phone" support conversations, and carrier API responses. Because an IMEI persists across SIM swaps and factory resets, regulators treat it as a strong personal identifier — HIPAA lists device identifiers and serial numbers among its 18 Safe Harbor identifiers.
Cookie identifiers are pseudonymous by design, but that is precisely why they are regulated: their entire purpose is to recognize the same person on a return visit. The detector flags high-entropy identifier values in cookie syntax (name=value pairs in Cookie/Set-Cookie headers or query dumps). User-agent strings are detected as complete units so a single mask replaces the whole fingerprint. When your analytics genuinely need coarse browser statistics, mask user agents with mask_mode: "hash" instead of removal — identical agents hash to identical tokens, so aggregate counts survive while the raw fingerprint disappears.
This question generates more engineering-legal debate than almost any other in privacy, so it deserves a precise answer. Under GDPR, data is personal when it relates to an identified or identifiable natural person, and identifiability is judged by "all the means reasonably likely to be used" by the controller or by another person (Recital 26).
The engineering consequence is straightforward: if your logs, tickets, or exports contain client IPs, you are processing personal data, and every GDPR duty attaches — lawful basis, minimization, retention limits, breach notification, and data subject access. Detection is the mechanism that makes those duties operational: you cannot minimize, delete, or disclose what you have not found. Our GDPR PII detection guide covers the full compliance workflow, and the DLP guide shows how to enforce policies automatically at egress points.
Practical pattern: keep raw IPs only in a short-retention hot store for security operations (a legitimate-interest purpose recognized by Recital 49), and run every longer-lived copy — analytics exports, ticket archives, training datasets — through the detection API with mask_mode: "hash". Security teams keep correlation ability; the archive stops accumulating raw identifiers.
Log files are the highest-volume habitat of network identifiers, and they have properties that make detection both easier and harder than in prose. Easier, because log formats are semi-structured and the model's context signals are strong. Harder, because volume is enormous and log lines mix identifying values with operationally similar-looking noise.
entities to the six network/device types is faster and eliminates irrelevant matches in log noise.replace for human-readable sanitized logs, hash when downstream systems still need to group events by client, redact for exports leaving your security boundary.This "sanitize-at-ingest" architecture — placed in a Logstash filter, a Fluent Bit output plugin, a Kafka stream processor, or a CloudWatch subscription Lambda — means the identifiers never reach long-term storage at all, which is dramatically stronger than retroactive cleanup. The log file PII scanning guide and the ETL and streaming pipeline guide walk through complete deployments for the major stacks.
replace — placeholders like [IP_ADDRESS] keep lines readable and make it obvious what was removed.hash — the same client always produces the same token, so counting, joining, and alerting still work without storing the address.redact — remove the values entirely; placeholders can themselves leak structure you do not want to publish.This script reads a raw access log in chunks, masks all network and device identifiers with consistent hashes, and writes the sanitized log for long-term retention:
import requests API_URL = "https://piidetectionapi.com/api/moderate.php" NETWORK_ENTITIES = ["IP_ADDRESS", "MAC_ADDRESS", "DEVICE_ID", "IMEI", "COOKIE", "USER_AGENT"] CHUNK_CHARS = 45000 # stay under the 50k request limit def sanitize_chunk(chunk: str) -> str: resp = requests.post(API_URL, json={ "api_key": "YOUR_API_KEY", "api_type": "pii_detection", "text": chunk, "entities": NETWORK_ENTITIES, "mask_mode": "hash", # same IP -> same token, correlation preserved }, timeout=60) resp.raise_for_status() return resp.json()["anonymized_text"] with open("access.log") as src, open("access.sanitized.log", "w") as dst: buffer = [] size = 0 for line in src: buffer.append(line) size += len(line) if size >= CHUNK_CHARS: dst.write(sanitize_chunk("".join(buffer))) buffer, size = [], 0 if buffer: dst.write(sanitize_chunk("".join(buffer)))
// Scrub network identifiers from ticket bodies before they are stored async function scrubNetworkIdentifiers(req, res, next) { try { 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: req.body.ticketBody, entities: ["IP_ADDRESS", "MAC_ADDRESS", "IMEI", "COOKIE"], mask_mode: "replace", custom_instruction: "Do not flag private RFC 1918 IP ranges", }), }); const data = await resp.json(); req.body.ticketBody = data.anonymized_text; req.piiAudit = { found: data.entities_detected, ms: data.processing_time_ms }; next(); } catch (err) { next(err); // fail closed: do not store unscanned text on API failure } }
If you scan text dense with software version numbers, raise threshold so only high-confidence addresses survive:
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": "Upgraded agent to 7.2.1.4400; client 198.51.100.23 reconnected.", "entities": ["IP_ADDRESS"], "threshold": 0.8, "mask_mode": "replace" }'
Four-part version strings are the classic IPv4 false positive. The model resolves most through context — "released", "upgraded to", "SDK" push confidence down; "connected from", "client=", "src" push it up. For build-log-heavy corpora, combine a raised threshold with a custom_instruction such as "ignore software version numbers".
RFC 5737 reserves 192.0.2.0/24, 198.51.100.0/24, and 203.0.113.0/24 for documentation, and 2001:db8::/32 serves the same role for IPv6. These are detected like any other address — the API cannot know your text is documentation — so if you scan technical manuals, exclude them explicitly via custom_instruction.
Threat-intelligence write-ups deliberately obfuscate addresses as 203[.]0[.]113[.]45 or hxxp://… to prevent accidental clicks. The detector recognizes common defanging conventions, which matters if your policy is to strip indicators before sharing reports externally — see the cybersecurity industry guide for the incident-response workflow.
Modern mobile operating systems randomize Wi-Fi MAC addresses per network, so a detected MAC may be ephemeral rather than a stable hardware identifier. Detection still flags it — a randomized MAC is stable per network and remains an identifier in that scope — but your retention policy may reasonably treat locally-administered MACs (second hex digit 2, 6, A, or E) differently.
Note: Offsets in the response are character positions into the exact string you submitted. If you pre-process logs (trimming, re-encoding), do the masking with anonymized_text from the same call rather than applying offsets to a different copy of the data.
Yes. Both families are returned under the IP_ADDRESS entity type, including compressed IPv6 notation, IPv4-mapped IPv6 forms, zone indices, bracketed address-plus-port forms, and CIDR notation. You do not need separate configuration for the two families.
They are detected by default, because internal addresses can identify employees and internal infrastructure exposure is itself a security concern. If your policy excludes them, add a custom_instruction such as "do not flag private RFC 1918 IP ranges" — no client-side filtering needed.
Use mask_mode: "hash". Every occurrence of the same address maps to the same consistent token, so grouping, joining, and rate-limit analytics continue to work while the raw address is no longer stored. Note that consistent hashing is pseudonymization under GDPR, not anonymization — it reduces risk but keeps the data in scope.
In most operational contexts, yes. The CJEU held in Breyer that a dynamic IP is personal data for a website operator because legal means exist to obtain the subscriber link from the ISP. Combined with the timestamps that logs always carry, treat logged client IPs as personal data unless your DPO documents otherwise.
Yes — batch lines into chunks of up to 50,000 characters per request instead of scanning line by line. A single request can cover tens of thousands of short log lines. See pricing for volume tiers, and the on-premise deployment option if data cannot leave your environment.
Alone, a common user agent is shared by millions of browsers. But an unusual UA, or any UA combined with an IP and timestamp, can fingerprint an individual browser with high precision — which is why CCPA lists "unique identifiers" and probabilistic identifiers explicitly. Detecting USER_AGENT lets you apply hashing so aggregate browser statistics survive without retaining the raw fingerprint.
Yes. The identifiers themselves are language-neutral, and the surrounding-context model works across the 60+ languages the API supports, so a MAC address inside a German support ticket or a Japanese crash report is detected just as reliably. See the supported languages page.
Try the live demo on your own log data, or get an API key and sanitize your first file in minutes.
Try the Live Demo View Pricing