Skip to content

pii · Guide

Redaction & tokens ​

Four actions share one detection pipeline: report-only (detect), remove-and-tag (redact), partial-mask (mask), and reversible substitution with a vault (tokenizeText/detokenizeText).

The four actions ​

FunctionoutputTextUse it for
detectUnchanged — identical to the inputReport findings only, without touching the text.
redactEvery finding replaced with [REDACTED_<CATEGORY>]Logging or storage where the shape of the removed data does not need to be recoverable at all.
maskEvery finding replaced with a partial mask — see belowHuman-readable output that still hints at the original (j***@gmail.com, a card ending 1111).
tokenizeTextEvery finding replaced with a [PREFIX_N] token, with a vault mapping tokens back to real valuesReversible redaction: pass the same output text and vault to detokenizeText to restore the original.

Every function has an Async counterpart (detectAsync, redactAsync, maskAsync, tokenizeAsync) that is the only way to reach the dictionary and NER tiers — the synchronous versions always run the pattern tier only, regardless of detection.mode.

Mask strategies ​

Each category's mask field picks one of four strategies, applied when the action is mask (or detect/tokenize, which use the same partial mask for display; redact always uses the fixed [REDACTED_<CATEGORY>] tag regardless of the category's own mask kind):

KindBehaviourExample categories
fixedA constant replacement string.birthDate → [DOB], password → [PASSWORD], person → [PERSON]
keep-edgesKeeps head characters at the start and tail at the end, fills the middle with *.iban (4 head / 4 tail), apiKey (4/4), tr_plaka (2/0)
keep-lastKeeps the last tail characters, fills the rest with *.phone/tr_phone/de_phone (4), creditCard (4), tc_kimlik/tr_vkn (4)
keep-domainEmail-specific: keeps the domain, masks the local part except its first character.email (ahmet@example.com → a****@example.com)

A value shorter than the characters a strategy would keep is masked entirely (fill.repeat(value.length)), so a strategy never accidentally reveals more than it hides on a short match.

The finding shape ​

Every entry in findings (JS) / "findings" (Python) carries:

FieldTypeMeaning
categorystringThe category id (email, tc_kimlik, …).
valuestringThe exact matched substring.
start / endnumberCharacter offsets into the input — UTF-16 code units in JS, Unicode code points in Python. See the offset note below.
labelstringHuman-readable category label, localised via locale.
messagestringA formatted sentence, e.g. "Email address detected".
severity'low' | 'medium' | 'high'From the category definition.
confidencenumberThe fused score after context boosting (and, in pattern+ner-verified, judge fusion).
actionstringThe action that produced this finding ('detect', 'redact', 'mask', 'tokenize').
blockbooleantrue only when action === 'block'.
replacementstringWhat this finding was replaced with in outputText (or would be).
detector'pattern' | 'dictionary'Which tier raised the candidate.
evidencestring[]Human-readable reasons — 'checksum/format validated', 'context word "tel"', 'spelled digits', and so on.

The top-level result also carries inputLength, outputText, hasBlocking (true if any finding has block: true), action, languages, and — only when non-empty — degraded (a list of reasons the NER tier fell back, such as a timeout or a missing runtime).

Overlapping findings ​

When two candidates overlap, the higher-severity one wins; ties are broken by the longer span. Findings are otherwise ordered by their start offset, longer-first among equal starts, before overlap resolution runs — so a phone finding losing to a birthDate finding on the same digits (a dotted date that also looked phone-shaped) reflects an actual measured false-positive fix, not an arbitrary priority order. See Categories for the validator-level guards (validatePhone's date and IP exclusions, for example) that prevent most of these collisions before overlap resolution is even needed.

Reversible tokenization ​

js
const { outputText, vault } = pii.tokenizeText('Kart no: 4111111111111111');
// outputText: 'Kart no: [CREDITCARD_1]'
// vault: { '[CREDITCARD_1]': { value: '4111111111111111', category: 'creditCard' } }

pii.detokenizeText(outputText, vault); // { outputText: 'Kart no: 4111111111111111' }

The same (category, value) pair always maps to the same token within one call, so a value repeated in the input is not assigned two different tokens. Token prefixes are derived from the category id (uppercased, non-alphanumerics collapsed to _), numbered per prefix starting at 1. detokenizeText replaces the longest tokens first, so one token string that happens to be a prefix of another never gets partially substituted.

The vault is caller-managed

tokenizeText does not persist the vault anywhere — you own storing it (a database row, a session, an encrypted blob) and matching it back up with the tokenized text later. Losing the vault means the original values are unrecoverable from the tokenized text alone.

Where to go next ​

Studio · Pulse · Console · Agent SDK and more — the Cognipeer documentation hub