Skip to content

Developer guide · Anthropic

How to mask personal data before it reaches the Claude API

Maskflare EngineeringPublished 6 min read

Short answer

Mask the personal data in each Messages API request before it leaves your server and restore it in Claude's answer. Write your own validators for a few formats, use Presidio for prototypes, or point the Anthropic SDK at a masking gateway, which covers the system prompt, every message, tool input and streamed responses.

Claude tends to get the long documents: contracts, case files, a year of support history for one account. A single Messages API request can carry hundreds of names, addresses and account numbers, most of which the task doesn't need. A summary of a dispute needs to know there are two parties and a disputed CHF 2,400. It doesn't need Ada Lovelace's IBAN.

This guide covers what Anthropic keeps, then three ways to mask the data before it goes: your own code, Presidio, and a gateway in front of the Anthropic SDK.

What does Anthropic keep from API requests?

As of October 2026, Anthropic's commercial data retention article and its API and data retention docs say:

  • Deleted within 30 days by default. For API users, inputs and outputs are deleted on Anthropic's backend within 30 days of receipt or generation.
  • Not used for training without permission. The API docs state that retained data is never used for model training without your express permission.
  • Exceptions exist. Files uploaded through the Files API stay until you delete them. Content flagged for a Usage Policy violation can be kept up to 2 years, and longer retention applies where the law requires it.
  • Zero data retention is an arrangement. ZDR covers eligible API features under an agreement with Anthropic. Some models that Anthropic designates as Covered Models require 30-day retention and aren't available under ZDR unless Anthropic expressly authorizes it.
  • No EU inference option on the first-party API. The inference_geo parameter accepts global (the default) or us, and the only workspace storage region is us. EU regions are available through Amazon Bedrock or Google Cloud, where the cloud provider is the processor.

Check Anthropic's data residency page and your agreement for the current position; these pages are updated often.

For a European team, the last point matters. Personal data sent to the first-party API may be processed outside the EU, so you need a transfer mechanism on top of the DPA. Sending masked text instead leaves the provider with tokens, not identities.

Where does personal data sit in a Messages API request?

  • The `system` prompt. Templates often interpolate the customer's name, plan and account number.
  • Every message in `messages`. The API is stateless, so the full conversation goes up on every call.
  • Documents and long context. Pasted contracts, transcripts and retrieved chunks, often the bulk of the tokens.
  • `tool_use` input and `tool_result` content. Claude asks for a lookup, your code returns a customer record, and that record goes up on the next turn.

Option 1: validate and redact it yourself

For a handful of formats, your own code is fine. IBANs are a good example because they carry a mod-97 checksum, so you can tell a real one from a random alphanumeric string:

Python: IBANs with a mod-97 check
import re

# Country code, check digits, then groups of up to four characters
IBAN = re.compile(r"\b[A-Z]{2}\d{2}(?: ?[A-Z0-9]{4}){2,7}(?: ?[A-Z0-9]{1,4})?\b")

def iban_ok(candidate: str) -> bool:
    s = candidate.replace(" ", "")
    if not 15 <= len(s) <= 34:
        return False
    rearranged = s[4:] + s[:4]
    digits = "".join(str(int(ch, 36)) for ch in rearranged)  # A=10 ... Z=35
    return int(digits) % 97 == 1

def redact_ibans(text: str) -> str:
    return IBAN.sub(lambda m: "[IBAN]" if iban_ok(m.group()) else m.group(), text)

print(redact_ibans("Refund to CH93 0076 2011 6238 5295 7, ref AB12 3456 7890 1234"))
# Refund to [IBAN], ref AB12 3456 7890 1234

The checksum is doing the work: the reference number next to the IBAN has the same shape and fails the check. But the pattern assumes groups of four. Someone who types CH93 007620116238 52957 slips straight through, and that's the long tail you'll spend months on: odd spacing, national IDs per country, names, secrets, encoded text. The code also discards the value, so Claude can't write "we've sent CHF 2,400 to your account ending 957".

Option 2: Presidio in front of the Anthropic SDK

Presidio works the same way here as with any provider: analyze the text, anonymize it, call client.messages.create, and decrypt placeholders in the answer if you used the encrypt operator. You'll need to walk the Messages API shape yourself, including system, content blocks and tool_result blocks, and handle streamed content_block_delta events if you restore while streaming. Good for a prototype; plan the engineering time before production. Our Presidio comparison goes into detail.

Option 3: put a masking gateway in front of Claude

