Skip to content

Redaction API

Philter’s Redaction API provides access to Philter’s ability to redact sensitive information from text and to retrieve the health status of Philter. The endpoint path is /api/filter (Philter is named for the filter engine that powers redaction).

Philter serves HTTPS on port 8080 with a generated self-signed certificate by default, so the curl examples on this page pass -k. See the TLS settings to supply your own certificate.

Each filter request can optionally have a context. The context is optional: when it is omitted (or sent as an empty value) the request uses no context features. Token replacements are not persisted or shared across requests, and any entity-type disambiguation is limited to the single document being filtered. Contexts provide a means for logically grouping your documents during filtering. For example, documents pertaining to one health care provider may be submitted under the context hospital1, and documents pertaining to another health care provider may be submitted under the context hospital2.

The context for each filter request impacts how sensitive information is replaced when found in the text. Referential integrity can be enabled at either the context or document level. When enabled at the context level, all instances of a given piece of sensitive information will be replaced consistently by the same value. This allows for maintaining meaning across all documents in the context.

Each filter request submitted to Philter is automatically assigned a document identifier. The document identifier is an alphanumeric value unique to that request. No two documents should be assigned the same document identifier. The document identifier is returned in the X-Document-Id header of each synchronous filter or explain response. An asynchronous PDF request's 202 Accepted response returns it in the JSON body as documentId.

Filter

The filter endpoint receives plain text or a PDF document and returns the redacted text or redacted PDF document.

The types of sensitive information found and how each type is redacted is determined by the chosen policy.

Method Endpoint Description
POST /api/filter Filter the given text.

Query Parameters

  • p - The name of the policy to use for filtering. Defaults to default if not provided.
  • c - The filtering context. Optional; when omitted or empty, no context is used (see above).
  • async - PDF only. Whether to process the request asynchronously. Defaults to true. The text endpoint is always synchronous and ignores this parameter.
  • filename - Optional. A filename to record with this request in the redaction ledger. When omitted, the ledger records none-provided. Where it is kept, with the ledger or an asynchronous job, it is stored unencrypted so it can be searched and sorted, so do not put sensitive values in it; see What is encrypted at rest.
  • fairness_key - Asynchronous PDF only. Optional. Documents your account queues with different fairness keys take turns in the queue. See Fairness keys.
  • sign - Optional, defaults to false. sign=true requests an output signature on a plain-text response when the deployment has not enabled signing. When signing is enabled in settings, every text response is signed and sign=false does not change that. PDF and ZIP requests with sign=true are refused with 400 Bad Request, because PDF responses are not signed.

Headers

  • Authorization - The value should be set to Bearer <token> where <token> is your API key.
  • Content-Type - The value should be set to text/plain or application/pdf.

Request Size

The request body may be up to MAX_FILE_SIZE_BYTES (10 MB by default; see Settings). A larger body is rejected with 413 Payload Too Large and a message stating the limit.

Resolved execution configuration is limited to 1 MiB of UTF-8 JSON, independently of the uploaded policy size. Custom-list expansion is bounded across all dictionaries and ignored terms; captured async configuration also includes account defaults and context settings. Exceeding this limit returns 413 Payload Too Large. Async jobs whose complete encrypted record exceeds the storage limit (16 MiB minus a 16 KiB metadata reserve) also return 413 before queue admission. These rejections do not block subsequent submissions.

Content Type Verification

The body is checked against the declared Content-Type before redaction begins. A body that contradicts it is rejected with 415 Unsupported Media Type, naming both what was declared and what was found.

This exists because the alternative fails silently. A PDF submitted as text/plain would otherwise run through the text pipeline, match almost nothing in the compressed bytes, and return 200 with output that looks redacted. Refusing the request is safer than returning a document Philter never parsed.

Philter accepts text/plain and application/pdf. A body recognized as any other format, such as a .docx, a legacy Office file, or an image, is rejected rather than treated as text.

Response Headers

Every filter response reports which policy version governed the request, so the applied policy is recorded without a second call:

  • X-Philter-Policy-Name - The name of the policy that was applied.
  • X-Philter-Policy-Version - The revision (version) of that policy that was applied. For an asynchronous PDF request the version is pinned when the request is accepted, and the 202 Accepted response carries these headers; the deferred redaction is then governed by that pinned version.

Every successful plain-text filter response (200 OK) also includes:

  • X-Document-Id - The ID this redaction was recorded under. When the request's context has the redaction ledger enabled, it is the ID of the ledger chain written for the document, so GET /api/ledger/{documentId} retrieves that chain. It is also bound into the X-Philter-Signature JWT payload as documentId when output signing is enabled. For an asynchronous PDF redaction, this is the same documentId returned in the 202 Accepted body, so one ID identifies the job, the downloaded result, and the ledger chain.

