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.

Warning

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:

codeMeaning
request_blockedA content guardrail matched. blocked_by names the rule.
provider_deniedPolicy does not allow this provider.
model_denied / model_not_allowedPolicy does not allow this model.
provider_key_missingNo 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.

Note Redaction is silent — nothing is raised. A 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 /v1 in 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_URL in the environment, the SDK reads it — and the same no-/v1 rule applies to that value.
  • Pointing this SDK at a non-Anthropic provider slug returns 404 unsupported_api: only anthropic serves the Messages API. The error message names the base URL you should be using instead.