PII Redaction
Find and redact names, emails, phone numbers, addresses, SSNs, card numbers and dates of birth in free text.
The problem
Any team handling customer support transcripts, chat logs, form submissions, or documents that flow through third-party tools (logging, analytics, LLM prompts) eventually needs a scrubbing step before that text leaves their hands.
Who it's for
Teams that route customer text through logging, analytics, or third-party/LLM tools and need a scrubbing step in front of it — support platforms, chat and helpdesk tools, and internal data pipelines.
Use cases
- Scrubbing support transcripts before they're logged or sent to analytics
- Redacting sensitive fields from form submissions before storage
What it does
Send free text and get back a redacted version with each detected entity replaced by a `[REDACTED_TYPE]` placeholder, plus a structured list of every entity it redacted — type, exact original value, and offsets into the original text — so a caller can log, mask, or audit precisely what was found. Detects seven entity types: person names, email addresses, phone numbers, physical addresses, Social Security Numbers, credit card numbers, and dates of birth. It does not detect health information. Detection runs on an AI model, so your text is sent to our model provider, Anthropic, to produce the response. If the model reports a value that cannot be found in your text, as written or in another written form, that value is NOT redacted — the response says so in `warnings` and counts it in `unlocated_count`, so check both on every response. Neither field proves every value was found: a value the model misses is left in the text and is not reported.
Example
Request
POST /redact
{ "text": "Hi, this is Jane Doe. Reach me at jane.doe@example.com or 555-123-4567." }
Response
Captured live 2026-09-19 from the request above, values unedited. It replaced a hand-written example dating from 2026-08-27 — the only one on this site that had never been captured. The capture matched it exactly, field for field.
{
"redacted_text": "Hi, this is [REDACTED_PERSON]. Reach me at [REDACTED_EMAIL] or [REDACTED_PHONE].",
"entities": [
{ "type": "PERSON", "value": "Jane Doe", "start": 12, "end": 20 },
{ "type": "EMAIL", "value": "jane.doe@example.com", "start": 34, "end": 54 },
{ "type": "PHONE", "value": "555-123-4567", "start": 58, "end": 70 }
]
}
Calling it directly — real, working requests you can paste and run:
cURL
curl -X POST "https://pii-phi-redaction-detection-api.p.rapidapi.com/redact" \
-H "content-type: application/json" \
-H "X-RapidAPI-Key: $RAPIDAPI_KEY" \
-H "X-RapidAPI-Host: pii-phi-redaction-detection-api.p.rapidapi.com" \
-d '{"text": "Hi, this is Jane Doe. Reach me at jane.doe@example.com or 555-123-4567."}'
Python
import os
import requests
RAPIDAPI_HOST = "pii-phi-redaction-detection-api.p.rapidapi.com"
resp = requests.post(
f"https://{RAPIDAPI_HOST}/redact",
json={"text": "Hi, this is Jane Doe. Reach me at jane.doe@example.com or 555-123-4567."},
headers={
"X-RapidAPI-Key": os.environ["RAPIDAPI_KEY"],
"X-RapidAPI-Host": RAPIDAPI_HOST,
},
)
print(resp.json())
Node.js
const RAPIDAPI_HOST = 'pii-phi-redaction-detection-api.p.rapidapi.com';
const res = await fetch(`https://${RAPIDAPI_HOST}/redact`, {
method: 'POST',
headers: {
'content-type': 'application/json',
'X-RapidAPI-Key': process.env.RAPIDAPI_KEY,
'X-RapidAPI-Host': RAPIDAPI_HOST,
},
body: JSON.stringify({
text: 'Hi, this is Jane Doe. Reach me at jane.doe@example.com or 555-123-4567.',
}),
});
console.log(await res.json());
Machine-readable: the OpenAPI 3.1 document for this API, generated from its code — import it into Postman or a client generator. More on the developer page.
A ready-made Postman collection is also available: import it and the shared environment in Postman (Import, then select the file), then set RAPIDAPI_KEY to your RapidAPI key.
Pricing
A free tier of 20 requests a month covers light testing, no card required. Paid plans start at $49/month for 2,000 requests and are hard-capped — calls stop at the limit rather than billing you. Only the top plan (20,000 requests) runs on a soft limit, with overage at $0.01 per additional request beyond it.
Privacy commitment — the load-bearing promise of this API. By definition, every request to this endpoint contains text that may include sensitive personal information. Aervik Labs does not store submitted text beyond what’s needed to process the request: no database, no logging of request bodies, and no retention of input text, output text, or detected entities once the response is returned. Our model provider sees the text: detection runs on an AI model, so each request’s text is sent to Anthropic, which processes it under its own API data-retention policy. Every request also passes through RapidAPI and Cloudflare (security). Don’t send health information: this API does not detect it.
Check warnings and unlocated_count on every response. If the model reports a value that
cannot be found in your text in any written form — usually because a Unicode form or an invisible
character differs (letter case, spacing and digit separators are matched) — that value is not redacted, and the response says so rather than hiding it.
A value reported under a type this API does not recognize is counted the same way. Those fields
report values the model found but did not redact. They cannot report a value the model never
found, so a response with neither field is not proof that the text is clean. A dash-formatted SSN,
a card number that passes the Luhn check, an email address written in ASCII characters, or a North
American phone number written with separators is also
matched by pattern, so it is redacted even when the model misses it; names, addresses and dates of
birth have no such fallback. The same value written in capitals or with different spacing or
separators elsewhere in the text is redacted wherever it appears.
Detected values are located deterministically in the real input text rather than trusted
from the model’s own character counting, so offsets always line up exactly with the
original string: original_text.slice(start, end) always equals value, with start
inclusive and end exclusive. Offsets count UTF-16 code units, the way JavaScript indexes a
string. Python counts most emoji as one character where JavaScript counts two, so after one
text[start:end] in Python is shifted. Use redacted_text, or slice the UTF-16 encoding:
text.encode("utf-16-le")[2 * start:2 * end].decode("utf-16-le"). Callers that need the raw
value scrubbed from their own logs should redact it themselves before logging a raw API
response.
Overlapping values become one redaction. When two values found overlap, such as a name inside
an address, they are merged into one entity with one placeholder covering both. Its type is the
type of the longest value, and merged_types lists each type merged, in the order the values start
in the text. The same name written elsewhere in the text, outside the address, is a separate entity.
What it does not do.
- Seven types only. It does not detect IP addresses, bank account or IBAN numbers, passport, driver’s license or other national ID numbers (Social Security Numbers are U.S. only), API keys or other secrets, or health information.
- The phone pattern covers North American numbers. It matches 3-3-4 digit groups with
separators, optionally with parentheses or a
+1prefix. A number written in another country’s format is redacted when the model reports it, and how often the model does has not been measured. - One text per request, up to 50,000 UTF-16 code units. Longer text is refused with
input_too_large; split it. - No confidence scores, and one placeholder per type. Every email becomes
[REDACTED_EMAIL]; placeholders are not numbered, and they change the text’s length, so offsets always index the original text, neverredacted_text. - English only, as far as we have measured. Accuracy on other languages has not been measured.
- Very dense text can keep failing. If the list of values found is too long for one
model reply, the request fails with
redaction_failedrather than returning a partial redaction. If the same text fails twice, split it into smaller pieces.