Current availability
The proxy is the proxy command in the tightlip package 0.1.0, installed from the download page as described in Installation. It needs Node 18 or later and a Tightlip API key. See the API reference for how keys and credits work.
We ran it on 15 October 2026. The AI provider and the credit meter were local stand-ins, so no request left the test machine. Every response and log line below comes from those runs. Port numbers are shown as <port> because the runs picked a free port.
Start the proxy
# macOS or Linux
export TIGHTLIP_API_KEY=tl_live_...
npx --no-install tightlip proxy
# Windows PowerShell
$env:TIGHTLIP_API_KEY = "tl_live_..."
npx --no-install tightlip proxyTightlip proxy on http://127.0.0.1:<port>
OpenAI: baseURL http://127.0.0.1:<port>/v1
Anthropic: baseURL http://127.0.0.1:<port>
Only placeholders reach the provider; answers are restored here. Ctrl+C to stop.Without TIGHTLIP_API_KEY set, the command prints that the key is not set and exits with code 1.
Flags
tightlip proxy [--port 8787] [--host 127.0.0.1] [--profile <name>]
[--openai-base <url>] [--anthropic-base <url>]
Local OpenAI- and Anthropic-compatible proxy. Protects each request, restores each
answer. Needs TIGHTLIP_API_KEY in the environment. Point your client at
http://localhost:8787/v1 (OpenAI) or http://localhost:8787 (Anthropic).| Flag | Default | Meaning |
|---|---|---|
--port | 8787 | Port to listen on. |
--host | 127.0.0.1 | Interface to listen on. The default accepts connections from this machine only. |
--profile | general | Detection profile. An unknown name exits with code 2 and lists the valid ones. |
--openai-base | the OpenAI API | Where OpenAI-style requests go after protection. |
--anthropic-base | the Anthropic API | Where Anthropic-style requests go after protection. |
Point a client at it
Set the client's base URL to the proxy. Your client still sends its own provider key, and the proxy passes it on to the provider.
const openai = new OpenAI({ baseURL: 'http://localhost:8787/v1' });
const anthropic = new Anthropic({ baseURL: 'http://localhost:8787' });Endpoints
| Request | What happened |
|---|---|
GET /health | 200 with {"ok":true}. |
GET /v1/models | Passed straight to the provider. Model lists carry no user text. |
POST /v1/chat/completions | Protected, forwarded, and restored. JSON and streamed answers. |
POST /v1/responses | Protected, forwarded, and restored. |
POST /v1/messages | Anthropic format. Protected, forwarded, and restored. |
| Any other path | 404 tightlip_unsupported_path. Nothing is sent to the provider. |
| Status | Error type | When |
|---|---|---|
| 400 | invalid_request_error | The body is not valid JSON. |
| 401 | tightlip_auth | The meter rejected the API key. |
| 402 | tightlip_quota | The account is out of credits. |
| 503 | tightlip_meter_unavailable | The meter could not be reached. |
In each of the four error cases the provider received nothing.
{"error":{"message":"Tightlip proxy forwards POST /v1/chat/completions, /v1/responses and /v1/messages only, so POST /v1/embeddings was not sent.","type":"tightlip_unsupported_path","code":"tightlip_unsupported_path"}}One request, start to finish
POST /v1/chat/completions
{"model":"example-model","messages":[{"role":"user","content":"Write to our client Rosalind Achterberg about Case No. 2:25-cv-01482."}]}[
{ role: 'system', content: 'Some details in this conversation were replaced with placeho...' },
{ role: 'user', content: 'Write to our client [PERSON_1] about Case No. [CASE_NUMBER_1].' }
]Dear [PERSON_1], your hearing for [CASE_NUMBER_1] is set.Dear Rosalind Achterberg, your hearing for 2:25-cv-01482 is set.The proxy added PLACEHOLDER_INSTRUCTION as a system message because something was replaced. For a request with nothing to protect, the provider received the message unchanged and no credit was reported.
Streaming
With "stream": true, the provider's chunks split placeholders in the middle. The proxy held back each partial placeholder and sent whole values to the client.
data: {"id":"c1","object":"chat.completion.chunk","choices":[{"index":0,"delta":{"content":"Dear [PER"}}]}
data: {"id":"c1","object":"chat.completion.chunk","choices":[{"index":0,"delta":{"content":"SON_1], your hearing for [CASE_NUM"}}]}
data: {"id":"c1","object":"chat.completion.chunk","choices":[{"index":0,"delta":{"content":"BER_1] is set."}}]}
data: [DONE]data: {"id":"c1","object":"chat.completion.chunk","choices":[{"index":0,"delta":{"content":"Dear "}}]}
data: {"id":"c1","object":"chat.completion.chunk","choices":[{"index":0,"delta":{"content":"Rosalind Achterberg, your hearing for "}}]}
data: {"id":"c1","object":"chat.completion.chunk","choices":[{"index":0,"delta":{"content":"2:25-cv-01482 is set."}}]}
data: [DONE]Logs
The proxy writes one line per protected request to standard error. It logs counts and status, not text.
POST /v1/chat/completions -> 200 (2 values protected)
POST /v1/messages -> 200 (1 values protected)
POST /v1/responses -> 200 (1 values protected)
POST /v1/chat/completions -> 402 tightlip_quota
POST /v1/chat/completions -> 401 tightlip_auth
POST /v1/chat/completions -> 503 tightlip_meter_unavailableCheck a file first
The same package has a check command. It shows what would be protected in a file, or in standard input with -. It runs on your machine, needs no key and spends no credits. Add --json for machine-readable output and --protected to print the placeholder text.
npx --no-install tightlip check notes.txt --profile legal --protectednotes.txt: 3 items would be protected (profile: legal)
1:29 PERSON "Rosalind Achterberg"
1:64 CASE_NUMBER "2:25-cv-01482"
2:14 EMAIL "rosalind.a@example.com"
By type: PERSON 1, CASE_NUMBER 1, EMAIL 1
Cost through the SDK or proxy: 1 credit. This check ran on your machine and sent nothing.
--- protected text ---
Draft a reply to our client [PERSON_1] about Case No. [CASE_NUMBER_1].
Her email is [EMAIL_1].