Anthropic (TypeScript)

The @anthropic-ai/sdk package 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 are untouched.

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 requests …/v1/v1/messages, which is a 404. Every other client on this site takes /v1 in the base URL — this one does not.

Install

npm install @anthropic-ai/sdk

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 ts --sdk anthropic

Or by hand:

import Anthropic from '@anthropic-ai/sdk'

const org = process.env.VULNETIX_ORG_UUID

const client = new Anthropic({
  baseURL: `https://guardrails.vulnetix.com/anthropic/${org}`, // no /v1
  authToken: process.env.VULNETIX_API_KEY, // Vulnetix key, not an Anthropic key
})

const message = await client.messages.create({
  model: 'claude-sonnet-5',
  max_tokens: 1024,
  messages: [{ role: 'user', content: 'Hello' }],
})
console.log(message.content)

authToken sends the key as Authorization: Bearer. The apiKey option you already know 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.

Streaming

Unchanged.

const stream = client.messages.stream({
  model: 'claude-sonnet-5',
  max_tokens: 1024,
  messages: [{ role: 'user', content: 'Hello' }],
})

stream.on('text', (text) => process.stdout.write(text))
const final = await stream.finalMessage()

Handling a guardrail block

A request your policy refuses never reaches Anthropic. It comes back as an Anthropic-shaped 403, which the SDK throws as Anthropic.PermissionDeniedError.

type FirewallError = { code?: string; blocked_by?: string; message?: string }

try {
  const message = await client.messages.create({
    model: 'claude-sonnet-5',
    max_tokens: 1024,
    messages: [{ role: 'user', content: prompt }],
  })
} catch (err) {
  if (err instanceof Anthropic.PermissionDeniedError) {   // HTTP 403
    const detail = err.error as FirewallError | undefined
    if (detail?.code === 'request_blocked') {
      throw new Error(`AI firewall blocked this request: rule "${detail.blocked_by}"`)
    }
    // provider_denied, model_denied, model_not_allowed, provider_key_missing
  }
  throw err
}

The body is in Anthropic’s dialect — {"type": "error", "error": {"type": "permission_error", "code": …}} — but the code values are the same across every surface:

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. Anthropic’s own safety behaviour 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 thrown. 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 you get an ordinary Message back. It surprises people; it is not a bug.

Tool / function calling

Tools pass through untouched — the tools you define 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. For an agent this is the important case: a tool that reads a file with credentials in it puts those credentials into the next request, where the firewall can block or redact them — even though your user never typed them.

Gotchas

  • No /v1 in the base URL. Worth repeating: it is the single most common misconfiguration on this site.
  • The credential is your Vulnetix key. An Anthropic key here is a 401.
  • The SDK reads ANTHROPIC_BASE_URL and ANTHROPIC_AUTH_TOKEN from the environment when you omit the options — and the same no-/v1 rule applies to that value. This is how vulnetix ai-firewall install claude-code wires Claude Code.
  • err.error is the parsed error object. blocked_by is a Vulnetix addition and is not in the SDK’s types, so cast rather than fight the compiler.
  • Pointing this SDK at a non-Anthropic provider slug returns 404 unsupported_api — only anthropic serves the Messages API.