piidetectionapi.com
Home
Solutions - Fundamentals
What Is PII Detection? NER vs Regex vs Rules Accuracy, Precision & Recall PII in Test Data
Solutions - Compliance
GDPR Personal Data HIPAA PHI Detection CCPA / CPRA PCI DSS Card Data
Solutions - AI & LLM Safety
LLM Guardrails Chatbot PII Filtering RAG Pipelines
Solutions - Data Discovery & DLP
Data Loss Prevention Log File Scanning Support Tickets Email Scanning Documents & PDFs Database Discovery ETL & Streaming Pipelines
Industries - Financial
Banking Fintech Insurance
Industries - Healthcare
Healthcare Pharma & Clinical Trials Telehealth
Industries - Public Sector & Legal
Government & FOIA Law Enforcement Law Firms & eDiscovery Education (FERPA)
Industries - Technology
SaaS Platforms Cybersecurity & IR Telecommunications Gaming & Platforms
Industries - Other
HR & Recruiting Retail & E-commerce Call Centers & BPO Real Estate Travel & Hospitality Marketing & AdTech
How-to Guides - Identity & Contact
Detect Names Detect Email Addresses Detect Phone Numbers Detect Physical Addresses Detect Dates of Birth
How-to Guides - IDs & Financial
Detect SSNs Detect Passport Numbers Detect Drivers Licenses Detect Credit Card Numbers Detect Bank Accounts & IBAN
How-to Guides - Technical & Health
Detect IP & Device IDs Detect Medical Records & PHI
Resources
Pricing API Docs Supported Entities Languages About Contact Sign In Try the Live Demo Get Started
Developer Documentation

API Documentation

Everything you need to integrate the PII Detection API into your applications: the complete endpoint reference plus hands-on tutorials for batch scanning, offset handling, redaction pipelines, long-document chunking, retries, and security.

How usage is counted: each request deducts the words in your submitted text plus a fixed 1,500-word allowance for the detection instruction set sent with every request. A 100-word text therefore deducts 1,600 words. Current usage is returned in every response as total_words, words_used and remaining_words.
150+
Entity Types
REST
Architecture
99.9%
Uptime SLA
Navigation

Documentation Overview

Find exactly what you need. Start with the endpoint reference if you are integrating, or jump into the tutorials for production patterns. The condensed reference lives on the API reference page.

Security

Authentication

All API requests require authentication using an API key. Your key should be kept secret and never exposed in client-side code.

API Key Authentication

Authentication with the PII Detection API is done via the api_key parameter included in each JSON request body. Every account can create multiple API keys for different environments or applications, and keys can be revoked at any time without affecting other keys.

Base URL: https://piidetectionapi.com/api/moderate.php

Request Format: Include your api_key and api_type parameters in the JSON body of every request. The api_type must be set to "pii_detection" for all detection requests. Set Content-Type: application/json in your request headers.

Environment Variables: We recommend storing your API key in an environment variable rather than hardcoding it. This makes it easy to use different keys for development, staging, and production without code changes.

Key Rotation: Regularly rotate your API keys as a security best practice. You can create a new key, update your applications, then delete the old key without any service interruption.

cURL
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": "Sample text to scan for PII"
  }'
API Reference

Detection Endpoint

A single endpoint scans text for 150+ entity types and returns each match with its type, exact text, character offsets, and a confidence score — plus an optional masked copy of the input. Requests and responses are JSON.

POST /api/moderate.php

The primary endpoint for PII detection. Identifies and locates sensitive entities across personal, financial, medical/PHI, credential, location, device/network, and demographic categories, then optionally applies a masking strategy (replace, redact, or hash) to produce anonymized_text.

Request Parameters

Parameter Type Required Description
api_key string Required Your API key for authentication.
api_type string Required Always "pii_detection".
text string Required The content to scan. Up to 50,000 characters per request — see the chunking tutorial for longer documents.
entities array Optional Entity types to detect, e.g. ["PERSON_NAME", "EMAIL_ADDRESS", "SSN"]. Default: all 150+ supported types. Full catalog on the entities page.
exclude_entities array Optional Entity types to skip. Matches of these types are neither reported nor masked. Useful for keeping generic terms (e.g. clinical vocabulary via ["MEDICAL_TERM", "TREATMENT"]) visible while names and IDs are still detected.
mask_mode string Optional How anonymized_text is built: "replace" (default, [TYPE] placeholders), "redact" (removes matches), or "hash" (consistent hashes — identical values map to identical tokens).
threshold number Optional Minimum confidence (0–1) for an entity to be included. Default 0.5. Raise for precision, lower for recall.
custom_instruction string Optional Natural-language exclusions to preserve specific terms (e.g. "Do not flag 'Acme Corporation'"). Maximum 500 characters.

