Agent-readable docs index: /llms.txt. Full docs in one file: /llms-full.txt. Download /docs.zip to grep all markdown files locally.

PII types

KINDI's detection produces one of the entity types below for each detected span. Several detection layers run over your text (deterministic recognizers, transformer NER, your glossary); where two of them overlap, a fixed priority ranking decides which single type survives.
The Type values below are the detection entity types: exactly what /mask reports in spans[].type and the <MASKED_{TYPE}_…> token. /redact uses a slightly different public placeholder prefix for some of them (e.g. PERSONPERSON_NAME); see Placeholder shape.

Catalogue

There are 23 togglable entity types plus CUSTOM_TERM. The On by default column is what a new account starts with; see Which types are on by default.
TypeOn by defaultWhat it catchesLatin exampleArabic example
CUSTOM_TERMalwaysCaller-defined glossary terms (codenames, aliases, contract IDs)Project Falconمشروع الصقر
PERSONLatin + Arabic namesMohammed bin Salmanمحمد بن سلمان
NATIONAL_ID10-digit NID, leading 1, checksum-validated1012345672١٠١٢٣٤٥٦٧٢
RESIDENCE_PERMIT10-digit iqama, leading 2, checksum-validated2012345670
PHONE_NUMBERKSA mobile + international+966 50 123 4567٠٥٠١٢٣٤٥٦٧
EMAIL_ADDRESSRFC 5321salem@example.sa
DATE_TIMEDates, times, durations2026-05-27, Jan 1 1980٢٠٢٦/٠٥/٢٧
IBAN24-char Saudi IBAN, mod-97 validatedSA03 8000 0000 6080 1016 7519
CREDIT_CARD14–16 digit PANs, Luhn-validated, mada BIN table for network tagging4111 1111 1111 1111٤١١١ ١١١١ ١١١١ ١١١١
PASSPORTICAO MRZ formatA12345678
ADDRESSREDF + short-code addresses7821 King Fahd Rd٧٨٢١ طريق الملك فهد
ORGANIZATIONSaudi organizationsSaudi Aramcoأرامكو السعودية
LOCATIONCities, regions, countriesRiyadhالرياض
MRNHospital medical record numberH123456, MRN: 0012345
BUSINESS_IDCommercial registration (context-gated)CR 1010xxxxxxسجل تجاري ١٠١٠xxxxxx
TAX_IDZATCA zakat number, leading 3 (shape-only, no public check digit)3xxxxxxxxx
VEHICLE_PLATEStrict 17-letter Latin + Arabic plate alphabetsأ ب ج 1234أ ب ج ١٢٣٤
STUDENT_IDStudent ID (context-gated)Student ID 4400123456
INSURANCE_POLICYInsurance policy ID (context-gated)Policy POL-987654
MEDICAL_LICENSESaudi MOH / SCFHS practitioner license numbersMOH-123456, SCFHS-987654
API_KEYProvider-prefixed secrets (Stripe, AWS, GitHub, …). Prefix-anchored only; KINDI deliberately does not flag arbitrary high-entropy stringssk_live_…, AKIA…, ghp_…
MONETARY_AMOUNTAmounts in 18 currencies, symbol / ISO-code / word forms, incl. ArabicSAR 12,500, $4,300١٢٥٠٠ ريال
SSNUS-format social security numbers123-45-6789
IP_ADDRESSIPv4 + IPv6 literals203.0.113.42

Which types are on by default

Ten types are core and enabled for every new account. The other 13 are extended and OFF by default — a request will not detect them until you switch them on. That is the single most common cause of "KINDI missed my ORGANIZATION / MRN / MONETARY_AMOUNT".
  • Core (on): PERSON, NATIONAL_ID, RESIDENCE_PERMIT, PHONE_NUMBER, EMAIL_ADDRESS, DATE_TIME, IBAN, CREDIT_CARD, PASSPORT, ADDRESS
  • Extended (off): ORGANIZATION, LOCATION, MRN, BUSINESS_ID, TAX_ID, VEHICLE_PLATE, STUDENT_ID, INSURANCE_POLICY, MEDICAL_LICENSE, API_KEY, MONETARY_AMOUNT, SSN, IP_ADDRESS
Toggle them in the dashboard under Settings → Entities, or set the whole enabled list at once with PUT /me/entities. CUSTOM_TERM is never filtered by the toggles; a glossary term always masks.
Accounts created before entity toggles shipped are grandfathered to the full set, so an older key may detect types this page marks as off. Don't infer your configuration from behavior; read it back from the dashboard or the API.

Checksums: fabricated identifiers are ignored on purpose

