Agents / Guide

Strip personal data from text before it reaches an LLM

Redact government IDs, contact details, names and card numbers with a rescan that proves the output is clean, and learn which categories the default levels leave out.

Scrub first, then send

Support tickets, contracts and clinical notes routinely end up in prompts. The PII Redaction endpoint removes personal data from text, returns a count by category and the position of each redaction, and never repeats the original values in its response. It stores nothing and accepts up to 1,000,000 characters.

It then rescans its own output. verification.passed: true with residualDetections: 0 is evidence that nothing detectable remains for the categories you asked for, which is the claim to log next to the model call.

Request
curl -sS -X POST "https://$RAPIDAPI_HOST/v1/redact-text" \
  -H "Content-Type: application/json" \
  -H "X-RapidAPI-Key: $RAPIDAPI_KEY" \
  -H "X-RapidAPI-Host: $RAPIDAPI_HOST" \
  -d '{
    "text": "Ticket 4471: Maria Gonzalez ([email protected]) says her card 4111 1111 1111 1111 was charged twice. SSN on file 123-45-6789.",
    "level": 4,
    "include": ["credit_card"],
    "style": "label"
  }' | jq '.data | {redactedText, summary, verification}'

Choose the level, then check what it does not cover

level is a cumulative preset: 1 is government identifiers (SSN, ITIN, EIN, UK NINO, Canadian SIN, driver's license, passport), 2 adds dates of birth, 3 adds street addresses and phone numbers, 4 adds person names and email addresses. The default is 1.

Payment card numbers are not part of any level. We ran the same ticket at level 4 without include and the card number came back untouched, while verification.passed was still true, because the rescan checks only the categories that were requested. Add "include": ["credit_card"] (also available: iban, routing_number, account_number, ip_address, url, date, organization), or set categories to the exact list you want.

Read the coverage array in the response. It states, per category, the detection method and its limits: for example, addresses are pattern-matched for US and UK layouts only, and driver's license and unlabeled birth-date detection depends on a nearby label.

What the redacted text looks like

With style: "label" each removal is replaced by its category, which keeps the text readable for a model:

Response (abridged)
{
  "data": {
    "redactedText": "Ticket 4471: [REDACTED:PERSON] ([REDACTED:EMAIL]) says her card [REDACTED:CARD] was charged twice. SSN on file [REDACTED:SSN].",
    "summary": { "total": 4, "byCategory": { "person_name": 1, "email": 1, "credit_card": 1, "ssn": 1 }, "charactersRemoved": 70 },
    "verification": { "passed": true, "residualDetections": 0, "leakedValues": 0 }
  }
}
Without include, at level 4 (card number left in place)
{ "redactedText": "Ticket 4471: [REDACTED:PERSON] ([REDACTED:EMAIL]) says her card 4111 1111 1111 1111 was charged twice. SSN on file [REDACTED:SSN]." }

Use it as a wrapper around your model call

Person names are detected heuristically (confidence: "heuristic" in each redaction), so add known names, project names and customer names to terms, and put strings that must survive, such as your own company name, in keep. minDigitRun redacts any run of that many digits, a blunt but useful catch-all for account and order numbers. useModel adds a language-model pass for names, organizations and addresses, but it sends the text (up to 60,000 characters) to a model provider. Leave it off when the point of redaction is to keep the text away from third parties.

Fail closed: if the call errors or verification.passed is false, do not send the original text onward.

Python
import os, requests

def redact(text: str) -> str:
    r = requests.post(
        f"https://{os.environ['RAPIDAPI_HOST']}/v1/redact-text",
        headers={"X-RapidAPI-Key": os.environ["RAPIDAPI_KEY"], "X-RapidAPI-Host": os.environ["RAPIDAPI_HOST"]},
        json={"text": text, "level": 4, "include": ["credit_card", "iban"], "terms": ["Acme Corp"], "style": "label"},
        timeout=30,
    )
    r.raise_for_status()
    data = r.json()["data"]
    if not data["verification"]["passed"]:
        raise RuntimeError("redaction did not verify; refusing to forward text")
    return data["redactedText"]

prompt = "Summarize this ticket:\n" + redact(open("ticket.txt").read())

Try it

  • Subscribe on RapidAPI and call it with your key.
  • Prefer to pay per call in USDC with no account? The same request body goes to POST https://agents.writerslogic.com/api/v1/redact-text; see the endpoint documentation.
  • Every response is signed and can be checked for free at POST https://agents.writerslogic.com/api/v1/verify-provenance.

More guides for this API