Anthropic (Python)
The Anthropic Python SDK speaks the Messages API, which the gateway serves on the
anthropic provider. You change the base URL and the credential; your messages,
max_tokens, tools, and streaming loop stay exactly as they are.
The base URL has NO /v1. The Anthropic SDK appends /v1 to whatever base URL
you give it, so the gateway URL must stop at the organisation UUID:
https://guardrails.vulnetix.com/anthropic/{orgUuid}
Add /v1 yourself and the SDK will request …/v1/v1/messages, which is a 404.
Every other client on this site takes /v1 in the base URL — this one does not.
Install
pip install anthropic
Configure
Have the CLI print the snippet, with your organisation UUID already filled in and the base URL correct by construction:
vulnetix ai-firewall snippet --lang python --sdk anthropic
Or by hand:
import os
from anthropic import Anthropic
org = os.environ["VULNETIX_ORG_UUID"]
client = Anthropic(
base_url=f"https://guardrails.vulnetix.com/anthropic/{org}", # no /v1
auth_token=os.environ["VULNETIX_API_KEY"], # Vulnetix key, not an Anthropic key
)
message = client.messages.create(
model="claude-sonnet-5",
max_tokens=1024,
messages=[{"role": "user", "content": "Hello"}],
)
print(message.content[0].text)
auth_token sends the key as Authorization: Bearer. If you would rather use the
constructor argument you already know, api_key= works too — the SDK sends that as
the x-api-key header, and the gateway accepts either header on every route.
What must change is the value: it is your Vulnetix API key, never your Anthropic
key. That one stays in the vault, and the gateway swaps it in upstream.
Model names pass through verbatim — use whatever Anthropic calls the model.
Streaming
Unchanged.
with client.messages.stream(
model="claude-sonnet-5",
max_tokens=1024,
messages=[{"role": "user", "content": "Hello"}],
) as stream:
for text in stream.text_stream:
print(text, end="", flush=True)
Handling a guardrail block
A request your policy refuses never reaches Anthropic. It comes back as an
Anthropic-shaped 403, which the SDK raises as
anthropic.PermissionDeniedError.
import anthropic
try:
message = client.messages.create(
model="claude-sonnet-5",
max_tokens=1024,
messages=[{"role": "user", "content": prompt}],
)
except anthropic.PermissionDeniedError as e: # HTTP 403
err = e.response.json().get("error", {})
code = err.get("code") # request_blocked, model_denied, ...
rule = err.get("blocked_by") # the rule that stopped it
if code == "request_blocked":
raise RuntimeError(f"AI firewall blocked this request: rule {rule!r}") from e
raise
The body is in Anthropic’s dialect, not OpenAI’s — the envelope is
{"type": "error", "error": {"type": "permission_error", "code": …}} — but the
code values are the same everywhere:
code | Meaning |
|---|---|
request_blocked | A content guardrail matched. blocked_by names the rule. |
provider_denied | Policy does not allow this provider. |
model_denied / model_not_allowed | Policy does not allow this model. |
provider_key_missing | No Anthropic key is stored in the vault. |
Read code to tell my organisation blocked this apart from Anthropic rejected
this. A refusal from Anthropic’s own safety systems is a 200 with a refusal in
the text, or one of Anthropic’s own error codes — never request_blocked.
pii_redact rule rewrites the
matched spans in your prompt to the literal [REDACTED] and forwards the request.
Claude answers the redacted prompt, and your code sees an ordinary Message. If a
response seems to be missing the point, check your redaction rules before you blame
the model.Tool / function calling
Tools pass through untouched — tools, tool_choice, and the tool_use blocks you
get back are Anthropic’s own. Guardrails inspect the request you send, and that
includes the tool_result blocks you send back. This is the case that matters for
agents: a tool that reads a file with credentials in it puts those credentials into
the next request, where the firewall sees them and can block or redact — even
though the user never typed them.
Gotchas
- No
/v1in the base URL. Worth repeating, because it is the single most common misconfiguration on this whole site. - The credential is your Vulnetix key. An Anthropic key here is a
401. - If you set
ANTHROPIC_BASE_URLin the environment, the SDK reads it — and the same no-/v1rule applies to that value. - Pointing this SDK at a non-Anthropic provider slug returns
404 unsupported_api: onlyanthropicserves the Messages API. The error message names the base URL you should be using instead.