Skip to content

Anthropic

Point the Anthropic SDK at Privyx. In the default transparent mode /v1/messages and /v1/messages/count_tokens (the same body) are routed and transformed, and the client's x-api-key / anthropic-version headers are forwarded upstream.

import anthropic

client = anthropic.Anthropic(base_url="http://localhost:8000")  # the key still comes from ANTHROPIC_API_KEY

reply = client.messages.create(
    model="claude-opus-5-5",
    max_tokens=16000,
    messages=[{"role": "user", "content": "Write a short greeting to alice@example.com"}],
)
print(next(block.text for block in reply.content if block.type == "text"))

Start it with:

privyx proxy --upstream https://api.anthropic.com

The base URL has no /v1: the SDK adds /v1/messages itself. Streaming and tool calls are shown in An app on the OpenAI or Anthropic SDK, and Claude Code in Coding agents.

Configuration

provider:
  type: anthropic
  base_url: https://api.anthropic.com/v1/messages
  headers:
    anthropic-version: "2023-06-01"

Do not write api_key: ${VAR}: Privyx does not expand environment variables in config files, so the literal string is sent upstream as the key.

Privyx's own key comes, in both modes, from PRIVYX_ANTHROPIC_API_KEY when provider.type is anthropic, else from PRIVYX_API_KEY / provider.api_key. When one is set it replaces the client's key on every request; when none is, the transparent proxy relays the client's own x-api-key header and the gateway sends none.

What is covered

system (a string or text blocks) and every string in messages are pseudonymized, except opaque keys — ids, type, name, signature, base64 data, URLs, media_type, cache_control (the full rule is in proxy.md). That reaches, among others, text / thinking, tool_use and server_tool_use input (every leaf, whatever its key), tool_result content, document blocks (a plain-text source.data, source.content, title, context), search_result (title, content[].text), citations[].cited_text, and code-execution results (stdout / stderr). tools is config apart from its description strings (tool and input_schema parameter descriptions); tool_choice, metadata, and the rest of the top level are left alone. A batch response is restored at every string leaf.

Streaming, reasoning & tools

Anthropic's content_block_delta events carry delta.text for text, delta.thinking for reasoning, and delta.input_json_delta for tool inputs. StreamRouter deanonymizes text and thinking on separate buffers, and accumulates a tool_use / server_tool_use block's partial JSON until content_block_stop, then emits it restored in one frame. Every other event — citations_delta (a complete cited_text), a content_block_start carrying a whole server-tool result, message_start — is restored leaf by leaf; signature values are never touched, and message_stop is emitted last so nothing arrives after it. The thinking text a signature covers is remembered as the upstream sent it, so when the client echoes the block back it goes upstream byte-identical, not re-pseudonymized (proxy.md).