Redaction
POST /lmkit/v1/pdf-redact permanently DELETES content instead of drawing a black box over
it. Text glyphs are removed from the content streams, image pixels are scrubbed and
re-encoded, vector graphics are trimmed, intersecting annotations are deleted, and content
nested inside shared Form XObjects is processed per instance, so redacting one occurrence
never silently blanks another page reusing the same resource. The removed content cannot be
recovered from the output by text extraction, raw stream inspection, or rendering. That is
the difference between redaction and decoration, and it is not pedantry: cover-up
"redaction" keeps leaking real documents, which is why the
NSA's redaction guidance exists.
1Three ways to say what goes#
Selectors combine freely in one call:
| Selector | Removes |
|---|---|
search_terms |
Every occurrence of each string; case_sensitive and whole_word tune the match. With "search_mode": "Regex" each term is a .NET regular expression (2-second timeout per page, 400 regex_timeout past it). Terms also find the words the page hides (under a clip, under a later fill): they are in the file. |
areas |
Explicit page regions, in points: the selector for "everything in the signature block", coordinates from layout analysis or a reviewer's UI. |
apply_existing_annotations |
Content under the document's own pre-existing redaction marks, applied FOR REAL: the workflow where reviewers mark in their PDF tool and the server executes. |
An encrypted source opens with password; the output is written UNENCRYPTED, readable without it, and carries no
trace of what was removed. Each explicit area may carry a label (60 characters at most, for example a legal
basis such as "Art. 9 GDPR"), drawn inside its fill box (section 3).
The boxes pdf-search returns per match (boxes, with page_index) are in the
frame areas take: points, top-left origin of the unrotated page. A reviewer UI can show the matches, let a
person pick, and send the picked boxes back as areas: they burn exactly what a search_terms redaction of
the same matches burns.
2Scope: what kinds of content are in play#
Redaction's reach is explicit, not implied. Four toggles say which content classes a selector may destroy, so a text-only policy cannot accidentally scrub a diagram, and a scorched-earth policy can:
redact_images: pixels under the target regions are cleared and the images re-encoded.redact_vector_graphics: paths and shapes intersecting the regions are trimmed away.remove_annotations: annotations intersecting the regions are deleted with the content.recurse_into_forms: content inside Form XObjects (shared, reusable content blocks) is processed per instance rather than skipped.
Surviving content is untouched: removal is glyph-granular and pixel-granular, so the sentence keeps every word you did not target.
3Marks are policy, not protection#
draw_fill_boxes optionally paints a box (in fill_color) where content was destroyed.
The box is PURELY visual: the content is equally gone without it. Some workflows want the
classic black bar as a reading signal ("something was here"); others, like anonymized
datasets, want no visual trace at all. Both are one flag, and neither changes what an
attacker can recover, which is nothing.
An area's label is drawn centered in its box, on one line, in white on a dark fill and black on a light one
(label_color overrides), shrunk to fit and left out when the box cannot hold it legibly (labels_drawn,
labels_omitted). It is drawn AFTER the burn as vector outlines: it adds no text to the document, so it never
shows up in search, and it covers nothing outside its own box. No fill box, no label.
3b. What page redaction does not reach: sanitize#
A document repeats its content in places that are not on any page. With sanitize (default true) the call
strips them after the burn and reports it in sanitized:
| Place | Reported as |
|---|---|
| Document information (title, author, subject, keywords, creator, producer, dates, custom entries) and the XMP metadata of the document and its pages | metadata (bool) |
| Embedded files and file attachment annotations | attachments (count) |
| Document scripts, a JavaScript open action, JavaScript actions of pages, annotations and form fields | javascript (count) |
| The outline (bookmarks): titles often repeat a name | outlines (bool) |
| /PieceInfo private application data | piece_info (count) |
| XFA form data (its own copy of every field value) | xfa (bool) |
| Page thumbnails (pictures of the page BEFORE redaction) | thumbnails (count) |
| Alternate and replacement text of the structure tree (/Alt, /ActualText, /E, /T); the tags stay | structure_text (count) |
Redaction also never lets a removed annotation come back through another reference: a form field under an area leaves the field tree with its value, and an annotation the structure tree still points at is emptied, not left whole.
3c. Signed documents#
Redaction rewrites the file, so every digital signature of the output is invalid by construction. A signed input
is refused with 422 signed_document unless allow_signature_invalidation is true, the same consent
the watermark routes ask for.
4The response is forensic, and verification is one call#
The response returns the redacted document as a file id plus counters that make the
operation auditable: content_removed and pages_processed at the top,
search_matches for how many occurrences the terms hit, and the removal ledger:
removed_glyphs, removed_text_objects, edited_text_objects, edited_images,
removed_images, removed_paths, edited_forms, removed_annotations. A compliance
record can state precisely what the operation did.
Each call proves its own work before it answers. With verify (default true) the output is re-opened and
every character of its text layer, invisible OCR text included, is read under every area (explicit areas and
search matches alike). A character still there fails the call with 422 verification_failed, the message naming
the page, and NO file is stored: a redaction that cannot prove its areas are empty never ships. A stored output
answers "verified": true and "residual_characters": 0. The classic way to hit it is content the call was
told not to reach, text inside a Form XObject with recurse_into_forms false.
Then PROVE it again, independently: run pdf-search for the redacted terms against the
output file id. Zero matches on a search that includes fuzzy mode is the verification a
reviewer can rerun at any time, against the artifact itself rather than a log entry. If the
document must remain archival, note that redaction is a rewrite:
re-validate PDF/A conformance afterward.
5The compliance pipeline#
The high-leverage composition, out of endpoints in this library:
- PII detection over the document returns what it found and where.
- A human reviews the findings: detection proposes, a person disposes.
- The confirmed occurrences feed
areas(theirbounding_box), or the confirmed strings feedsearch_terms; area selections cover anything visual. pdf-searchon the output proves absence; the counters and the search report are the evidence trail.
Detection, review, destruction, proof: two endpoint calls and one human checkpoint around the decision that actually needs one.
6Stated plainly#
- Redaction here means the content is GONE, at glyph, pixel, path, and annotation granularity, with shared resources handled per instance.
- Selectors, scope toggles, and visual marks are separate decisions, so the call states policy exactly; the counters state what happened exactly.
- What is not on a page (metadata, attachments, scripts, bookmarks, thumbnails, structure text) goes too.
- Every output is verified before it is stored, and absence stays verifiable by searching the output, which turns "trust us" into "check it".