Compliance Redaction, End to End
The workflow a data-protection request or a disclosure order actually demands: not "we drew
boxes" but a chain of artifacts proving what was found, what a human approved, what was
destroyed, and that it is gone. Every step emits evidence; store all of it together.
Cookbook conventions apply; the document rides as $FILE_ID from one
upload.
1Detect: what personal data is in this document?#
PII detection reads the whole document (OCR included) and returns each entity with its occurrences located on the page:
curl -s "$LMKIT_URL/lmkit/v1/detect-pii" \
-H "Authorization: Bearer $LMKIT_API_KEY" -H "Content-Type: application/json" \
-d '{
"input": "'$FILE_ID'",
"input_format": "FileIdentifier",
"custom_labels": ["Employee id"],
"guidance": "Company names are not personal data in this workflow."
}'
{
"overall_confidence": 0.93,
"entities": [
{ "label": "Person", "entity_type": "Person", "value": "Amelie Durand",
"confidence": 0.98,
"occurrences": [ { "page_index": 0, "start_offset": 141, "length": 13,
"bounding_box": { "left": 96.2, "top": 214.7, "width": 118.4, "height": 12.9 } } ] },
{ "label": "BankAccountNumber", "entity_type": "BankAccountNumber", "value": "FR7630006000011234567890189",
"confidence": 1.0,
"occurrences": [ { "page_index": 1, "start_offset": 88, "length": 27,
"bounding_box": { "left": 60.0, "top": 640.2, "width": 208.9, "height": 11.8 } } ] }
]
}
Omitting built_in_types considers every built-in type; custom_labels adds your
domain's identifiers per request, no training involved. label is the built-in type's name
(Person, EmailAddress, PhoneNumber, PostalAddress, Url, IpAddress, DateOfBirth,
SocialSecurityNumber, CreditCardNumber, BankAccountNumber, CompanyIdentifier, VatNumber, Other) or
your custom label.
Two detectors run by default ("detectors": ["llm", "patterns"]). The language model finds what only reading
understands (names, addresses); the deterministic detectors find what a checksum proves: IBANs (country length and
mod-97), payment cards (issuer range and Luhn), French social security numbers (NIR key, Corsica included), SIREN
and SIRET (Luhn, La Poste's rule), EU VAT numbers, e-mail addresses, phone numbers and IP addresses. A validated hit
replaces the model's copy of the same identifier and is reported with confidence 1.0, every occurrence located
on its page (OCR text of scans included). "detectors": ["patterns"] alone answers at once, with no model.
2Review: detection proposes, a person disposes#
The one human checkpoint, and it belongs here, BEFORE destruction. Render pages with the
bounding_box coordinates drawn (this is exactly what the boxes are for) and let the
reviewer confirm, drop false positives, and add anything missed. The output of review is
two lists your code assembles: confirmed strings and, for visual-only targets like a
signature, confirmed page areas.
3Destroy: redaction that removes, not covers#
Feed both lists to redaction in one call. Content under the selectors is DELETED from the file: glyphs, pixels, paths, annotations.
curl -s "$LMKIT_URL/lmkit/v1/pdf-redact" \
-H "Authorization: Bearer $LMKIT_API_KEY" -H "Content-Type: application/json" \
-d '{
"input": "'$FILE_ID'",
"input_format": "FileIdentifier",
"search_terms": ["Amelie Durand", "FR7630006000011234567890189"],
"areas": [ { "page_index": 2, "left": 380.0, "top": 700.0, "width": 160.0, "height": 60.0,
"label": "Signature" } ],
"redact_images": true,
"redact_vector_graphics": true,
"remove_annotations": true,
"recurse_into_forms": true,
"draw_fill_boxes": true,
"sanitize": true,
"verify": true
}'
{
"file_id": "b7c3...",
"content_removed": true,
"pages_processed": 3,
"search_matches": 5,
"removed_glyphs": 214, "removed_text_objects": 3, "edited_text_objects": 4,
"edited_images": 1, "removed_images": 0, "removed_paths": 2,
"edited_forms": 1, "removed_annotations": 1,
"sanitized": { "metadata": true, "attachments": 0, "javascript": 0, "outlines": true,
"piece_info": 0, "xfa": false, "thumbnails": 0, "structure_text": 0 },
"verified": true, "residual_characters": 0,
"labels_drawn": 1, "labels_omitted": 0
}
The counters are evidence artifact number one: what the operation did, exactly. Note the
scope toggles are explicit policy: this call scrubs images and vectors too, and paints the
classic boxes; an invisible-trace workflow sets draw_fill_boxes to false and nothing else
changes. sanitize stripped the metadata and bookmarks that repeated the name, and verified says the output was
re-read under every area before it was stored: had any character survived, the call would have answered 422
verification_failed and stored nothing.
4Prove: search the output for what must be gone#
Absence is testable (Search, Layout, and Inspection). Run each redacted string against the OUTPUT file, in fuzzy mode so OCR-flavored near-misses cannot hide:
curl -s "$LMKIT_URL/lmkit/v1/pdf-search" \
-H "Authorization: Bearer $LMKIT_API_KEY" -H "Content-Type: application/json" \
-d '{
"input": "b7c3...",
"input_format": "FileIdentifier",
"query": "Amelie Durand",
"search_mode": "Fuzzy",
"max_edit_distance": 2
}'
{ "total_matches": 0, "scanned_pages": 3, "limited_by_max_results": false, "matches": [] }
Zero matches, full scan, no truncation: evidence artifact number two, re-runnable by any auditor against the artifact itself.
5Archive: conformance with a report#
Redaction rewrote the file, so the archival copy is minted now: convert to PDF/A (the response embeds an independent validation of its own output) and store the artifact with its reports.
curl -s "$LMKIT_URL/lmkit/v1/pdf-to-pdfa" \
-H "Authorization: Bearer $LMKIT_API_KEY" -H "Content-Type: application/json" \
-d '{ "input": "b7c3...", "input_format": "FileIdentifier", "level": "PdfA2b" }'
{
"file_id": "e19a...", "level": "PdfA2b", "conforms": true,
"fixes_applied": ["FontsEmbedded", "MiscKeys"],
"validation": { "verdict": "compliant", "flavor_validated": "PdfA2b",
"rules_evaluated": 73, "rules_failed": 0, "findings": [] }
}
Download e19a... as the record of note. The compliance folder now holds four artifacts:
the detection report, the reviewer's confirmation, the redaction counters plus the
zero-match search, and the conversion-with-validation report. That chain answers the audit
before it is asked.
6Production notes#
- Never skip step 2. Detection at high confidence is still a proposal; the workflow's legal weight comes from a person approving the exact list that step 3 executes.
- Sweep at scale, redact per document. For a folder-level obligation, run detection
across the corpus first and queue only documents with findings; detection is
job-friendly with
Prefer: respond-async(the contract). - Metadata is part of disclosure.
sanitize(on by default) strips titles, authors, keywords, XMP, attachments, scripts, bookmarks and thumbnails; checkpdf-infoon the final artifact all the same.