Skip to content

Legal Holds API

The legal holds API sets, lists, and releases legal holds: named, audited instructions that block deletion of redaction evidence until the hold is released.

Admin cross-user access: by default each endpoint operates on the calling user's own holds. An admin may target another user by adding an owner=<username> query parameter to any endpoint (set, list, get, release). A non-admin that names another user as owner, or an owner that does not exist, receives 404 Not Found. Cross-user access is disabled by default; enable it with ADMIN_CROSS_USER_ACCESS_ENABLED=true (see Settings). While disabled, naming another user as owner also returns 404 Not Found. A deactivated user may be named as owner: deactivation keeps their data, and an admin reaches it as for an active user.

All endpoints require authentication with a Bearer token. See API Keys.

Set a Hold

Method Endpoint Description
POST /api/holds Create a named hold that blocks deletion of evidence.

Request Body

Field Required Description
reference Yes Hold identifier, unique for the calling user (for example LIT-2026-001). Philter does not interpret it, but it is how the hold is read and released, in a request path, so it cannot contain /, \, ;, %, or control characters, and cannot be . or ... Other text, including spaces and periods, is allowed.
scopeType Yes document_chain to protect one document's ledger chain, or user to protect all of the hold owner's ledger evidence. The owner is the caller, or the user named by owner.
scopeValue For document_chain For document_chain, the document id. For user, optional: the hold already covers everything its owner holds. If given, it must be the owner's username, or the request is refused with 400 Bad Request. Philter stores and returns the owner's username for a user hold either way.
reason No Free-text description of why the hold was set.

Query Parameters

  • owner (optional, admin only) - Username of the user whose evidence the hold protects.

Responses

  • 201 Created - The hold was set and is now active. The body is the hold.
  • 400 Bad Request - A required field is missing, scopeType is not recognized, or the reference breaks the rule above. The body carries a message naming the problem.
  • 404 Not Found - The owner does not exist or may not be reached. The reason is not_found.
  • 409 Conflict - The hold was not set. The body carries a message and a reason:
    • hold_exists - A hold with this reference already exists for the user.
    • operation_in_progress - Another evidence or hold operation for the user is active, or was interrupted and requires recovery. Retry once it finishes.

Example request:

curl -k -X POST -H "Authorization: Bearer <token>" -H "Content-Type: application/json" \
  "https://localhost:8080/api/holds" \
  -d '{"reference":"LIT-2026-001","scopeType":"document_chain","scopeValue":"doc-abc123","reason":"Preserve pending resolution"}'

Hold object

Set, list, and get return holds with these fields:

Field Description
reference The hold identifier.
scopeType document_chain or user.
scopeValue The document id, or for a user hold the owner's username.
reason The reason given when the hold was set, or null.
setAt When the hold was set (ISO 8601, UTC).
owner The owner's username. Present only in a listing with all_users=true.
{
  "reference": "LIT-2026-001",
  "scopeType": "document_chain",
  "scopeValue": "doc-abc123",
  "reason": "Preserve pending resolution",
  "setAt": "2026-06-08T14:11:33.000Z"
}

List Holds

Method Endpoint Description
GET /api/holds List active holds, most recently set first.

Query Parameters

  • owner (optional, admin only) - Username of another user whose holds to list.
  • offset (optional, default 0) - Number of holds to skip.
  • limit (optional, default 25, max 100) - Maximum number of holds to return.
  • q (optional) - Only holds whose reference contains q, ignoring case.
  • sort (optional, default set) - set, newest first by default, or reference. With order (asc or desc); see Listings.
  • all_users (optional, default false) - List every user's holds instead of the caller's; each hold then also has an owner field with its owner's username. Requires an administrator and ADMIN_CROSS_USER_ACCESS_ENABLED=true (disabled by default), as owner does; otherwise it returns 404 Not Found. Cannot be combined with owner.

Responses

  • 200 OK - The holds on the requested page in holds, each a hold object, and total, how many there are across every page.
  • 400 Bad Request - Both owner and all_users were given, or sort or order is not one this listing accepts.

Example request:

curl -k -H "Authorization: Bearer <token>" "https://localhost:8080/api/holds?offset=0&limit=25"

Get a Hold

Method Endpoint Description
GET /api/holds/{reference} Retrieve a single hold by its reference.

Query Parameters

  • owner (optional, admin only) - Username of another user whose hold to retrieve.

Responses

  • 200 OK - The hold details.
  • 404 Not Found - No hold with that reference exists for the user.

Example request:

curl -k -H "Authorization: Bearer <token>" "https://localhost:8080/api/holds/LIT-2026-001"

Release a Hold

Method Endpoint Description
DELETE /api/holds/{reference} Release (delete) the named hold.

Once released, evidence previously covered by this hold becomes eligible for deletion if no other hold remains. Only the user who set the hold, or an administrator, can release it, so a user cannot release a hold an administrator placed on their evidence. Releasing a hold does not delete any ledger data. Every release is recorded in the audit log as legal_hold_released.

Query Parameters

  • owner (optional, admin only) - Username of another user whose hold to release.

Responses

  • 204 No Content - The hold was released.
  • 403 Forbidden - The caller neither set the hold nor is an administrator. The reason is admin_required.
  • 404 Not Found - No hold with that reference exists for the user. An owner that does not exist or may not be reached gets the same not_found body.
  • 409 Conflict - The hold was not released because another evidence or hold operation for the user is active or requires recovery. The body carries a message and the reason operation_in_progress.

Example request:

curl -k -X DELETE -H "Authorization: Bearer <token>" "https://localhost:8080/api/holds/LIT-2026-001"

Example 409 response:

{
  "message": "An evidence or legal-hold operation is active or requires recovery for this owner.",
  "reason": "operation_in_progress"
}

Blocked Deletions

While any hold covers the target evidence, deletion endpoints return 423 Locked and list the blocking hold references in the response body. The attempt is recorded in the audit log as legal_hold_blocked_deletion. See Legal Holds for which operations are checked.