When output signing is enabled with the Settings API, or the request passes sign=true, successful plain-text filter responses additionally include:

  • X-Philter-Signature - A compact ES256 JWT that cryptographically attests the response body. The JWT payload contains:
  • bodyHash: SHA-256 (lowercase hex) of the response body.
  • policyName: name of the applied policy.
  • policyVersion: revision of the applied policy.
  • documentId: the value from the X-Document-Id response header.
  • iat: Unix epoch (seconds) when the JWT was issued.

Verify the signature using the public key from GET /api/signing-key. See Output Signing for full documentation and code examples. PDF responses are not signed; if signing is enabled and the signing operation fails, the request returns HTTP 500.

Plain text

Plain text redaction is always synchronous. The response body is the redacted text.

curl -k -X POST "https://localhost:8080/api/filter" -d @file.txt -H "Content-Type: text/plain" -H "Authorization: Bearer <token>"

PDF documents

PDF redaction is asynchronous by default in Philter 4.0. The server enqueues the request and returns 202 Accepted with a JSON body containing the assigned documentId and a Location: /api/documents/{documentId} header. A request naming a policy that does not exist is refused with 404 Not Found before it is enqueued, so a mistyped policy name surfaces immediately rather than as a failed job. The synchronous text endpoint answers the same way. The redacted bytes can then be downloaded from the Documents API once the job's status is COMPLETE.

curl -k -X POST "https://localhost:8080/api/filter" \
  -d @file.pdf \
  -H "Content-Type: application/pdf" \
  -H "Authorization: Bearer <token>" \
  -i

Example response (async, default):

HTTP/1.1 202 Accepted
Location: /api/documents/c0c2c5a8-3a78-4e56-bf2a-44ad8b3a8e9f
Content-Type: application/json

{"documentId":"c0c2c5a8-3a78-4e56-bf2a-44ad8b3a8e9f"}

To receive the redacted PDF inline (3.x behavior), append ?async=false:

curl -k -X POST "https://localhost:8080/api/filter?async=false" \
  -d @file.pdf \
  -H "Content-Type: application/pdf" \
  -H "Authorization: Bearer <token>" \
  -o redacted.pdf

Migration from 3.x. Clients that previously consumed the redacted bytes directly from POST /api/filter should either (a) add ?async=false to keep the synchronous behavior, or (b) switch to the async flow (poll GET /api/documents/{id}/status, then download from GET /api/documents/{id}). Async deliveries can additionally trigger a signed webhook when they complete.

Fairness keys

Queued documents from different accounts take turns, so one account's large batch does not hold up every other account's documents. A client that submits on behalf of many people under one account, such as a review application, can pass fairness_key to get the same turn-taking among its own documents: documents with different keys take turns. A key's first document waits for one document from each key already in the queue, not for all of them.

curl -k -X POST "https://localhost:8080/api/filter?fairness_key=reviewer-42" \
  -d @file.pdf \
  -H "Content-Type: application/pdf" \
  -H "Authorization: Bearer <token>"
  • The key is opaque to Philter. Use an internal identifier such as a user or group ID. Do not put personal data in a fairness key: it is stored unencrypted with the queued document so that the queue can be ordered.
  • A key is 1 to 64 characters of letters, digits, ., _, : and -. Any other value is refused with 400 Bad Request, as is a key on a request with async=false.
  • Fairness keys are designed to change only the order of your own account's documents, not your account's share of the queue relative to other accounts. With MongoDB storage, a document claimed at the moment another is being queued can occasionally give an account one extra turn.
  • Documents queued without a key take turns together, as if they shared one key. An account that never passes a key has its documents claimed in submission order, as before.

Explain

The explain endpoint behaves much like the filter endpoint in that receives plain text and returns the redacted plain text. However, the explain endpoint provides a detailed explanation describing how the text was redacted. Also, the explain endpoint does not support PDF documents.

The types of sensitive information found and how each type is redacted is determined by the chosen policy.

Method Endpoint Description
POST /api/explain Filter the given text and provide a detailed explanation.

Query Parameters

  • p - The name of the policy to use for filtering. Defaults to default if not provided.
  • c - The filtering context. Optional; when omitted or empty, no context is used (see above).
  • filename - Optional. A filename to record with this request in the redaction ledger. When omitted, the ledger records none-provided. Where it is kept, with the ledger or an asynchronous job, it is stored unencrypted so it can be searched and sorted, so do not put sensitive values in it; see What is encrypted at rest.
  • sign - Optional, defaults to false. sign=true requests an output signature in the X-Philter-Signature header when the deployment has not enabled signing. The signature covers the JSON response body.

Headers

  • Authorization - The value should be set to Bearer <token> where <token> is your API key.
  • Content-Type - The value should be set to text/plain.

Example explain request:

curl -k -X POST "https://localhost:8080/api/explain" -d @file.txt -H "Content-Type: text/plain" -H "Authorization: Bearer <token>"

Example explain response:

The response body carries the applied policy's policyName, policyVersion, and policyContentHash; the explain response does not set the X-Philter-Policy-* headers. The document ID is in the X-Document-Id response header. identifiedSpans lists every span the filters found; appliedSpans lists the spans that were redacted.

