Skip to content

API

Philter has no built-in user interface: everything, including administration, is done through its API. A user interface signs people in with their own username and password through the Sign-in API and uses a session key for each person, as Philter UI, in development, is being built to; see Sign-in Security.

Philter's API has the following sections:

  • Redaction API - Submit text or PDFs for redaction. In Philter 4.0, PDF requests are asynchronous by default; the redacted bytes are downloaded via the Documents API.
  • Documents API - List, poll, download, and delete asynchronous PDF redactions.
  • Webhooks - Receive signed HTTP notifications when an asynchronous redaction completes or fails.
  • Policies API - Create, modify, and delete policies.
  • Custom Lists API - Create, modify, and delete custom lists. Custom lists are referenced by policies to identify terms to redact.
  • Always/Never Redact Lists API - Get, replace (POST), and append to (PUT) the account's always-redact and never-redact lists, applied across all of your policies.
  • Contexts API - Create, update, and delete contexts and inspect their entries. Contexts maintain referential integrity across documents.
  • Redaction Ledger API - List, read, verify, export, and delete redaction ledger chains.
  • Legal Holds API - Set, list, and release legal holds that block deletion of evidence.
  • Users API - Create, list, promote, deactivate, and reactivate users, and manage their passwords.
  • Sign-in API - Exchange a username and password for a session key. Disabled by default.
  • API Keys API - Create, re-scope, and revoke API keys.
  • Limits API - Read the limits and rules Philter enforces, and what the caller may do.
  • Settings API - Read and change the deployment's administrator settings.
  • Audit API - List and export the audit log.

Listings

Every endpoint that lists things returns an object with the items on the requested page and total, how many items the listing has across every page, so a client can show a count and page through without guessing:

{
  "contexts": [
    { "name": "default", "createdAt": "2026-10-01T12:00:00.000Z" },
    { "name": "claims-2025", "createdAt": "2026-10-05T10:20:30.000Z" }
  ],
  "total": 2
}

Listings share these query parameters:

  • offset (default 0) - How many items to skip.
  • limit (default 25, at most 100) - How many items to return. A larger value is reduced to 100.
  • sort - The field to order by, from the listing's fields in the table below. An unknown field is refused with 400 Bad Request naming the fields it accepts.
  • order - asc or desc. Left out, the listing's default sort keeps its default direction, and any other sort is ascending. Anything else is refused with 400 Bad Request.
  • q - Where a listing supports it, only items whose field (see the table) contains q, ignoring case. q is matched as plain text, not as a pattern.

Items with the same value for the sort field are kept in a stable order, so paging does not skip or repeat them. total counts the items that match q and any filters.

Listing Items sort (default first) q searches Other filters
GET /api/policies policies name, created, updated name all_users, managed, deleted (the managed and deleted listings sort by name only)
GET /api/policies/{policyName}/versions versions revision (newest first)
GET /api/contexts contexts name, created name all_users
GET /api/contexts/{name}/entries entries created (newest first), reads
GET /api/lists lists name name all_users
GET /api/holds holds set (newest first), reference reference all_users
GET /api/documents documents submitted (newest first), fileName document id or file name status
GET /api/ledger/{documentId} entries chain order only
GET /api/ledger chains created (newest first), filename document id or file name all_users
GET /api/users users username, created username role, active
GET /api/api-keys and GET /api/users/{username}/api-keys apiKeys created (oldest first) session
GET /api/audit events timestamp (newest first), by order only event, from, to, owner
GET /api/pii-counts counts day (newest first), context, documents from, to, context, owner
GET /api/webhook/deliveries deliveries created (newest first), by order only

Errors

Every error Philter returns under /api is a JSON object with the same fields, whatever refused the request and whatever the request's Accept header asked for:

{
  "message": "This API key does not have the 'policies:read' scope.",
  "reason": "missing_scope"
}
  • message - What happened, written for a person to read. It may change between releases; do not match on it.
  • reason - Why the request was refused, as a stable code. Use it in code to decide what to do.
  • field - Present when a request is refused as invalid and the invalid parameter or body field is known, such as sort or password.

A refusal with a specific reason gives it, as listed below. Any other error gives the reason its status implies: invalid_request (400), invalid_credentials (401), forbidden (403), not_found (404), method_not_allowed (405), not_acceptable (406), conflict (409), payload_too_large (413), unsupported_media_type (415), rate_limited (429), service_unavailable (503), or internal_error (500).

Request parameters that cannot be parsed are refused on any endpoint with 400 Bad Request and invalid_request: a parameter with no name (?=) in the query string or a form body, or more than 1,000 parameters. A form body too large to parse is refused with 413 and payload_too_large.

