Skip to main content

ANY /proxy/anthropic/*

Proxy requests to Anthropic API with automatic credential injection, policy enforcement, prompt drift detection, and cost tracking. All requests are logged in the audit trail.

Authentication

Does not require session authentication. Uses Anthropic API key from the credential vault.
The proxy does not use session tokens. Configure your Anthropic API key in the Fishnet credential vault, and the proxy will automatically inject it into upstream requests.

Endpoint Format

POST /proxy/anthropic/v1/messages
POST /proxy/anthropic/v1/complete
# Any Anthropic API endpoint
The path after /proxy/anthropic is forwarded to the Anthropic API (default: https://api.anthropic.com).

Request Flow

  1. Rate limiting (if configured): Check llm.rate_limit_per_minute
  2. LLM guards (if enabled):
    • Model allowlist check (llm.allowed_models)
    • Prompt drift detection (llm.prompt_drift)
    • Prompt size limits (llm.prompt_size_guard)
  3. Credential injection: Retrieve Anthropic API key from vault and add x-api-key header
  4. Upstream request: Forward to Anthropic API
  5. Cost tracking (if enabled): Parse usage and record cost based on llm.model_pricing
  6. Audit logging: Record decision, cost, and cryptographic proof in audit log

Policy Enforcement

llm.rate_limit_per_minute
integer
default:"0"
Maximum requests per minute across all LLM providers. 0 = disabled.
llm.allowed_models
array
default:"[]"
List of allowed model names (case-insensitive). Empty = all models allowed.Example: ["claude-4.5-sonnet", "claude-3.5-haiku"]
llm.prompt_drift.enabled
boolean
default:"true"
Enable prompt drift detection. Records baseline system prompt and alerts on changes.
llm.prompt_drift.mode
string
default:"alert"
Action when drift is detected: alert (log warning) or deny (block request).
llm.prompt_size_guard.enabled
boolean
default:"false"
Enable prompt size limiting.
llm.prompt_size_guard.max_prompt_tokens
integer
default:"0"
Maximum total characters in prompt (approximate). 0 = no limit.

Cost Tracking

Fishnet tracks token usage and calculates costs based on model pricing:
[llm]
track_spend = true

[llm.model_pricing]
"claude-4.5-sonnet".input_per_million_usd = 3.00
"claude-4.5-sonnet".output_per_million_usd = 15.00
"claude-3.5-haiku".input_per_million_usd = 0.80
"claude-3.5-haiku".output_per_million_usd = 4.00
Anthropic’s Messages API includes usage in both streaming and non-streaming responses. Fishnet parses usage.input_tokens and usage.output_tokens from the response.

Headers Forwarding

All request headers are forwarded to Anthropic except:
  • x-api-key (replaced with vault credential)
  • authorization (stripped)
  • host, connection, keep-alive, transfer-encoding, content-length (HTTP infrastructure)
Required Anthropic headers like anthropic-version are forwarded as-is.

Body Forwarding

Request bodies are forwarded as-is to Anthropic. For JSON requests, Fishnet parses the body to:
  • Extract the model field for allowlist checking
  • Extract the stream field to detect streaming requests
  • Extract system prompts from messages for drift detection
  • Count total characters for size limits

Examples

curl -X POST http://localhost:3080/proxy/anthropic/v1/messages \
  -H "Content-Type: application/json" \
  -H "anthropic-version: 2023-06-01" \
  -d '{
    "model": "claude-4.5-sonnet",
    "max_tokens": 1024,
    "messages": [
      {"role": "user", "content": "What is Fishnet?"}
    ]
  }'

Streaming Example

import requests
import json

url = "http://localhost:3080/proxy/anthropic/v1/messages"
headers = {
    "Content-Type": "application/json",
    "anthropic-version": "2023-06-01"
}
payload = {
    "model": "claude-4.5-sonnet",
    "max_tokens": 1024,
    "messages": [{"role": "user", "content": "Count to 5"}],
    "stream": True
}

response = requests.post(url, headers=headers, json=payload, stream=True)

for line in response.iter_lines():
    if line:
        line = line.decode('utf-8')
        if line.startswith('data: '):
            data = line[6:]
            event = json.loads(data)
            print(event)
Anthropic includes usage in streaming responses via message_start and message_delta events. Fishnet accumulates these to track total cost.

Error Responses

400 Bad Request
object
{"error": "request body is not valid JSON"}
Invalid JSON body when Content-Type is application/json.
403 Forbidden
object
{"error": "model not in allowlist: claude-2.1"}
Model not in llm.allowed_models list.
403 Forbidden
object
{"error": "System prompt drift detected: ..."}
Prompt drift detected and llm.prompt_drift.mode = deny.
403 Forbidden
object
{"error": "Prompt size 5000 chars exceeds limit of 1000"}
Prompt exceeds llm.prompt_size_guard.max_prompt_tokens when action is deny.
429 Too Many Requests
object
{
  "error": "rate limit exceeded, retry after 42s",
  "retry_after_seconds": 42
}
Rate limit exceeded (configured in llm.rate_limit_per_minute).
502 Bad Gateway
object
{"error": "upstream provider is unavailable"}
Failed to connect to Anthropic API.

Audit Log Entry

Each proxied request creates an audit log entry:
{
  "id": 43,
  "timestamp": 1709510405000,
  "intent_type": "api_call",
  "service": "anthropic",
  "action": "POST /v1/messages",
  "decision": "approved",
  "reason": null,
  "cost_usd": 0.004567,
  "policy_version_hash": "a1b2c3...",
  "intent_hash": "234567...",
  "permit_hash": null,
  "merkle_root": "edcba9..."
}
Retrieve via GET /api/audit?service=anthropic.

Configuration

Add your Anthropic API key to the credential vault:
# Via Dashboard: Credentials > Add Credential
# Service: anthropic
# Name: (any name, e.g., "primary")
# Key: sk-ant-...
Or via API:
curl -X POST http://localhost:3080/api/credentials \
  -H "Authorization: Bearer fn_sess_YOUR_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "service": "anthropic",
    "name": "primary",
    "key": "sk-ant-..."
  }'

Build docs developers (and LLMs) love