{
  "filteredText": "{{{REDACTED-person}}} was a patient.",
  "piece": 0,
  "context": "",
  "explanation": {
    "appliedSpans": [
      {
        "characterStart": 0,
        "characterEnd": 17,
        "filterType": "PERSON",
        "context": "",
        "confidence": 0.92,
        "text": "George Washington",
        "replacement": "{{{REDACTED-person}}}",
        "ignored": false,
        "applied": true,
        "priority": 0,
        "lineNumber": 0,
        "pageNumber": 0,
        "paragraphNumber": 0,
        "lowerLeftX": 0.0,
        "lowerLeftY": 0.0,
        "upperRightX": 0.0,
        "upperRightY": 0.0
      }
    ],
    "identifiedSpans": [
      {
        "characterStart": 0,
        "characterEnd": 17,
        "filterType": "PERSON",
        "context": "",
        "confidence": 0.92,
        "text": "George Washington",
        "replacement": "{{{REDACTED-person}}}",
        "ignored": false,
        "applied": true,
        "priority": 0,
        "lineNumber": 0,
        "pageNumber": 0,
        "paragraphNumber": 0,
        "lowerLeftX": 0.0,
        "lowerLeftY": 0.0,
        "upperRightX": 0.0,
        "upperRightY": 0.0
      }
    ]
  },
  "tokens": 5,
  "policyName": "default",
  "policyVersion": 3,
  "policyContentHash": "9f2c4e1a7b3d5f60812a4c6e8b0d2f4a6c8e0b2d4f6a8c0e2b4d6f8a0c2e4b6d"
}

Health

The health endpoint is useful in determining the current state of Philter. It can be used by monitoring software to assess Philter's availability or by your cloud provider for purposes of determining Philter's health when deployed behind a load balancer.

The endpoint is unauthenticated.

Method Endpoint Description
GET /api/health Gets the health of Philter.

Example request:

curl -k -X GET "https://localhost:8080/api/health"

Example response:

{
  "status": "UP",
  "applicationVersion": "4.0.0",
  "redactionPolicySchemaVersion": "1.0.0",
  "gitCommit": "a1b2c3d"
}

status is UP when Philter is healthy, and the response code is 200. Treat any other status value, or any non-200 response, as unhealthy. This is the health response shape shared across Philterd products.

Response formats and evidence identifiers

Text requests use Content-Type: text/plain and Accept: text/plain. PDF requests use Content-Type: application/pdf and select Accept: application/pdf or Accept: application/zip. A ZIP contains one entry named redacted.pdf. Both synchronous responses and completed asynchronous downloads contain actual ZIP bytes when ZIP is selected. PDF async acceptance returns JSON regardless of the selected download format. Set async=false for an inline PDF or ZIP.

Synchronous text, PDF, and ZIP responses expose X-Document-Id, allowing lookup of the corresponding ledger when the context records one. /api/filter responses, including async acceptance, report raw policy identity through X-Philter-Policy-Name, X-Philter-Policy-Version, and X-Philter-Policy-Hash; /api/explain reports it in the response body instead. Async acceptance additionally reports X-Effective-Configuration-SHA256; see captured configuration and which policy redacted a document.

Account async capacity returns 429; global capacity, admission contention, or required recovery returns 503, with Retry-After: 5. An incompatible Accept returns 406 and an unsupported Content-Type returns 415. A policy whose PhEye filter calls a PhEye service that cannot be reached, or that answers with an error, returns 503 with a message saying how to fix the policy or the deployment; an asynchronous document fails with the same message. /api/explain returns a JSON object containing filteredText, explanation, and governing-policy metadata; it does not return plain text.

A request whose policy Phileas refuses to load, such as one saved before Philter checked policies when they are saved, returns 400 Bad Request with Phileas's reason. An asynchronous PDF request is refused this way when it is submitted, before it is queued. See Filter Strategy Conditions.

A request whose context c does not exist returns 400 Bad Request with Context does not exist., for text, PDF, and ZIP output, synchronous or asynchronous, and from /api/explain. A queued document uses the context as it was when the document was accepted, and a context with queued documents cannot be deleted; see Deleting a Context With Pending Work.

Malformed, unparseable, and password-protected PDFs return 400 Bad Request. This applies to PDF and ZIP output, including asynchronous submissions, which are validated before queue admission. A PDF must contain at least one page. Storage, signing, and other infrastructure failures remain server errors. PDF validation does not perform OCR; image-only PDFs are outside the supported redaction scope.

Async PDF admission (including ZIP output) retries acknowledged guard contention for up to 500 ms, with randomized 5–25 ms pauses. This bounds contention waiting, not database/network operation timeouts. Once the guard is acquired, account and global capacity checks still apply. An exhausted wait returns 503; account capacity returns 429. Both responses include Retry-After: 5. Database exceptions do not trigger application-level acquisition retries, guard release, or job-insert replay. Plain text and async=false requests bypass queue admission. A PDF sent with async=false while the instance is already redacting PDF_MAX_CONCURRENT PDFs is refused with 503 and Retry-After: 5.