Status reason Meaning
401 invalid_credentials The API key is missing, malformed, or unknown, or is a revoked long-lived key. Sign-in also gives it for a wrong username or password, without saying which.
401 session_expired The session key has ended: it passed its idle timeout or maximum lifetime, was revoked, or signed out. Sign in again.
401 user_deactivated The API key's user is deactivated.
403 missing_scope The API key does not hold the scope the endpoint requires.
403 admin_required The operation requires an administrator.
403 feature_disabled The operation is turned off in this deployment, such as ledger deletion.
403 password_change_required The session key may only change its user's password until it is changed.
403 mfa_enrollment_required The session key may only enroll its user in MFA until enrollment is confirmed.
403 wrong_password The current password is not correct.
400, 403 invalid_code The MFA code is not valid: 400 when confirming enrollment, 403 when removing it.
403 mfa_locked The user's MFA is locked after repeated bad codes; an administrator must unlock it.
403 scope_not_held The calling key cannot create a key, widen a key's scopes, or grant scopes it does not hold.
404 not_found Nothing with that name, id, or path exists, or the owner does not exist or may not be reached. The two read the same, so a value cannot be used to discover what exists.
409 self_action_refused The caller cannot do this to their own user or to the key making the request, such as deactivating themselves or revoking the calling key.
409 last_admin The user is the last active administrator.
409 already_exists A user with that username already exists, active or deactivated.
409 mfa_unavailable, mfa_already_enrolled, mfa_not_enrolled MFA is not turned on in this deployment, or the user is already enrolled, or not enrolled.
409 changed_concurrently Another request changed the same thing first, such as the password. Read it again and retry.
409 admin_change_in_progress Another change to the administrators is in progress, such as a role change or deactivation through another instance. Retry-After gives the seconds to wait. MongoDB storage only.
409 not_a_session_key Only a session key can sign itself out.
409 webhook_not_set No webhook is set, so there is nothing to test.
409 externally_managed The signing key is managed outside Philter.
409 context_in_use The context has queued or running redactions.
409 document_not_ready The document is still being redacted.
410 document_failed The document's redaction failed, so there is nothing to download.

Some refusals carry their own reasons, documented with their endpoints: the policy conflicts (policy_exists, policy_managed, policy_changed, policy_default), context_exists and context_limit_reached, list_exists, hold_exists and operation_in_progress, redact_list_changed, entry_unreadable, and sign-in's locked and rate_limited.

Request ids

Every response under /api carries an X-Request-Id header with an id Philter gives the request, whether the request succeeded or was refused:

HTTP/1.1 404
X-Request-Id: 01929f2e-6c3a-7b1d-9e4f-3a2b1c0d9e8f
Content-Type: application/json

{"message":"Not found.","reason":"not_found"}

Every audit event the request causes records the same id as its request_id, so a client that keeps the id, or a person who quotes it, can find exactly what the request did with GET /api/audit. The exception is redaction itself: document_redaction_initiated and document_redaction_completed record the document id (X-Document-Id, or documentId for an asynchronous PDF) as their request_id, so a queued redaction's start and its later completion share one id with its ledger chain. Philter always makes its own id and does not take one from the request. A request refused by the web server before it reaches Philter, such as one with a malformed path, has no id.

OpenAPI Specification

Philter's API is described by an OpenAPI specification generated from the application's source. The OpenAPI export integration test regenerates it and checks it against the registered routes and the committed copy. Run that test when changing an endpoint; a successful ordinary compilation alone does not refresh the published artifact. You can always find it in any of these places:

  • In this documentation: openapi.json. This is the copy built with these pages, from the same source revision, and needs no running instance.
  • In the GitHub repository: docs/docs/api_and_sdks/openapi.json. This is the committed copy, reflecting the latest code on main.
  • From a running Philter instance: https://<your-philter-host>:8080/v3/api-docs. This is the live specification served by that instance, reflecting its version and configuration.

Use the specification to generate an API client for your language (for example with OpenAPI Generator). See Client SDKs for more.

Interactive API Reference (Swagger UI)

Every running Philter instance serves an interactive API reference (Swagger UI) generated from the live OpenAPI specification. It lists every endpoint with its parameters, request and response schemas, and lets you try requests directly from the browser.

  • Swagger UI: https://<your-philter-host>:8080/swagger-ui/index.html

To authorize a request from Swagger UI, send your API key as a bearer token in the Authorization header (see API Keys and Authentication).

Securing Philter's API

Philter serves its API over HTTPS, using a self-signed certificate generated on first start unless you supply your own. See TLS for how to replace the certificate or terminate TLS in front of Philter.

Rate limiting

Philter enforces the limits that depend on who a request is for, which only Philter can see:

  • Password guessing. Failed sign-ins and wrong current passwords when changing a password lock the user for a while, and sign-in is limited per client address.
  • Asynchronous documents. Queue admission limits active jobs and input bytes per account and overall.
  • Request size. Request bodies are capped by MAX_FILE_SIZE_BYTES for documents and MAX_FILE_SIZE_BYTES_OTHER for everything else.

Philter does not limit how many requests a client or API key makes. Put a reverse proxy, load balancer, or API gateway in front of Philter to limit request rates, as you would for any API. Consider tighter per-key limits on the two endpoints that return original values in the clear, so a stolen or misused key cannot reverse a whole dataset quickly:

  • POST /api/reidentify and POST /api/reidentify/text (scope reidentify), where each request can carry as many values, or as much text, as fit in MAX_FILE_SIZE_BYTES_OTHER.
  • GET /api/ledger/{documentId}/export (scope ledger:export).

For example, with NGINX, keyed by the Authorization header so each API key is limited separately:

limit_req_zone $http_authorization zone=philter_reversal:10m rate=10r/m;

location ~ ^/api/reidentify(/text)?$ {
    limit_req zone=philter_reversal burst=5 nodelay;
    proxy_pass https://philter:8080;
}

location ~ ^/api/ledger/[^/]+/export$ {
    limit_req zone=philter_reversal burst=5 nodelay;
    proxy_pass https://philter:8080;
}

Grant reidentify and ledger:export only to the keys that need them, and watch the redaction_reversed and redaction_ledger_exported events in the audit log for unexpected volume.

SDKs

The Philter Java SDK provides convenient methods for using Philter's API. For other languages, generate a client from the OpenAPI specification above. See Client SDKs for more information.

Complete endpoint inventory

The endpoint inventory lists every HTTP operation, its required scope, and its detailed reference. Additional API sections include the Redaction Ledger API, Legal Holds API, policy history and rollback, re-identification, and public health and signing keys.