Implement data privacy controls for OpenRouter API usage. Use when handling PII, meeting GDPR/CCPA requirements, or protecting sensitive data in prompts. Triggers: 'openrouter privacy', 'openrouter pii', 'openrouter gdpr', 'openrouter data handling'.
Install with Codex or Claude Copy this prompt, paste it into Codex, Claude, or another assistant, and let it review the skill page and install it for you.
A direct command skips the review prompt. Inspect the source before running it.
Implement data privacy controls for OpenRouter API usage. Use when handling PII, meeting GDPR/CCPA requirements, or protecting sensitive data in prompts. Triggers: 'openrouter privacy', 'openrouter pii', 'openrouter gdpr', 'openrouter data handling'.
Designed for Claude Code, also compatible with Codex and OpenClaw
OpenRouter Data Privacy
Overview
When sending data through OpenRouter to upstream LLM providers, you're responsible for ensuring prompts don't leak PII inappropriately. OpenRouter itself does not train on API data, but each upstream provider has its own data retention and training policies. This skill covers PII detection and redaction, placeholder substitution, provider selection for privacy, and consent tracking.
Prerequisites
An OpenRouter API key (sk-or-v1-...) exported as OPENROUTER_API_KEY — see the openrouter-install-auth skill for setup
Python 3.8+ with the OpenAI SDK (pip install openai) — every pattern in this skill is Python
A sensitivity classification for your workloads (public / standard / sensitive) so privacy_aware_completion() can route each one
A list of providers your org approves for sensitive data, to plug into provider.order with allow_fallbacks: False
Instructions
Start with PII Detection and Redaction: adapt PII_RULES (email, phone, SSN, credit card, sk-or-v1- API keys, IPs) to your data, then run scan_and_redact() on representative inputs and review the findings for false positives.
When downstream code needs the original values back, use the Placeholder Substitution Pattern instead of plain redaction — PrivacyProxy.anonymize() before the API call, deanonymize() on the model's reply.
Classify each workload and route it via Provider Selection for Privacy: privacy_aware_completion() maps sensitivity to a model plus a provider block (order: ["Anthropic"], allow_fallbacks: False for standard/sensitive).
Wire the Privacy Middleware into every call path, choosing block_on_pii=True (raise on detection) or auto_redact=True (scrub and continue) per workload.
Apply the Enterprise Considerations: hash logged prompts (SHA-256) for GDPR right-to-erasure, and use BYOK for the most sensitive workloads.
"""Scan text for PII and return redacted version with findings."""
for
in
for
match
in
"type"
"value_prefix"
match
4
"..."
return
len
0
Placeholder Substitution Pattern
import os, uuid
from openai import OpenAI
client = OpenAI(
base_url="https://openrouter.ai/api/v1",
api_key=os.environ["OPENROUTER_API_KEY"],
default_headers={"HTTP-Referer": "https://my-app.com", "X-Title": "my-app"},
)
classPrivacyProxy:
"""Replace PII with placeholders before API, restore after."""def__init__(self):
self._map: dict[str, str] = {}
defanonymize(self, text: str) -> str:
"""Replace PII with unique placeholders."""
result = scan_and_redact(text)
ifnot result.has_pii:
return text
# Use deterministic placeholders for consistent replacement
anonymized = text
for pii_type, pattern in PII_RULES:
formatchin re.finditer(pattern, anonymized):
original = match.group()
if original notinself._map:
placeholder = f"[{pii_type.upper()}_{len(self._map)}]"self._map[placeholder] = original
else:
placeholder = next(k for k, v inself._map.items() if v == original)
anonymized = anonymized.replace(original, placeholder, 1)
return anonymized
defdeanonymize(self, text: str) -> str:
"""Restore original values from placeholders."""
result = text
for placeholder, original inself._map.items():
result = result.replace(placeholder, original)
return result
# Usage
proxy = PrivacyProxy()
user_input = "Contact john@example.com or call 555-123-4567"
safe_input = proxy.anonymize(user_input)
# safe_input = "Contact [EMAIL_0] or call [PHONE_1]"
response = client.chat.completions.create(
model="anthropic/claude-3.5-sonnet",
messages=[{"role": "user", "content": safe_input}],
max_tokens=200,
)
# Restore PII in the response if model referenced it
result = proxy.deanonymize(response.choices[0].message.content)
Provider Selection for Privacy
# Force specific provider to control data handlingdefprivacy_aware_completion(messages, sensitivity="standard"):
"""Route to appropriate provider based on data sensitivity."""
PRIVACY_CONFIG = {
"public": {
"model": "openai/gpt-4o-mini",
"provider": None, # Any provider OK
},
"standard": {
"model": "anthropic/claude-3.5-sonnet",
"provider": {"order": ["Anthropic"], "allow_fallbacks": False},
},
"sensitive": {
"model": "anthropic/claude-3.5-sonnet",
"provider": {"order": ["Anthropic"], "allow_fallbacks": False},
# Add PII redaction as mandatory pre-processing
},
}
config = PRIVACY_CONFIG.get(sensitivity, PRIVACY_CONFIG["standard"])
extra = {}
if config["provider"]:
extra["extra_body"] = {"provider": config["provider"]}
return client.chat.completions.create(
model=config["model"],
messages=messages,
max_tokens=1024,
**extra,
)
Privacy Middleware
classPrivacyMiddleware:
"""Enforce privacy policies before every API call."""def__init__(self, block_on_pii: bool = False, auto_redact: bool = True):
self.block_on_pii = block_on_pii
self.auto_redact = auto_redact
defprocess(self, messages: list[dict]) -> list[dict]:
"""Scan and optionally redact PII from all messages."""
processed = []
for msg in messages:
content = msg.get("content", "")
ifisinstance(content, str):
result = scan_and_redact(content)
if result.has_pii:
ifself.block_on_pii:
raise ValueError(f"PII detected: {[f['type'] for f in result.findings]}")
ifself.auto_redact:
msg = {**msg, "content": result.clean_text}
processed.append(msg)
return processed
Output
The privacy flows in this skill produce:
A PiiScanResult per scan: clean_text with placeholders substituted, findings (PII type + first-4-chars value prefix per match), and a has_pii flag
Anonymized prompts like "Contact [EMAIL_0] or call [PHONE_1]" plus the placeholder→original map that deanonymize() uses to restore values in the response
Chat completions served only by approved providers when the provider.order + allow_fallbacks: False config is applied
A ValueError listing the detected PII types when PrivacyMiddleware runs with block_on_pii=True
Examples
Scanning a support message before it leaves your infrastructure:
result = scan_and_redact("Contact john@example.com or call 555-123-4567")
print(result.clean_text) # Contact [EMAIL] or call [PHONE]print(result.has_pii) # Trueprint(result.findings) # [{'type': 'email', 'value_prefix': 'john...'}, {'type': 'phone', ...}]
To keep the values recoverable, run the same input through PrivacyProxy.anonymize() instead, send the placeholder version to the model, then deanonymize() the reply. More worked examples: references/examples.md.
Error Handling
Error
Cause
Fix
PII detected in prompt
User input contains sensitive data
Auto-redact or block and prompt user to remove
Provider retained data
Using provider with training-on-API-data
Switch to Anthropic or use BYOK
Placeholder in response
Model used placeholder literally
Map it back with deanonymize()
False positive PII match
Regex too aggressive
Tune patterns; use NLP-based PII detection for accuracy
Enterprise Considerations
OpenRouter does not train on API data; check each upstream provider's data use policy separately
Use provider.order + allow_fallbacks: false to ensure data only flows to approved providers
Implement PII redaction as middleware that runs on every request, not optional per-call
For GDPR right-to-erasure: don't log raw prompts -- hash them (SHA-256)
Use BYOK for sensitive workloads so data flows directly to the provider under your account
Build a data classification system that auto-routes based on sensitivity level