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.
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:
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. 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.
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
/v1in 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_URLandANTHROPIC_AUTH_TOKENfrom the environment when you omit the options — and the same no-/v1rule applies to that value. This is howvulnetix ai-firewall install claude-codewires Claude Code. err.erroris the parsederrorobject.blocked_byis 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— onlyanthropicserves the Messages API.