How to Redact PII from the Anthropic (Claude) API in Python
If your application sends user-generated text to the Anthropic Claude API, it will eventually send personal data — a Social Security number in a support ticket, a customer name in a chat message, a card number in a form. This guide shows three ways to catch and redact that PII in Python before it reaches Anthropic's servers.
The Problem
When you call client.messages.create(), the entire prompt — including any PII embedded in it — leaves your infrastructure and transits Anthropic's. Even under Anthropic's zero-retention commercial terms, that transit can still violate HIPAA, CCPA, GDPR, or your own data-classification policy.
import anthropic
client = anthropic.Anthropic()
# This sends "John Smith, SSN 123-45-6789" to Anthropic
response = client.messages.create(
model="claude-sonnet-4",
max_tokens=1024,
messages=[{
"role": "user",
"content": "Summarize this support ticket: Customer John Smith "
"(SSN 123-45-6789) called about billing issue #4521.",
}],
)Approach 1: Regex Pattern Matching
The fastest approach. Sanitize the message content before you build the messages array. Catches structured PII like SSNs and cards, but misses free-text PII like names and addresses.
import re
import anthropic
SSN = re.compile(r'\b(?!000|666|9\d{2})\d{3}[- ]?(?!00)\d{2}[- ]?(?!0000)\d{4}\b')
CARD = re.compile(r'\b(?:\d[ -]*?){13,16}\b')
def redact(text: str) -> str:
text = SSN.sub("[SSN_REDACTED]", text)
text = CARD.sub("[CARD_REDACTED]", text)
return text
client = anthropic.Anthropic()
raw = "Customer John Smith (SSN 123-45-6789), card 4111-1111-1111-1111."
response = client.messages.create(
model="claude-sonnet-4",
max_tokens=1024,
messages=[{"role": "user", "content": redact(raw)}],
)
# Claude sees: "Customer John Smith (SSN [SSN_REDACTED]), card [CARD_REDACTED]."
# ^ name still leaks — regex can't catch itLimitation: You'd need a separate pattern for every PII type — phone numbers, emails, driver's licenses, passports, IBANs — and regex still can't catch context-dependent PII like person names. Maintaining dozens of patterns is error-prone.
Approach 2: NLP-Based Detection (Presidio)
Microsoft Presidio combines NLP models with pattern matching to detect 30+ entity types — SSNs, cards, names, addresses, and more. Much broader coverage than regex, at the cost of some latency.
from presidio_analyzer import AnalyzerEngine
from presidio_anonymizer import AnonymizerEngine
import anthropic
analyzer = AnalyzerEngine()
anonymizer = AnonymizerEngine()
def redact_pii(text: str) -> str:
results = analyzer.analyze(
text=text, language="en",
entities=["US_SSN", "CREDIT_CARD", "PHONE_NUMBER",
"EMAIL_ADDRESS", "PERSON", "US_DRIVER_LICENSE"],
)
return anonymizer.anonymize(text=text, analyzer_results=results).text
client = anthropic.Anthropic()
raw = "Customer John Smith (SSN 123-45-6789) called about billing."
response = client.messages.create(
model="claude-sonnet-4",
max_tokens=1024,
messages=[{"role": "user", "content": redact_pii(raw)}],
)
# Claude sees: "Customer <PERSON> (SSN <US_SSN>) called about billing."Trade-off: Presidio adds ~30–200ms per request and you must deploy and maintain the NLP models yourself. Great for batch jobs; can be tight for real-time chat.
Approach 3: Gateway-Level Redaction (Zero Code Changes)
Instead of adding redaction code to every call, route requests through an AI gateway that detects and redacts PII before forwarding to Anthropic. Because the gateway is OpenAI-compatible, you can even call Claude through the OpenAI SDK — one client, any model, automatic 30+ entity redaction.
from openai import OpenAI
# Point the OpenAI SDK at the gateway — then request a Claude model
client = OpenAI(
base_url="https://api.aisecuritygateway.ai/v1",
api_key="aisg_your_key_here",
)
response = client.chat.completions.create(
model="claude-sonnet-4", # Claude, called via the OpenAI SDK
messages=[{
"role": "user",
"content": "Customer John Smith (SSN 123-45-6789) called about "
"billing issue #4521. Card ending 4111-1111-1111-1111.",
}],
)
# What Anthropic sees: "Customer [PERSON] (SSN [US_SSN]) called about
# billing issue #4521. Card ending [CREDIT_CARD]."Prefer to keep the native Anthropic SDK? Point its base_url at the gateway and the same redaction applies:
import anthropic
client = anthropic.Anthropic(
base_url="https://api.aisecuritygateway.ai",
api_key="aisg_your_key_here",
)
response = client.messages.create(
model="claude-sonnet-4",
max_tokens=1024,
messages=[{"role": "user", "content": "Customer John Smith, SSN 123-45-6789..."}],
)
# PII is redacted at the gateway before it ever reaches Anthropic.Which Approach Should You Use?
| Criteria | Regex | Presidio NLP | Gateway |
|---|---|---|---|
| Entity coverage | Per pattern | 30+ entity types | 30+ entity types |
| Setup time | 5 minutes | 30–60 minutes | 2 minutes |
| Added latency | < 1ms | 30–200ms | < 50ms |
| Catches names/addresses | No | Yes | Yes |
| Code changes required | Per API call | Per API call | 2 lines total |
| Maintenance | High (pattern updates) | Medium (model updates) | None |
| Works across providers | Manual per provider | Manual per provider | Automatic |
Beyond SSNs: Other PII in Claude Traffic
Production Claude traffic carries far more than SSNs:
- Credit/debit card numbers — Luhn-validated, all major networks
- Phone numbers — US, UK, and international formats
- Email addresses — including corporate domains
- Person names — NLP-based, handles “John Smith” and “Dr. Jane Doe”
- Physical addresses — street, city, state, ZIP
- Driver's license & passport numbers — multi-region formats
- Medical record numbers — HIPAA-relevant
- IBAN / bank account numbers — EU banking identifiers
Claude's vision support also means PII can arrive inside images — a scanned form, a screenshot. A gateway that runs OCR-based redaction catches those too, which regex and text-only Presidio pipelines miss.
Stop writing PII regex for every provider
Occludra auto-redacts 30+ entity types from every API call — Claude, GPT, Gemini, Llama, and 8+ more — in under 50ms, with two lines of code. Text and image OCR.
Join the Community