Skip to content

Developer Quick Start

This guide walks through creating a redaction policy, redacting text, and redacting a PDF, using curl and Python.

Before You Start

You need a running Philter instance and an API key. Philter does not start without PHILTER_ENCRYPTION_KEY (see Encryption) and, on its first start, PHILTER_BOOTSTRAP_API_KEY, the first API key, which Philter seeds onto the admin user (see bootstrap settings). Running from the repository, ./compose.sh generates both into .env. Create narrower keys with the API Keys API.

Every request carries the key as a bearer token:

Authorization: Bearer <YOUR_API_KEY>

The examples use https://localhost:8080 and pass -k to curl because the Docker image generates a self-signed certificate on first start. Replace the host with your own endpoint, and drop -k once you install a trusted certificate.

Step 1: Create a Redaction Policy

A policy defines what Philter looks for and how each match is transformed. Philter ships with a default policy; this creates a custom one.

Save the following as my-policy.json:

{
  "identifiers": {
    "ssn": {
      "ssnFilterStrategies": [
        { "strategy": "REDACT" }
      ]
    },
    "emailAddress": {
      "emailAddressFilterStrategies": [
        { "strategy": "MASK" }
      ]
    }
  }
}

REDACT replaces the match with a label, and MASK replaces each character with a mask character. See Filter Strategies for the full set and Policy Schema for every field.

The policy name comes from the required name query parameter. If a policy with that name already exists, the request is refused with 409 and the reason policy_exists; replace an existing policy with PUT /api/policies/{name}.

Using curl

curl -k -X POST "https://localhost:8080/api/policies?name=my-custom-policy" \
     -H "Authorization: Bearer YOUR_API_KEY" \
     -H "Content-Type: application/json" \
     -d @my-policy.json

A successful save returns 201 Created. An invalid policy returns 400 Bad Request with a message describing the problem, and nothing is stored.

Using Python

import requests

api_key = "YOUR_API_KEY"
base_url = "https://localhost:8080"

with open("my-policy.json") as f:
    policy_json = f.read()

response = requests.post(
    f"{base_url}/api/policies",
    params={"name": "my-custom-policy"},
    headers={
        "Authorization": f"Bearer {api_key}",
        "Content-Type": "application/json",
    },
    data=policy_json,
    verify=False,
)

print(response.status_code)

Step 2: Redact Text

POST /api/filter with Content-Type: text/plain redacts text synchronously and returns the redacted text in the response body.

Two query parameters control the request:

  • p (optional, default default) is the policy to apply.
  • c (optional) is the context. Documents sharing a context receive consistent replacements for the same value, so the same person can be tracked across documents without exposing their identity.

The examples use the default context created for a new account. If you deleted it, recreate it through the Contexts API or choose an existing context before continuing.

Using curl

curl -k -X POST "https://localhost:8080/api/filter?c=default&p=my-custom-policy" \
     -H "Authorization: Bearer YOUR_API_KEY" \
     -H "Content-Type: text/plain" \
     -d "Send the results to jdoe@example.com and reference SSN 123-45-6789."
Send the results to **************** and reference SSN {{{REDACTED-ssn}}}.

The policy created in Step 1 covers only SSNs and email addresses, so nothing else in the text is changed. Person names, dates, and the rest require their own entries in the policy; the default policy is a broader starting point.

The response carries X-Philter-Policy-Name and X-Philter-Policy-Version recording which policy version governed the request, and X-Document-Id identifying the redaction (the ID of its ledger chain when the context has the ledger enabled). When output signing is enabled, or the request passes sign=true, an X-Philter-Signature JWT attests the response body.

Using Python

import requests

api_key = "YOUR_API_KEY"
base_url = "https://localhost:8080"

response = requests.post(
    f"{base_url}/api/filter",
    params={"c": "default", "p": "my-custom-policy"},
    headers={
        "Authorization": f"Bearer {api_key}",
        "Content-Type": "text/plain",
    },
    data="Send the results to jdoe@example.com and reference SSN 123-45-6789.",
    verify=False,
)

print(response.text)
print(response.headers["X-Philter-Policy-Version"])

Step 3: Redact a PDF

POST /api/filter with Content-Type: application/pdf is asynchronous by default. Philter returns 202 Accepted with a documentId, redacts on a background worker, and holds the result for you to download. Append ?async=false to receive the redacted PDF inline instead.

The body may be up to MAX_FILE_SIZE_BYTES (10 MB by default); a larger body is rejected with 413 Payload Too Large. The body is also checked against the declared Content-Type, so a file that contradicts its header is rejected with 415 Unsupported Media Type rather than silently passed through the wrong pipeline.

Submit

curl -k -X POST "https://localhost:8080/api/filter?c=default&p=my-custom-policy" \
     -H "Authorization: Bearer YOUR_API_KEY" \
     -H "Content-Type: application/pdf" \
     -H "Accept: application/pdf" \
     --data-binary @sample_document.pdf
{"documentId":"c0c2c5a8-3a78-4e56-bf2a-44ad8b3a8e9f"}

Poll for Completion

curl -k -H "Authorization: Bearer YOUR_API_KEY" \
     https://localhost:8080/api/documents/c0c2c5a8-3a78-4e56-bf2a-44ad8b3a8e9f/status
{"documentId":"c0c2c5a8-3a78-4e56-bf2a-44ad8b3a8e9f","status":"PROCESSING"}

A document moves through PENDING, PROCESSING, and then COMPLETE or FAILED. Rather than polling, you can configure a webhook to receive a signed notification when the redaction finishes; see Webhooks.

Download

curl -k -H "Authorization: Bearer YOUR_API_KEY" \
     -o redacted_document.pdf \
     https://localhost:8080/api/documents/c0c2c5a8-3a78-4e56-bf2a-44ad8b3a8e9f

Downloading before the redaction completes returns 409 Conflict; a failed redaction returns 410 Gone. Completed records are removed after PENDING_DOCUMENTS_TTL_SECONDS (7 days by default).

The Whole Flow in Python

import time

import requests

api_key = "YOUR_API_KEY"
base_url = "https://localhost:8080"
headers = {"Authorization": f"Bearer {api_key}"}

with open("sample_document.pdf", "rb") as f:
    submit = requests.post(
        f"{base_url}/api/filter",
        params={"c": "default", "p": "my-custom-policy"},
        headers={**headers, "Content-Type": "application/pdf", "Accept": "application/pdf"},
        data=f,
        verify=False,
    )

document_id = submit.json()["documentId"]

while True:
    status = requests.get(
        f"{base_url}/api/documents/{document_id}/status",
        headers=headers,
        verify=False,
    ).json()["status"]

    if status in ("COMPLETE", "FAILED"):
        break

    time.sleep(2)

if status == "COMPLETE":
    download = requests.get(
        f"{base_url}/api/documents/{document_id}",
        headers=headers,
        verify=False,
    )
    with open("redacted_document.pdf", "wb") as f:
        f.write(download.content)

Validate the Output

Detection is probabilistic, so review the output against your own data before putting a policy into production. POST /api/explain returns the spans Philter identified and the policy that produced them, which is the fastest way to see why a value was or was not redacted. The Redaction Ledger records what was redacted in each document for later review.

Next Steps

  • API Reference for every endpoint.
  • Filtering API and Documents API for the full request and response details used above.
  • Policy Schema for advanced policy configuration.
  • Client SDKs for the Java SDK and for generating a client in other languages.
  • The interactive Swagger UI on your own instance at https://localhost:8080/swagger-ui/index.html.