Skip to content

Local proxy

Point your AI client at your own machine.

The proxy protects each OpenAI or Anthropic request on your machine, forwards the placeholder version, and restores the answer.

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

Terminal
# 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 proxy
Printed on start
Tightlip 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

From tightlip --help
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).
Proxy flags
FlagDefaultMeaning
--port8787Port to listen on.
--host127.0.0.1Interface to listen on. The default accepts connections from this machine only.
--profilegeneralDetection profile. An unknown name exits with code 2 and lists the valid ones.
--openai-basethe OpenAI APIWhere OpenAI-style requests go after protection.
--anthropic-basethe Anthropic APIWhere 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.

Base URLs printed by the proxy help
const openai = new OpenAI({ baseURL: 'http://localhost:8787/v1' });
const anthropic = new Anthropic({ baseURL: 'http://localhost:8787' });

Endpoints

Proxy endpoints observed in the recorded run
RequestWhat happened
GET /health200 with {"ok":true}.
GET /v1/modelsPassed straight to the provider. Model lists carry no user text.
POST /v1/chat/completionsProtected, forwarded, and restored. JSON and streamed answers.
POST /v1/responsesProtected, forwarded, and restored.
POST /v1/messagesAnthropic format. Protected, forwarded, and restored.
Any other path404 tightlip_unsupported_path. Nothing is sent to the provider.
Proxy error responses observed in the recorded run
StatusError typeWhen
400invalid_request_errorThe body is not valid JSON.
401tightlip_authThe meter rejected the API key.
402tightlip_quotaThe account is out of credits.
503tightlip_meter_unavailableThe meter could not be reached.

In each of the four error cases the provider received nothing.

POST /v1/embeddings, 404 body
{"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

Your client sent (Fictional example)
POST /v1/chat/completions
{"model":"example-model","messages":[{"role":"user","content":"Write to our client Rosalind Achterberg about Case No. 2:25-cv-01482."}]}
The provider received these messages
[
  { 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].' }
]
The provider answered
Dear [PERSON_1], your hearing for [CASE_NUMBER_1] is set.
Your client received
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.

Stream the provider sent
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]
Stream your client received
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.

Standard error from the recorded run
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_unavailable

Check 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.

Terminal
npx --no-install tightlip check notes.txt --profile legal --protected
Output for a two-line notes.txt (Fictional example)
notes.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].

Limits