Redact a prompt and restore the reply /
Send the model tokens instead of people, then put the real values back into what it writes.
The problem
You want a model to draft a reply to a customer email. The email has the customer’s name, address and IBAN in it. Redacting it the ordinary way solves the privacy problem and ruins the output: the model writes back to [PERSON_NAME] about invoice [BANK_ACCOUNT], and you have no way to turn that back into a sendable message.
tokenize=True is for exactly this shape of work. Each value is replaced by a token that names its type and nothing else, and the result carries the mapping that turns the tokens back into values.
The round trip
import euredact
prompt = (
"Write a friendly reply to Joren at joren.janssens@euredact.be "
"or call +32 475 12 34 56 about the unpaid invoice."
)
result = euredact.redact(prompt, countries=["BE"], tokenize=True)
print(result.redacted_text)
# "Write a friendly reply to Joren at EMAIL_K7Q2 or call PHONE_P4RT
# about the unpaid invoice."
reply = call_your_llm(result.redacted_text) # sees tokens, never the values
print(euredact.restore(reply, result.tokens))
# the model's reply, with the real address and number back in itThe Node.js SDK is the same three calls, with the options in camelCase:
import { redact, restore } from "euredact";
const result = redact(prompt, { countries: ["BE"], tokenize: true });
const reply = await callYourLlm(result.redactedText);
console.log(restore(reply, result.tokens));What the model can and cannot learn
Within one call the same value gets the same token, so a prompt that names someone three times still reads as one person and the model writes coherent prose about them.
Across calls it gets a different token. Nothing is retained on the instance, result.tokens is the only copy that exists, and two tokenized documents never reveal that they share a value. That is the point: a leaked transcript of your prompts is a set of tokens that cannot be joined back together.
It also means the mapping is yours to hold for exactly as long as you need it, and no longer. Keep it in memory for the length of the request if you can. If you must persist it — a queued job, a human-review step — it is personal data and belongs wherever the rest of your personal data lives, under the same retention rule.
Choosing between tokenize and referential integrity
Both give you stable labels instead of [EMAIL], so they look interchangeable. Their retention models are opposites, and they cannot be combined.
tokenize=True is per call and reversible. Reach for it when the text has to come back: prompts, drafts, anything a person will read as if the redaction never happened.
referential_integrity=True persists labels on the instance across calls, so the same customer is EMAIL_1 in every document you process. Reach for it when an analyst needs to see that two documents concern one person — and accept that the instance is now holding a mapping that says so.
Three things that bite
A model will mangle your tokens. It writes EMAIL_P4RTs or wraps one in punctuation. restore() replaces every occurrence including those, because leaving a token in a message you are about to send is the worse failure of the two.
Tokens are chosen to survive a second pass. They avoid any token-shaped string already in the document, so a model’s reply to a tokenized prompt can itself be redacted without restore() later putting the wrong value back.
Batches tokenize per text. The batch and iterator variants give one mapping per text, not one across the batch. Keep each mapping with its own document.
Names, employers and diagnoses
The rules tier matches things with a shape — addresses, IBANs, phone numbers. A person’s name has no shape, so tokenizing it needs the cloud tier, where mode="cloud" returns PERSON_NAME_W3NB alongside the rest. Tokenization works there the same way: the SDK rebuilds the text from the spans the service returns. The cloud tier is in closed alpha.