What is removed, and what is kept
Replaced with placeholders
Malaysian IC (old and new formats), MRN, passport, phone, names, emails,
account numbers.
Deliberately preserved
Ages, relative dates like “day 3 post-op”, vitals, dosages — all clinical
content.
The receipt
Every LLM-backed response carries aredaction_receipt:
object
What was found, by type — counts only, never the values.
boolean
Whether every placeholder was substituted back.
integer
How many were not. If a model altered one of our placeholders, the receipt says
so rather than silently handing you residue.
string
failed_closed on the 503 below — the request never left us.Fail-closed
There is no client-side flag that skips redaction for API keys. You cannot assert “this text is already de-identified” to bypass it.Two v1 limits, both refusals rather than silent degradation
Streaming while re-hydrating is refused
Streaming while re-hydrating is refused
Placeholders split across chunk boundaries, and models rewrite them
mid-stream, so a half-substituted answer is worse than a slower complete one.Send
redact_only=true to stream — nothing is substituted back, so there is
nothing to split.Tool and function calling is refused
Tool and function calling is refused
A tool call can carry context straight back out, which needs its own
exfiltration review. Refused with a 400 rather than dropped, so you never
believe your tools were honoured when they were not.

