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.123POST /api/v1/redact Authorization: Bearer mk_live_... Content-Type: application/json
1{ "text": "Call John Doe at +966 50 123 4567" }
12345678{ "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 }
| Field | Type | Required | Description |
text | string | yes | The text to redact. Min length 1. |
use_judge | boolean | — | Opt 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). |
glossary | string[] | — | Inline custom terms to also redact (as CUSTOM_TERM). |
glossary_id | string (UUID) | — | A persisted glossary to apply. Enabled glossaries auto-apply without this. |
transforms | object | — | Smart 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). |
| Field | Type | Description |
redacted_text | string | Text with each detected entity replaced by an enumerated placeholder (PERSON_NAME_01, PHONE_01, etc). Numbering restarts at _01 per request. |
pii_count | number | Count of distinct values replaced, not occurrences; a name appearing three times counts once. |
entity_counts | object | Per-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_count | number | Occurrences 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_id | string | Stable per-request id. |
processing_time_ms | integer | Server-side wall-clock for the request. |
judge_used | boolean | Whether 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.transforms, a date or address can be abstracted instead of fully
redacted; the output keeps enough shape for an LLM prompt to reason with.1234{ "text": "Patient DOB: 01/15/1985, seen at Al-Olaya District, Riyadh", "transforms": {"DATE_TIME": "generalize", "ADDRESS": "generalize"} }
123456{ "redacted_text": "Patient DOB: age 41, seen at Riyadh", "pii_count": 2, "entity_counts": {"DATE": 1, "ADDRESS": 1}, "generalized_count": 2 }
generalized_count tells you how many spans generalized.| Status | Description |
| 200 | Redaction succeeded. |
| 400 | Invalid request body, e.g. "unknown_judge_model". |
| 401 | Missing or invalid bearer key. |
| 402 | insufficient_tokens: top up at /dashboard/billing. |
| 403 | Bare 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. |
| 422 | Schema validation failed (wrong types, text empty). |
| 429 | Rate-limit exceeded; check Retry-After header. |
| 503 | masker_unavailable: the detection service is unreachable or overloaded. The request is refunded (best-effort); carries Retry-After on overload. |