Change the Anthropic SDK's base URL to your Maskflare gateway and authenticate with a Maskflare key. If you've stored the Anthropic key in the console, that's all. If you'd rather keep the Anthropic key in your app (the connection's "Application sends key" mode), pass it in the X-Maskflare-Provider-Key header:

Python: Anthropic SDK through Maskflare, pass-through key
from anthropic import Anthropic

client = Anthropic(
    base_url="https://YOUR_GATEWAY_HOST/v1/anthropic",
    api_key="mf_live_...",  # your Maskflare key
    # only when the provider connection is in "Application sends key" mode:
    default_headers={"X-Maskflare-Provider-Key": "sk-ant-..."},
)

message = client.messages.create(
    model="claude-opus-5-5",
    max_tokens=1024,
    system="You write replies for the billing team.",
    messages=[{"role": "user", "content":
        "Tell Ada Lovelace (ada@example.com) that CHF 2,400 went to "
        "CH93 0076 2011 6238 5295 7 today."}],
)
print(message.content[0].text)

Claude receives mf_mask_ tokens where the name, email and IBAN were. Each value keeps the same token for the whole request, so Claude can still refer to "the customer" consistently. With restoring on for the key, the answer you print has the real values back. Streaming works the same way, and tokens in streamed text and tool_use input are restored as the events arrive:

Python: streaming through the gateway
with client.messages.stream(
    model="claude-opus-5-5",
    max_tokens=1024,
    messages=[{"role": "user", "content": "Summarize this case note for Jan de Vries: ..."}],
) as stream:
    for text in stream.text_stream:
        print(text, end="", flush=True)

If a block rule matches before the call, the gateway returns HTTP 403 with the rule keys. If one matches mid-stream, the stream ends with an Anthropic-format error event, so the SDK raises it like any other API error. In TypeScript, the change is the same two settings:

TypeScript: Anthropic SDK through Maskflare
import Anthropic from '@anthropic-ai/sdk';

const client = new Anthropic({
  baseURL: 'https://YOUR_GATEWAY_HOST/v1/anthropic',
  apiKey: process.env.MASKFLARE_API_KEY!, // a Maskflare gateway key
});

const message = await client.messages.create({
  model: 'claude-opus-5-5',
  max_tokens: 1024,
  messages: [{ role: 'user', content: 'Draft a reply to ada@example.com about invoice 4471' }],
});

Not routing through a gateway? The SDKs mask text anywhere in your code: client.mask(text) returns output and tokens, and unmask_text (Python) or unmaskText (TypeScript) restores an answer. They take a Redaction-purpose key, which can't be used to forward AI traffic.

How do you roll it out without breaking anything?

Don't switch masking on for every Claude workload on a Monday morning. A gradual rollout takes about a week and tells you exactly what you were sending:

  1. Start in alert mode. Set the rules to alert instead of mask. Requests pass unchanged, and the traffic log counts which rules matched on which route. Records hold metadata and counts only, never the prompt or the value.
  2. Apply a compliance profile. The GDPR (EU/EEA) or UK GDPR profile switches on the matching detectors in one click. Add custom rules for your own identifiers, such as customer numbers or case references.
  3. Check the near-misses in the playground. Paste a realistic case note and use the explain view to see which rule matched and why a near-miss was rejected.
  4. Switch to mask and turn on restoring. Do it per environment: development first, then staging, then production, each with its own key prefix (mf_dev_, mf_stg_, mf_live_).
  5. Read a week of answers. Look for tokens the model changed or dropped, and for prompts where masking removed context the task needed. Adjust the rule, not the prompt.

If several people can change rules, turn on maker-checker approval so a second person signs off on every change. A rule that quietly stops matching IBANs is as much a risk as one that never existed.

What about Claude Code and the Claude web app?

Different paths, different controls. For engineers using Claude Code, the maskflare-hook command blocks prompts that contain secrets and masks Bash, WebFetch, Write, Edit and MCP tool input and output before the model reads it; see the Claude Code integration. For people typing into Claude on the web, browser protection masks the text before it's sent and restores values where the answer repeats them.

What are the limits?

  • The Anthropic route forwards to api.anthropic.com. It doesn't front Amazon Bedrock or Google Cloud endpoints, so if you use Claude through a cloud provider's EU region, mask in your code with the SDK.
  • Text only. Images and PDFs sent as content blocks aren't read as text by the masking step, so a photographed passport isn't masked. File policies can block or allow documents; that's a separate control.
  • Context can identify people. A unique job title plus a small town is identifying without any detectable value. Masking reduces exposure; it doesn't make data anonymous.
  • Names are the hardest category. Maskflare finds them with word lists, context and a local named-entity layer, but test on your own documents.

Frequently asked questions

Does Anthropic train Claude on API data?

As of October 2026, Anthropic's API documentation says retained data is never used for training without your express permission. Consumer plan settings don't apply to API traffic under the Commercial Terms. Check the current terms before relying on this.

How long does Anthropic keep API prompts?

By default, inputs and outputs are deleted within 30 days, with exceptions for the Files API, Usage Policy enforcement and legal requirements. Zero data retention is available for eligible features by arrangement.

Can I keep Claude API processing in the EU?

Not on the first-party API as of October 2026: inference_geo supports global and us only. EU regions are offered through Amazon Bedrock and Google Cloud. Masking means the personal data doesn't leave in the first place, wherever inference runs.

Which key goes in the Anthropic SDK?

A Maskflare key. Your Anthropic key is stored once in the console, or sent per request in the X-Maskflare-Provider-Key header when the connection is in pass-through mode.

Does masking break tool use?

No. tool_use input and tool results are scanned like any other text, and with restoring on, arguments Claude returns are restored before your code runs the tool.

About the author

Maskflare Engineering

Guides from the engineers who build Maskflare's gateway, detection engine, desktop agent and SDKs. Every code sample is run against the product before it's published, and every limit we mention is one we hit ourselves.

Keep reading

See it on your own data

Try Maskflare free.
Bring your hardest prompts.

28-day free trial or 10,000 requests, whichever comes first. Point one app at the gateway or enroll a few laptops,
and watch what gets masked. Or talk to us first.