Example Request

cURL
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"
  }'

Example Response

JSON
{
  "detected_entities": [
    {
      "type": "PERSON_NAME",
      "text": "John Doe",
      "start": 8,
      "end": 16,
      "confidence": 0.95
    },
    {
      "type": "EMAIL_ADDRESS",
      "text": "[email protected]",
      "start": 20,
      "end": 36,
      "confidence": 0.98
    },
    {
      "type": "PHONE_NUMBER",
      "text": "555-123-4567",
      "start": 40,
      "end": 52,
      "confidence": 0.97
    }
  ],
  "anonymized_text": "Contact [NAME] at [EMAIL] or [PHONE].",
  "entities_detected": 3,
  "processing_time_ms": 187,
  "mask_mode_used": "replace",
  "status": 200
}

Reading the response: detected_entities is the core detection output — each object carries the entity type, the matched text, zero-based character offsets start/end (end-exclusive, so text == input[start:end]), and a confidence score. anonymized_text is the masked copy, entities_detected the total count, processing_time_ms the server time, and status mirrors the HTTP status.

POST /api/moderate.php — Selective Detection & Threshold

Combine entities, exclude_entities, and threshold to control exactly what comes back. This request looks only for financial identifiers, at high confidence:

cURL
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": "Card 4111-1111-1111-1111 exp 12/25, IBAN GB29 NWBK 6016 1331 9268 19.",
    "entities": ["CREDIT_CARD_NUMBER", "CREDIT_CARD_EXPIRATION_DATE", "CVV_NUMBER", "IBAN_CODE", "SWIFT_BIC", "ROUTING_NUMBER"],
    "threshold": 0.8,
    "mask_mode": "hash"
  }'
POST /api/moderate.php — Custom Instructions

Use the custom_instruction parameter (max 500 characters) to preserve specific terms in plain English while everything else is still detected. Perfect for keeping your own company name, address, or product names out of the results.

cURL
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 Acme Corp, 123 Business Ave, San Francisco. Email: [email protected]",
    "mask_mode": "replace",
    "custom_instruction": "Do not flag Acme Corp or 123 Business Ave as these are our company details."
  }'
Tutorials

Production Patterns

Walkthroughs for the problems every real integration hits: working with offsets, building your own redaction on top of detection, batching, long documents, and resilient error handling.

Tutorial 1: Handling Character Offsets

Offsets are zero-based character indexes into the exact string you submitted; end is exclusive. That means text[start:end] in Python (or text.slice(start, end) in JavaScript) always equals the entity's text field. Two rules keep offsets reliable: (1) keep an untouched copy of the submitted text — any normalization (trimming, collapsing whitespace, decoding entities) must happen before the API call, never between the call and offset use; (2) when replacing spans, work right-to-left so earlier offsets stay valid.

Python — verify and use offsets
import requests

text = "Contact John Doe at [email protected] or 555-123-4567."

resp = requests.post(
    "https://piidetectionapi.com/api/moderate.php",
    json={
        "api_key": "YOUR_API_KEY",
        "api_type": "pii_detection",
        "text": text,
    },
    timeout=30,
)
data = resp.json()

for e in data["detected_entities"]:
    # The offsets always slice back to the matched text
    assert text[e["start"]:e["end"]] == e["text"]
    print(f"{e['type']:15s} {e['start']:4d}-{e['end']:4d}  {e['text']}")

One caveat for JavaScript: String.prototype.slice counts UTF-16 code units, so emoji and some non-Latin characters occupy two units. If your text contains such characters, compare against the entity's text field and adjust with a code-point-aware index if needed.

Tutorial 2: Building a Redaction Pipeline from Detection Output

The API already returns anonymized_text, but many teams need custom redaction: their own placeholder format, partial masking (keep the last 4 digits of a card), or per-type policies. Detection output gives you everything required — replace spans from the end of the string backwards so offsets never shift:

Python — custom redaction with per-type policies
def redact(text, entities):
    """Apply custom per-type redaction using detection offsets."""
    def policy(e):
        if e["type"] == "CREDIT_CARD_NUMBER":
            return "****-****-****-" + e["text"][-4:]   # keep last 4
        if e["type"] == "EMAIL_ADDRESS":
            return "<email removed>"
        return f"[{e['type']}]"                          # default placeholder

    # Sort by start descending: right-to-left replacement keeps offsets valid
    for e in sorted(entities, key=lambda x: x["start"], reverse=True):
        text = text[:e["start"]] + policy(e) + text[e["end"]:]
    return text

