- Most prompts need the structure of an identifier, not its value: the model can draft a reply to PERSON_1 about IBAN_1 just as well.
- Tokens must be consistent within a conversation and unlinkable across tenants, which rules out a single global deterministic hash.
- Pseudonymised data stays personal data for anyone who holds or can reach the mapping; treat masking as a supplementary measure, not an exit from GDPR.
- Detection is never perfect, so pseudonymisation should sit on top of in-jurisdiction routing, not replace it.
The model rarely needs the identifier
Look at what a support summarisation prompt actually asks a model to do. Read a ticket, identify the problem, propose a reply. The customer's name matters only as a slot in the greeting. The IBAN matters only as the thing the complaint is about. The German tax ID in the attached letter matters not at all. The model needs to know that these are a person, an account and an identifier, and that they are the same person and account each time they appear. It does not need their values.
That gap is where pseudonymisation earns its keep. Replace each identifier with a typed token on the way out, keep the mapping where the data originated, and restore the real values in the response before it reaches the user. The upstream model sees a coherent document with the identities removed. Your application sees the full answer.
Getting the tokens right
The token design decides whether this helps or hurts. Random tokens that change on every mention break the model's reasoning: it cannot tell that the person in paragraph one is the person in paragraph four. Tokens therefore need to be consistent within a conversation. They should also be typed, so the model knows PERSON_2 is a person and IBAN_1 is an account, which keeps generated text grammatical.
What they must not be is globally deterministic. If the same name always maps to the same token, for every tenant and every request, a party that observes many prompts can link records across them, and a party that guesses a name can test it. We scope the mapping to the tenant and the conversation, key it inside the origin jurisdiction, and keep it in a vault that never leaves that jurisdiction. On Sovereign Dedicated deployments the vault keys sit in the customer's HSM.
Restoration is the part people forget to test. Models paraphrase. They write PERSON_1's, split a token across a line break, or occasionally invent PERSON_3 when only two exist. The restoring step has to match tokens tolerantly, refuse to substitute anything it did not issue, and flag responses containing unknown tokens rather than silently passing them through. A reply that greets the customer as PERSON_1 is embarrassing; one that greets them with someone else's name is a breach.
- Consistent within a conversation, so the model can follow references.
- Typed, so outputs stay readable and restorable.
- Scoped per tenant and per conversation, so tokens cannot be linked across them.
- Reversible only inside the origin jurisdiction.
What it looks like from the application
From the calling code nothing changes except the base URL and two headers. The policy pack decides which entity types to mask. Detection runs in the gateway's data plane in the tenant's region, so the clear-text values do not travel any further than they already had to.
from openai import OpenAI
client = OpenAI(
base_url="https://eu.yttsecunet.com/v1",
api_key="jr_live_...",
default_headers={
"X-YTT-Policy": "eu-gdpr-strict",
"X-YTT-Tenant": "kundenservice-de",
},
)
ticket = (
"Kundin Petra Lindqvist (Steuer-ID 86095742719) meldet eine doppelte "
"Abbuchung von DE89 3704 0044 0532 0130 00 am 3. Juni."
)
resp = client.chat.completions.create(
model="mistral-large-latest",
messages=[
{"role": "system", "content": "Draft a short, polite reply in German."},
{"role": "user", "content": ticket},
],
)
# Upstream received: "Kundin [PERSON_1] (Steuer-ID [DE_TAX_ID_1]) meldet eine
# doppelte Abbuchung von [IBAN_1] am 3. Juni."
# The reply below has the real name and IBAN restored in-region.
print(resp.choices[0].message.content)Where the law sits
Article 4(5) GDPR defines pseudonymisation as processing so that data can no longer be attributed to a specific person without additional information kept separately and protected. Recital 26 is explicit that pseudonymised data which could be attributed using additional information remains personal data. The EDPB's Guidelines 01/2025 on pseudonymisation, published for consultation in January 2025, develop the point and treat pseudonymisation as a safeguard that can support, among other things, transfers under Article 46.
The Court of Justice added nuance in its September 2025 judgment in EDPS v SRB, holding that whether pseudonymised data is personal data can depend on the perspective of the recipient and whether it can reasonably reidentify. That is helpful, but it is not a licence. The controller holding the mapping still processes personal data, and whether a given recipient can reidentify depends on everything else in the prompt. Treat masking as a strong supplementary measure that shrinks what an importer sees, and keep your Chapter V analysis intact.
Where it leaks
I would rather you hear the limits from us. Named entity detection has false negatives. A misspelled name, an identifier split across lines or a format we have not seen will sometimes pass through. Free text also carries quasi-identifiers that no detector treats as PII: a rare diagnosis, a small town and an age together can single out one person. And some tasks genuinely need the value, such as checking whether an IBAN's check digits are valid, which you should do locally rather than ask a model about anyway.
That is why we never present pseudonymisation as the reason a request may leave its jurisdiction. It is defence in depth. The request is routed to an in-region endpoint first, and masking reduces what that endpoint, and anyone who could compel it, ever sees. If detection misses something, the miss stays inside the jurisdiction.
Measuring whether it works
Build a labelled set from your own traffic patterns, with synthetic identities in your real formats: German tax IDs, French NIR numbers, UK NHS numbers, IBANs from the countries you serve. Run it through the guardrail and measure recall per entity type, then look at every miss by hand. Re-run it whenever you change the policy pack or add a language. A single recall figure across all types hides the one format that matters to your regulator, so report them separately and track them release over release.
Measure the other direction too. Over-masking has a cost: if the detector tags product names or street names as people, answer quality drops and engineers start asking for exceptions. Sample masked prompts, have someone who reads the language review them, and tune per entity type. The goal is a guardrail your product team trusts enough to leave switched on, because a guardrail that gets disabled for quality reasons protects nobody, and the DPIA should record the recall you measured, not the recall you hoped for.