For the identifier types that have a published check digit, KINDI verifies it and drops the span if it fails:
TypeCheck
NATIONAL_ID / RESIDENCE_PERMITSaudi position-doubling Luhn variant over all 10 digits
IBANISO 13616 mod-97
CREDIT_CARDStandard mod-10 Luhn (unconditional; this one is never relaxed)
The practical consequence: a made-up number of the right shape, like 1012345678 or a random 16-digit string, is not masked. This is deliberate; checksum gating is what keeps precision high on 10-digit numbers that are really order references, timestamps, or part numbers. If you're building a test fixture, generate checksum-valid values (or use the examples in the table above) or your test will look like a detection miss. TAX_ID has no authoritative public check digit and stays shape-only.

Unicode anti-evasion

Before any detection runs, KINDI normalizes the input: it deletes Unicode format characters (Cf — zero-width spaces and joiners, bidi embeds, isolates and marks, soft hyphen, BOM, Arabic format marks) and invisible variation selectors, and folds fullwidth digits (123) to ASCII. Without this, a single zero-width space pasted inside an ID from a PDF or an RTL document defeats every digit pattern and the value flows through unmasked. Offsets in spans are mapped back to your original text, so this is invisible to you.

Placeholder shape depends on the endpoint

EndpointToken shapeExampleNotes
POST /api/v1/mask<MASKED_{TYPE}_{8-hex}><MASKED_PERSON_a1b2c3d4>{TYPE} is the detection entity type from the catalogue above. One token is minted per distinct (type, surface text) pair and reused for every repeat occurrence — the same name appearing three times yields the same token three times, and one entry in the mappings object. Two different people both named in the text get two different tokens. The encrypted envelope maps each token back to its original.
POST /api/v1/redact{PREFIX}_{NN}PERSON_NAME_01{PREFIX} is the public placeholder prefix (see mapping below). Enumerated per type, starting at _01. Repeated values within a single request share the same number; numbering restarts at _01 on the next request. No envelope, no recovery.

Detection type → redact placeholder

/mask keeps the raw detection type; /redact renames a handful of types to a friendlier public placeholder. Most types are identical across both endpoints; only these differ:
Detection type (/mask)Redact placeholder (/redact)
PERSONPERSON_NAME
PHONE_NUMBERPHONE
EMAIL_ADDRESSEMAIL
DATE_TIMEDATE
ORGANIZATIONORG
CREDIT_CARDCARD
IP_ADDRESSIP
MONETARY_AMOUNTAMOUNT
Every other type (CUSTOM_TERM, NATIONAL_ID, RESIDENCE_PERMIT, PASSPORT, IBAN, BUSINESS_ID, TAX_ID, MRN, ADDRESS, LOCATION, VEHICLE_PLATE, INSURANCE_POLICY, STUDENT_ID, MEDICAL_LICENSE, API_KEY, SSN) is spelled the same in both.
The regex to match each:
mask → /<MASKED_([A-Z_]+)_([a-f0-9]{8})>/g redact → /([A-Z_]+)_(\d{2,})/g
Do not apply the redact regex to mask output, or vice versa. They look superficially similar but are not interchangeable; the mask regex won't match a redact placeholder and the redact regex will greedily over-match across mask tokens.

Priority order (deduplication)

When two detections overlap, KINDI keeps the one with the higher priority score and discards the other. Each type carries a fixed numeric rank:
RankTypes
110CUSTOM_TERM — your glossary terms beat every recognizer and the NER, and bypass the confidence filters
100NATIONAL_ID
95RESIDENCE_PERMIT, IBAN
90PASSPORT, PERSON
85ADDRESS
80EMAIL_ADDRESS, TAX_ID
75VEHICLE_PLATE
70PHONE_NUMBER
65BUSINESS_ID
60MEDICAL_LICENSE, INSURANCE_POLICY, STUDENT_ID, API_KEY
55MRN
50CREDIT_CARD, SSN
35MONETARY_AMOUNT
30DATE_TIME
20LOCATION
10ORGANIZATION
5IP_ADDRESS
Ties between two types at the same rank fall through to the recognizer's own confidence score. (The retired legacy alias spans mirrored their canonical type's rank while dual-emit was active.)
Higher-priority entities suppress lower-priority overlaps on the same span. This is intentional: a 10-digit number that is also a valid NATIONAL_ID should mask as such, not as a generic PHONE_NUMBER, and a date inside an address shouldn't split the address in two.

What KINDI does NOT detect (yet)

If you need these, raise an issue; they're on the roadmap, not in the current pipeline:
  • Free-form medical diagnosis codes (ICD-10, SNOMED)
  • Bank account numbers outside the IBAN format
  • Biometric identifiers and device/advertising IDs
  • Arbitrary high-entropy secrets with no recognizable provider prefix (use a glossary term instead)