← Blog  ·  April 28, 2026  ·  5 min read

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.

See pricing →

Keep reading