OpenAI Privacy Filter with Python — 10-line tutorial
OpenAI Privacy Filter is accessible from Python in under 10 lines using httpx or requests. This tutorial covers the minimal viable integration, a production-ready wrapper class, and async patterns for batch workloads.
Prerequisites
pip install httpx # or: pip install requests
You'll need a license key from privacyfilter.run (or use the free tier, which works without a key for up to 3 calls/day).
The 10-line version
import httpx
text = "Hi, I'm Alex Tan. Reach me at alex@acme.com or +1 555-0123."
r = httpx.post(
"https://privacyfilter.run/api/redact",
json={"text": text, "license_key": "your-uuid-here"},
timeout=15,
).raise_for_status().json()
print(r["redacted_text"])
# → "Hi, I'm [PERSON_1]. Reach me at [EMAIL_2] or [PHONE_3]."
for e in r["entities"]:
print(e["type"], e["original"], "→", e["replacement"])
Response structure
{
"redacted_text": "Hi, I'm [PERSON_1]. Reach me at [EMAIL_2] or [PHONE_3].",
"entities": [
{"type": "PERSON", "original": "Alex Tan", "replacement": "[PERSON_1]", "start": 8, "end": 16},
{"type": "EMAIL", "original": "alex@acme.com", "replacement": "[EMAIL_2]", "start": 29, "end": 42},
{"type": "PHONE", "original": "+1 555-0123", "replacement": "[PHONE_3]", "start": 46, "end": 57}
],
"char_count": 61,
"plan": "credits_50"
}
Redaction modes
Pass "mode": "mask" for block characters, "mode": "tag" for XML-style tags:
# mode=mask
{"text": "Alex Tan", "mode": "mask"}
# → "████████"
# mode=tag
{"text": "Alex Tan", "mode": "tag"}
# → "<PII type='PERSON'>Alex Tan</PII>"
Production-ready wrapper
import httpx
from dataclasses import dataclass
@dataclass
class Entity:
type: str
original: str
replacement: str
start: int
end: int
class PrivacyFilter:
BASE = "https://privacyfilter.run/api"
def __init__(self, license_key: str, mode: str = "replace"):
self.license_key = license_key
self.mode = mode
self._client = httpx.Client(timeout=20)
def redact(self, text: str) -> tuple[str, list[Entity]]:
r = self._client.post(
f"{self.BASE}/redact",
json={"text": text, "license_key": self.license_key, "mode": self.mode},
).raise_for_status().json()
entities = [Entity(**e) for e in r["entities"]]
return r["redacted_text"], entities
def close(self):
self._client.close()
def __enter__(self):
return self
def __exit__(self, *_):
self.close()
# Usage
with PrivacyFilter("your-uuid-here") as pf:
clean, ents = pf.redact("Contact Sarah at sarah@example.com before Friday.")
print(clean)
print([e.type for e in ents])
Async pattern for batch processing
import asyncio
import httpx
LICENSE_KEY = "your-uuid-here"
async def redact_one(client: httpx.AsyncClient, text: str) -> dict:
r = await client.post(
"https://privacyfilter.run/api/redact",
json={"text": text, "license_key": LICENSE_KEY},
)
r.raise_for_status()
return r.json()
async def redact_batch(texts: list[str]) -> list[dict]:
async with httpx.AsyncClient(timeout=30) as client:
tasks = [redact_one(client, t) for t in texts]
return await asyncio.gather(*tasks)
texts = [
"Hi Maria, your invoice #2210 is ready.",
"Please contact Bob Smith at bob@widgets.io",
"The patient James Doe (DOB 1985-03-12) was discharged.",
]
results = asyncio.run(redact_batch(texts))
for r in results:
print(r["redacted_text"])
For very large batches (hundreds of documents), use the /api/redact/batch endpoint instead to reduce HTTP round-trips — it accepts up to 20 documents per call on paid plans.
Error handling
try:
r = httpx.post(...).raise_for_status().json()
except httpx.HTTPStatusError as e:
if e.response.status_code == 422:
detail = e.response.json().get("detail", "")
# common: char limit exceeded, invalid license key
print("Validation error:", detail)
elif e.response.status_code == 429:
print("Rate limit hit — back off and retry")
else:
raise
Get your license key at PrivacyFilter.run — $9 one-time for 50 redactions, or $19/month unlimited.