API Reference
The Philter AI Proxy provides several endpoints to redact sensitive information before sending requests to AI providers. All endpoints support both streaming and non-streaming requests.
Redacted Fields
The proxy inspects and redacts all text-bearing fields in the request body before forwarding. The table below lists exactly which fields are redacted per provider.
| Provider | Message type | Fields redacted |
|---|---|---|
| OpenAI / OpenAI-compatible / Azure OpenAI | role: user |
content (string) |
| OpenAI / OpenAI-compatible / Azure OpenAI | role: system |
content (string) |
| OpenAI / OpenAI-compatible / Azure OpenAI | role: tool |
content (string) |
| OpenAI / OpenAI-compatible / Azure OpenAI | role: assistant with tool calls |
tool_calls[].function.arguments - parsed as JSON, string values redacted, re-serialized |
OpenAI family — /embeddings |
input (string or array of strings; integer token-ID inputs are passed through unchanged) |
|
OpenAI family — /responses (Responses API) |
input (string, or message items incl. nested content[].text) and instructions |
|
OpenAI family — /moderations |
input (string or array of strings) |
|
OpenAI family — /images/generations |
prompt |
|
OpenAI family — /audio/speech (TTS) |
input |
|
OpenAI family — /completions (legacy) |
prompt (string or array) |
|
| Anthropic | Top-level | system (string) |
| Anthropic | text content block |
text |
| Anthropic | tool_result content block |
content (string or nested text blocks) |
| Gemini | text part |
text |
| Gemini | functionResponse part |
response object - all string values redacted recursively |
| Ollama generate | - | prompt, system |
| Ollama chat | role: * |
content |
| Bedrock Converse | Top-level | system[].text |
| Bedrock Converse | messages[].content[] |
text |
Fields not in the table (e.g., model names, IDs, non-string values) are forwarded unchanged. The OpenAI-family endpoint rows apply equally to the OpenAI, Azure OpenAI, and openai-compatible providers, since they share one request handler.
Fields not in the table above (model names, IDs, non-string values) are forwarded unchanged.
Supported request types
The proxy accepts a JSON request body. It does not parse multipart/form-data. That single rule decides every endpoint below, and predicts the answer for any endpoint not listed.
| Request type | Behavior | Error |
|---|---|---|
| Chat, Responses, embeddings, moderations, completions, image generations, audio speech | Redacted per the table above, then forwarded | - |
/v1/batches |
Forwarded unchanged. A batch references an uploaded file by ID, so there is no inline text to redact | - |
| Any endpoint the proxy does not recognise, with a JSON body | Forwarded unchanged, no redaction | - |
/v1/images/edits, /v1/images/variations |
Rejected. Not supported, and not planned | 400 invalid_request / unsupported_content_type |
/v1/files (uploads) |
Rejected. Not supported, and not planned | 400 invalid_request / unsupported_content_type |
/v1/audio/transcriptions, /v1/audio/translations |
Rejected today. Support is tracked in #40 | 400 invalid_request / unsupported_content_type |
Route rejected calls directly to the provider.
Why image edits are not planned. Redacting the prompt form field would mean parsing a multipart body, rewriting one field, and re-encoding the rest, which is the thing the proxy deliberately does not do.
Why file uploads are not planned. A batch file is many embedded requests, so redacting one upload would mean one Philter call per record inside a single synchronous request. Redact the file contents with Philter before uploading.
Why audio can be supported. Audio needs no multipart parsing: the body is binary with no inbound text to redact, so it can be forwarded untouched and the transcript scanned on the way back. It is an outbound-scanning feature, not an inbound-redaction one, which is why it does not break the rule above.
Response Scanning (Outbound)
By default, provider responses are forwarded to the client without modification. When outbound scanning is enabled for a route, the proxy buffers the provider's response and passes each text field through Philter before returning it to the client.
Response fields scanned per provider
| Provider | Response fields scanned |
|---|---|
| OpenAI / OpenAI-compatible | choices[].message.content |
| Anthropic | content[].text (where type == "text") |
| Gemini | candidates[].content.parts[].text |
| Ollama generate | response |
| Ollama chat | message.content |
| Bedrock Converse | output.message.content[].text |
Actions
The outbound.action setting controls what happens when PII is detected in a response:
| Action | HTTP status | Description |
|---|---|---|
redact (default) |
200 |
Detected PII is replaced with Philter's configured token before the response is returned |
block |
403 |
The response is suppressed entirely; the error body below is returned |
flag |
200 |
The original unmodified response is returned; a warning is written to the proxy log |
Block error response (403 Forbidden):
{"error":{"message":"response blocked: PII detected","type":"pii_blocked"}}
Streaming responses
Outbound scanning can only inspect non-streaming responses. When the provider returns a streaming response (Content-Type: text/event-stream, application/x-ndjson, or application/vnd.amazon.eventstream), the behavior depends on the configured action: block rejects the response with 403 (pii_blocked / outbound_stream_unscannable) rather than forwarding it unscanned, while redact and flag log a warning and forward the stream unchanged. Inbound prompt redaction is unaffected in every case.
Latency
Outbound scanning adds a full Philter round-trip after the provider responds. For latency-sensitive workloads, enable it only on routes where compliance requires it. See Configuration for configuration details.
Route Detection
The proxy routes requests based on the URL path:
| Path pattern | Provider |
|---|---|
/v1/messages |
Anthropic |
Path containing generateContent (case-insensitive) |
Gemini |
/api/generate |
Ollama |
/api/chat |
Ollama |
/model/{modelId}/converse |
Amazon Bedrock |
/model/{modelId}/converse-stream |
Amazon Bedrock (streaming) |
/openai/deployments/{deployment}/... |
Azure OpenAI (configured via providers.azure) |
/{name}/v1/... |
OpenAI-compatible (configured via providers.openaiCompatible) |
/health |
Health check (no proxying) |
| All other paths | OpenAI |
Endpoints
OpenAI Chat Completions
- URL:
/v1/chat/completions - Method:
POST - Streaming: Set
"stream": truein the request body. Response is SSE (text/event-stream). - Example:
bash curl -k https://localhost:8080/v1/chat/completions \ -H "Content-Type: application/json" \ -H "Authorization: Bearer $OPENAI_API_KEY" \ -d '{ "model": "gpt-4", "messages": [{"role": "user", "content": "Whose social security number is 123-45-6789"}] }'
Anthropic Messages
- URL:
/v1/messages - Method:
POST - Streaming: Set
"stream": truein the request body. Response is SSE (text/event-stream). - Example:
bash curl -k https://localhost:8080/v1/messages \ -H "Content-Type: application/json" \ -H "x-api-key: $ANTHROPIC_API_KEY" \ -H "anthropic-version: 2023-06-01" \ -d '{ "model": "claude-sonnet-4-20250514", "max_tokens": 1024, "messages": [{"role": "user", "content": "Whose social security number is 123-45-6789"}] }'
Gemini Generate Content
- URL:
/v1beta/models/{model}:generateContent - Method:
POST - Streaming: Use
:streamGenerateContentinstead of:generateContent. Response is chunked JSON. - Note: The Gemini API passes the API key as a URL query parameter (
?key=...) rather than a header. The proxy forwards the query string to the provider but never logs API keys - sensitive query parameters are redacted from all log and error output. - Example:
bash curl -k "https://localhost:8080/v1beta/models/gemini-2.0-flash:generateContent?key=$GEMINI_API_KEY" \ -H 'Content-Type: application/json' \ -X POST \ -d '{ "contents": [{ "parts":[{"text": "Whose social security number is 123-45-6789"}] }] }'
Ollama Generate
- URL:
/api/generate - Method:
POST - Streaming: Ollama streams by default (NDJSON). Set
"stream": falseto receive a single response. - Example:
bash curl -k https://localhost:8080/api/generate \ -H "Content-Type: application/json" \ -d '{ "model": "llama3", "prompt": "Whose social security number is 123-45-6789", "stream": false }'
Ollama Chat
- URL:
/api/chat - Method:
POST - Streaming: Ollama streams by default (NDJSON). Set
"stream": falseto receive a single response. - Example:
bash curl -k https://localhost:8080/api/chat \ -H "Content-Type: application/json" \ -d '{ "model": "llama3", "messages": [{"role": "user", "content": "Whose social security number is 123-45-6789"}], "stream": false }'
Amazon Bedrock Converse
- URL:
/model/{modelId}/converse - Method:
POST - Authentication: The proxy signs requests to Bedrock using AWS Signature Version 4 with credentials from the standard AWS credential chain (environment variables,
~/.aws/credentials, EC2/ECS instance profile, IRSA). The client does not need to supply AWS credentials. - Streaming: Not supported in the current release (
converseStreamis deferred). - Required configuration:
providers.bedrock.regionmust be set. See Configuration for details. - Example:
bash curl -k https://localhost:8080/model/amazon.titan-text-express-v1/converse \ -H "Content-Type: application/json" \ -d '{ "messages": [{"role": "user", "content": [{"text": "Whose SSN is 123-45-6789?"}]}], "inferenceConfig": {"maxTokens": 512} }'
OpenAI-Compatible Providers
- URL:
/{name}/v1/chat/completions(or any/{name}/v1/...path) - Method:
POST - Streaming: Supported. Behaviour is identical to the OpenAI endpoint.
- Required configuration: The provider must be registered under
providers.openaiCompatiblein the config. See Configuration for details. - Example (Mistral):
bash curl -k https://localhost:8080/mistral/v1/chat/completions \ -H "Content-Type: application/json" \ -H "Authorization: Bearer $MISTRAL_API_KEY" \ -d '{ "model": "mistral-small-latest", "messages": [{"role": "user", "content": "Whose SSN is 123-45-6789?"}] }'
The proxy strips the /{name} prefix before forwarding, so the provider receives a standard OpenAI-format request. All PII redaction and audit logging applies normally; the provider field in the audit log is set to the registered name (e.g., mistral).
Health Check
- URL:
/health - Method:
GET - Description: Returns the health status of the proxy.
- Response:
200 OKwith bodyok. - Example:
bash curl -k https://localhost:8080/health