Agent-readable docs index: /llms.txt. Full docs in one file: /llms-full.txt. Download /docs.zip to grep all markdown files locally.
Replace each detected entity with an enumerated placeholder (PERSON_NAME_01, PHONE_01, etc). The original spans are NOT recoverable; there's no envelope returned. Use this when you only need to log, display, or analyze masked text.
POST /api/v1/redact Authorization: Bearer mk_live_... Content-Type: application/json

Example

Request:
{ "text": "Call John Doe at +966 50 123 4567" }
Response:
{ "redacted_text": "Call PERSON_NAME_01 at PHONE_01", "pii_count": 2, "entity_counts": {"PERSON_NAME": 1, "PHONE": 1}, "request_id": "redact_9f21ac0e", "processing_time_ms": 38, "judge_used": false }

Request

FieldTypeRequiredDescription
textstringyesThe text to redact. Min length 1.
use_judgebooleanOpt into the LLM judge for this request (subject to the per-key judge_allowed gate). Also settable via the X-Use-Judge: true header (header wins).
glossarystring[]Inline custom terms to also redact (as CUSTOM_TERM).
glossary_idstring (UUID)A persisted glossary to apply. Enabled glossaries auto-apply without this.
transformsobjectSmart generalization: a {type: "generalize"} map. Only DATE_TIME and ADDRESS keys are accepted, only "generalize" as the value; anything else is a 422. An opted-in span is replaced with an abstracted value instead of a placeholder; when generalization fails, the normal placeholder is used (fail-closed).

Response

FieldTypeDescription
redacted_textstringText with each detected entity replaced by an enumerated placeholder (PERSON_NAME_01, PHONE_01, etc). Numbering restarts at _01 per request.
pii_countnumberCount of distinct values replaced, not occurrences; a name appearing three times counts once.
entity_countsobjectPer-placeholder-prefix tally of distinct values, e.g. {"PERSON_NAME":2,"PHONE":1}. Keys are redact prefixes (PERSON_NAME, PHONE, ORG, CARD, …), not detection type names; see PII types.
generalized_countnumberOccurrences replaced with a generalized value instead of a placeholder. 0 when the request sent no transforms or every candidate failed closed. Generalized spans still count in pii_count and entity_counts.
request_idstringStable per-request id.
processing_time_msintegerServer-side wall-clock for the request.
judge_usedbooleanWhether the LLM judge actually ran.
/redact placeholders are canonical-name only, so entity_counts never double-counts a Saudi entity. This was true even while /mask still emitted the now-retired legacy alias spans.

Smart generalization

With transforms, a date or address can be abstracted instead of fully redacted; the output keeps enough shape for an LLM prompt to reason with.
{ "text": "Patient DOB: 01/15/1985, seen at Al-Olaya District, Riyadh", "transforms": {"DATE_TIME": "generalize", "ADDRESS": "generalize"} }
{ "redacted_text": "Patient DOB: age 41, seen at Riyadh", "pii_count": 2, "entity_counts": {"DATE": 1, "ADDRESS": 1}, "generalized_count": 2 }
Rules, in brief (the full behavior table is in the generalization guide):
  • A date near a date-of-birth cue becomes an age; other dates reduce to month + year, or a bare year for numeric forms.
  • An address collapses to its city or region name; street, building, and postal numbers never survive.
  • Fail-closed: a span the generalizer cannot handle (Hijri dates, clock times, an address with no recognizable city) gets the normal enumerated placeholder. generalized_count tells you how many spans generalized.
Generalization deliberately keeps more information than a placeholder: an age plus a city can narrow someone's identity in a small population. It weakens the privacy guarantee in exchange for prompt utility; opt in per request and only where that trade-off is acceptable. See the guide for the full statement.

Status codes

StatusDescription
200Redaction succeeded.
400Invalid request body, e.g. "unknown_judge_model".
401Missing or invalid bearer key.
402insufficient_tokens: top up at /dashboard/billing.
403Bare 403, no JSON body — the request reached api.kindi.me from outside Saudi Arabia. Not an auth failure; see Error codes.
404"Glossary not found" — the glossary_id you sent doesn't exist or isn't yours. Checked before billing, so it isn't charged.
422Schema validation failed (wrong types, text empty).
429Rate-limit exceeded; check Retry-After header.
503masker_unavailable: the detection service is unreachable or overloaded. The request is refunded (best-effort); carries Retry-After on overload.