Redactions API — PDF & image
This document describes HTTP request/response shapes used by the RedactStripe app for PDF and image redactions, aligned with:
- API:
apps/api/src/routes/redactions.js+apps/api/src/services/redactions/redaction.service.js - Web client:
apps/web/src/features/pdf-redaction/api/redactionsApi.js,apps/web/src/features/pdf-redaction/hooks/useRedactionState.js - Image redaction UI: Fabric canvas and toolbar exist; save/load of image redactions through this API is not wired in the web app yet (see Image redaction — current status).
Unless noted, paths are under the API base (e.g. VITE_API_URL), typically /api/redactions. Requests use session/JWT as enforced by requireSession on the API.
Shared: save redactions (batch write)
POST /api/redactions/:docId
Path parameter
| Name | Type | Description |
|---|---|---|
docId | integer | Numeric batch document id (not file-… style). |
Request body (JSON)
{
"redactions": [ /* see PDF vs image item shapes below */ ],
"versionInfo": {
"versionId": "string | null"
}
}
| Field | Type | Required | Description |
|---|---|---|---|
redactions | array | Yes | Non-empty. All items in one request must share the same FOIA layer (B1–B7). |
versionInfo | object | No | Optimistic concurrency: versionId should match the server’s latest version for that layer, or omit/null on first save. |
Success response (201)
{
"success": true,
"message": "Saved N redaction(s)",
"count": 0,
"version_id": "string",
"version": 0,
"layer": "B7"
}
The web PDF flow reads version_id and stores it per layer for the next save (useRedactionState).
Common error responses
| Status | Meaning (examples) |
|---|---|
400 | Missing/invalid redactions, mixed layers, invalid coordinates, invalid layer/classification. |
404 | Document not found or access denied. |
409 | Version conflict (versionInfo.versionId does not match currentVersion in body). |
423 | Layer locked by another user (locked_by, etc. in body). |
PDF redaction items (implemented in web + API)
The web app builds each saved item with toApiRedaction in useRedactionState.js (flat PDF bounds, not a nested rect object).
Request: one PDF redaction object (inside redactions[])
| Field | Type | Required | Notes |
|---|---|---|---|
redaction_id | string (UUID) | Yes | Client-generated id; persisted as redaction_id. |
page_number | integer | Yes | ≥ 1. |
redaction_type | string | Yes | Client sends tool type; "full_page" is normalized to "rectangle" for the API payload. |
layer | string | Yes | FOIA layer, e.g. B1–B7. |
redaction_reason | string | null | No | |
classification_type | string | null | If layer B1 | Otherwise sent as null. |
pdf_x1, pdf_y1, pdf_x2, pdf_y2 | number | Yes | PDF coordinate space; finite; must form a box with positive width/height. |
pdf_polygon_points | array | For polygon/lasso/freehand | Server expects ≥ 3 points when polygon-like; client sends [] for rectangles. |
Example (rectangle)
{
"redaction_id": "550e8400-e29b-41d4-a716-446655440000",
"page_number": 1,
"redaction_type": "rectangle",
"layer": "B7",
"redaction_reason": null,
"classification_type": null,
"pdf_x1": 72,
"pdf_y1": 120,
"pdf_x2": 300,
"pdf_y2": 200,
"pdf_polygon_points": []
}
Web behavior (what actually gets POSTed)
- Saves run per layer group: multiple
POSTs if dirty redactions span multiple layers. - Each
POSTsends only redactions that are dirty or not yet persisted, excluding stages likepending/redaction_pending_reason(seehandleSaveRedactionsinuseRedactionState.js). - For PDFs with an existing version, the server may merge prior cumulative redactions for that version prefix before writing; the client still sends the layer batch built from
toApiRedaction.
GET /api/redactions/:docId
Query (optional)
layers— comma-separated FOIA layers, e.g.?layers=B7or?layers=B1,B7. Invalid layer names →400.
Success (200)
{
"success": true,
"redactions": [ /* stored box documents: PDF + image collections merged */ ],
"count": 0,
"allowedLayers": ["B1", "B2", "B3", "B4", "B5", "B6", "B7"],
"version_ids": ["string"]
}
Each element in redactions is a stored record (Mongo shape). The web app maps entries into client state via mapApiRedactionToClient (expects fields like redaction_id, page_number, redaction_type, pdf_x1…pdf_y2, pdf_polygon_points, layer, etc.).
GET /api/redactions/:docId/layer/:layer
Returns redactions for the latest version of that layer for the document. Body includes version_id for that snapshot.
Image redaction items (server contract; web save not implemented)
The API chooses PDF vs image validation based on the batch document type (isImageBatchDocument). Image saves use image_redaction_boxes and accept image pixel bounds (or x/y + width/height).
Request: one image redaction object (inside redactions[])
Either full bounds:
| Field | Type | Required |
|---|---|---|
image_x1, image_y1, image_x2, image_y2 | number | Yes (unless using x/y + size below) |
Or origin + size (server derives corners):
| Field | Type | Notes |
|---|---|---|
x, y | number | Top-left |
width / w, height / h | number | Used to compute image_x2 / image_y2 |
Also expected / supported (typical)
| Field | Type | Notes |
|---|---|---|
redaction_id | string | Required for upsert key with version_id. |
layer | string | B1–B7 |
redaction_type / type | string | Defaults to rectangle in storage. |
page_number | integer | Optional in builder; stored, defaults to 1 if omitted. |
redaction_reason, classification_type, polygon metadata, etc. | various | Same family of fields as PDF where applicable. |
Example (rectangle in image pixel space)
{
"redaction_id": "image-redaction-xxxxxxxx",
"layer": "B7",
"redaction_type": "rectangle",
"page_number": 1,
"image_x1": 10,
"image_y1": 20,
"image_x2": 110,
"image_y2": 80,
"redaction_reason": null,
"classification_type": null
}
Important: The current Fabric image redaction page does not yet serialize Fabric objects into this payload or call
POST /redactions/:docId. When implemented, coordinates should be in stable image space (e.g. natural image pixels), consistent with how the server validates and storesimage_x1…image_y2.
Update / delete single box
PUT /api/redactions/box/:redactionId
Body (single update — simplified path)
- Arbitrary JSON fields to merge onto the box, except disallowed keys (
_id,doc_id,created_at,created_bystripped server-side). - May include
version_idfor targeting (see service implementation).
Body (batch form)
{
"redactionList": [
{
"redaction_id": "pdf-redaction-… | image-redaction-…",
"version_id": "string",
"updates": { }
}
]
}
Response: 200 with per-item { redaction_id, version_id, success, message } for list form, or single-update result per service.
redaction_id prefix rules:
- PDF:
pdf-redaction-… - Image:
image-redaction-…
DELETE /api/redactions/box/:redactionId
Optional JSON body with redactionList: [{ "redaction_id", "version_id" }, …] for batch deletes; otherwise single-delete semantics per service.
Delete all redactions for a document
DELETE /api/redactions/:docId
Success (200)
{
"success": true,
"message": "Deleted redactions for document",
"deletedCount": 0
}
Restrictions: Non-admins may only clear if they are the batch owner (see service).
Redaction types catalog
GET /api/redactions/types
Returns the payload from getRedactionTypesPayload() (tool/metadata catalog for UI). Used by the web layer config (useRedactionLayerConfig / similar).
Image redaction — current status (web)
| Area | Status |
|---|---|
| Open image from dashboard | Uses authorized batch document download URL + fetch + Bearer → blob URL for Fabric (imageDocumentApi.js). |
| Draw rectangles | Fabric scene; viewport zoom/rotate applied to canvas (not individual object angles for view). |
| Save / load redactions | Not connected to POST/GET /redactions/:docId yet (ImageRedactionPage save handler is a stub). |
| Shared API module | image-redaction/api/imageRedactionsApi.js re-exports the same redactionsApi.js as PDF for future use. |
When image save is implemented, expect the same POST /api/redactions/:docId envelope (redactions + versionInfo), with each item satisfying validateImageItem on the server.
Quick reference — web client wrappers
| Method | Function | File |
|---|---|---|
| GET | loadRedactionsApi(docId, layers) | pdf-redaction/api/redactionsApi.js |
| DELETE all | deleteAllRedactionsApi(docId) | same |
| DELETE box | deleteRedactionBoxApi(redactionId, body?) | same |
| PUT box | updateRedactionBoxApi(redactionId, body) | same |
| POST save | apiRequest(\/redactions/${docId}\, { method: "POST", body }) | useRedactionState.js (not wrapped in redactionsApi.js) |
*Generated from repository behavior as of the docs authoring date; adjust if the API evolves.*