data = resp.json()
clean = redact(original_text, data["detected_entities"])

The same pattern powers highlighting in review UIs (wrap spans in <mark> tags instead of replacing them) and audit logs (store type + offsets, never the raw value). For end-to-end pipeline architectures see PII detection in ETL & streaming pipelines.

Tutorial 3: Batch Processing

To scan many records — support tickets, log lines, database rows — send one request per record and parallelize within your plan's rate limit. A bounded thread pool with a small concurrency (4–8 workers) saturates throughput without triggering 429s:

Python — concurrent batch scan
import requests
from concurrent.futures import ThreadPoolExecutor

API_URL = "https://piidetectionapi.com/api/moderate.php"

def detect(record):
    resp = requests.post(API_URL, json={
        "api_key": "YOUR_API_KEY",
        "api_type": "pii_detection",
        "text": record["text"],
        "threshold": 0.4,          # favor recall for discovery scans
    }, timeout=60)
    data = resp.json()
    return {
        "id": record["id"],
        "entities": data.get("detected_entities", []),
        "count": data.get("entities_detected", 0),
    }

records = [{"id": i, "text": t} for i, t in enumerate(load_tickets())]

with ThreadPoolExecutor(max_workers=6) as pool:
    results = list(pool.map(detect, records))

flagged = [r for r in results if r["count"] > 0]
print(f"{len(flagged)}/{len(results)} records contain PII")

Tips: keep each record under the 50,000-character limit; batch related short strings (e.g. all fields of one row) into a single request with clear separators to save per-request overhead; and persist results keyed by record ID so re-runs can skip already-scanned data.

Tutorial 4: Streaming & Chunking Long Documents

For documents beyond 50,000 characters, split on paragraph boundaries — never mid-sentence, because context is what lets the model distinguish a name from an ordinary word. Track each chunk's base offset and shift the returned entity offsets back into document coordinates:

Python — chunk, scan, remap offsets
MAX_CHARS = 50000

def chunk_document(doc):
    """Yield (base_offset, chunk) pairs split on paragraph boundaries."""
    start = 0
    while start < len(doc):
        end = min(start + MAX_CHARS, len(doc))
        if end < len(doc):
            cut = doc.rfind("\n\n", start, end)   # last paragraph break
            if cut > start:
                end = cut
        yield start, doc[start:end]
        start = end

all_entities = []
for base, chunk in chunk_document(long_document):
    resp = requests.post(API_URL, json={
        "api_key": "YOUR_API_KEY",
        "api_type": "pii_detection",
        "text": chunk,
    }, timeout=120)
    for e in resp.json()["detected_entities"]:
        e["start"] += base       # remap into document coordinates
        e["end"]   += base
        all_entities.append(e)

print(f"{len(all_entities)} entities across the whole document")

For streaming sources (chat messages, Kafka topics, log tails), scan each message as it arrives — messages are naturally sized well below the limit. Real-time patterns are covered in real-time PII filtering for chatbots and scanning application logs for PII.

Tutorial 5: Retries & Timeouts

Treat 429 and 5xx as retryable with exponential backoff plus jitter; treat 400/401/402/413 as permanent and surface them immediately. Set a generous client timeout — large texts can take several seconds — and always send requests over HTTPS:

JavaScript (Node) — retry with backoff
async function detectPII(text, { retries = 3 } = {}) {
    for (let attempt = 0; attempt <= retries; attempt++) {
        const controller = new AbortController();
        const timer = setTimeout(() => controller.abort(), 60000);
        try {
            const res = 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
                }),
                signal: controller.signal
            });
            const data = await res.json();

            if (data.status === 200) return data;
            if (data.status === 429 || data.status >= 500) {
                // retryable: exponential backoff + jitter
                const wait = Math.pow(2, attempt) * 1000 + Math.random() * 500;
                await new Promise(r => setTimeout(r, wait));
                continue;
            }
            throw new Error(`PII API error ${data.status}: ${data.error}`);
        } finally {
            clearTimeout(timer);
        }
    }
    throw new Error('PII API: max retries exceeded');
}

Tutorial 6: Best Practices & Security

Integration best practices:

  • Scan before storage, not after. Detect PII at the ingestion boundary (API gateway, message consumer, upload handler) so raw sensitive data never lands in logs, analytics, or LLM prompts. See PII detection for LLM guardrails.
  • Restrict entities to what you act on. Smaller result sets are easier to review, and your policies stay explicit.
  • Tune threshold per use case. Low (0.2–0.4) for discovery/DLP where recall matters; high (0.8+) for automated redaction where false positives damage documents.
  • Log entity types and offsets, never values. An audit record of {"type": "SSN", "start": 71, "end": 82} proves what was found without re-creating the exposure.
  • Use hash mode when analytics must survive masking. Consistent hashes preserve joins and counts across records.
  • Version your configuration. Store the entity list, threshold, and custom instructions in config so compliance can review exactly what production scans for.

Security: all traffic is TLS-encrypted end to end; submitted text is processed in memory and never stored or used for training; API keys can be rotated and revoked instantly from your dashboard; and the platform is GDPR-native audited. For workloads where data cannot leave your network, an on-premise deployment of the same detection engine is available — contact us. Details on data handling are on the data privacy page.

Detection

Supported Entity Types

The models detect over 150 types of sensitive data across personal identifiers, financial and payment card data, medical/PHI, credentials, location, device/network, and demographic categories. Here are the most commonly used types.

PERSON_NAME
EMAIL_ADDRESS
PHONE_NUMBER
SSN
CREDIT_CARD_NUMBER
ADDRESS
DATE_OF_BIRTH
PASSPORT_NUMBER
IBAN_CODE
MEDICAL_RECORD_NUMBER
API_KEY
IP_ADDRESS
View All 150+ Entity Types
Error Handling

HTTP Response Codes

The API uses standard HTTP response codes, mirrored in the JSON status field. All error responses include a JSON body with a descriptive error message.

200 Success. Detection results in the response body.
400 Bad Request. Invalid parameters or malformed JSON.
401 Unauthorized. Missing or invalid API key.
402 Insufficient credits. Top up or wait for the quota reset.
413 Payload too large. Text exceeds 50,000 characters.
429 Rate limited. Honor Retry-After and back off.
500 Server error. Internal error, retry with backoff.
503 Service unavailable. Temporary maintenance.
Error Response Example
{
  "error": "api_key is invalid or missing",
  "status": 401
}

Rate Limits & Quotas

Rate limits protect the API from abuse and ensure fair access for all users. Limits vary by plan and are applied per API key. When you exceed a limit, you'll receive a 429 response with a Retry-After header indicating when to retry.

Free Tier: 60 requests per minute. Ideal for testing and development against the interactive demo and small workloads.

Professional: 300 requests per minute. Suitable for production applications with moderate traffic, including batch scans with modest concurrency.

Enterprise: Custom rate limits based on your needs, with dedicated infrastructure and on-premise deployment options.

Payload limit: up to 50,000 characters of text per request — chunk longer documents as shown in Tutorial 4.

View Pricing Plans
FAQ

API Questions

What is the maximum text size per request?
50,000 characters per request. For longer documents, split on paragraph boundaries and remap the returned offsets by each chunk's base position — the chunking tutorial above includes ready-to-use code. Requests above the limit return status 413.
Which languages are supported?
Detection works in 60+ languages including English, Spanish, French, German, Portuguese, Italian, Dutch, Polish, Russian, Chinese, Japanese, Korean, Arabic, and Hebrew. Language is detected automatically — no parameter needed. See the full list on the supported languages page.
What is the difference between entities and exclude_entities?
entities is an allowlist: only the listed types are detected (default is all 150+). exclude_entities is a skip list applied on top: matching types are neither reported nor masked. Use custom_instruction instead when you want to preserve specific words or phrases rather than whole categories.
How does the confidence threshold work?
Every entity carries a confidence score from 0 to 1. The threshold parameter (default 0.5) filters out entities below your minimum before the response is built, affecting both detected_entities and the masking in anonymized_text. Lower it for discovery scans where recall matters; raise it for automated redaction where precision matters.
Is my data stored or used for training?
No. Submitted text is processed in memory and discarded after the response is returned — it is never stored, logged, or used to train models. All traffic is encrypted with TLS, and the platform is GDPR-native audited. On-premise deployment is available for data that cannot leave your network.
Can I get a redacted copy of the text as well as the detections?
Yes — every response includes anonymized_text, built from the detections according to mask_mode: replace for typed placeholders, redact to remove matches, or hash for consistent tokens that preserve joins. If you need a custom format, build it yourself from the offsets as shown in the redaction pipeline tutorial.

Ready to Start Detecting?

Get your API key and find sensitive data in your text within minutes — or try the live demo first, no signup required.