Node.js & TypeScript
The official openai package talks to the firewall exactly as it talks to OpenAI.
You change baseURL and apiKey; your model strings, messages, tools, and
streaming loop stay as they are. The gateway holds your provider key server-side,
so this process never carries one.
Install
npm install openai
Configure
Have the CLI print the snippet, with your organisation UUID already filled in:
vulnetix ai-firewall snippet --lang ts --sdk openai --provider openai
Or write it by hand:
import OpenAI from 'openai'
const client = new OpenAI({
baseURL: `https://guardrails.vulnetix.com/openai/${process.env.VULNETIX_ORG_UUID}/v1`,
apiKey: process.env.VULNETIX_API_KEY, // your Vulnetix key, not an OpenAI key
})
const response = await client.chat.completions.create({
model: 'gpt-4o-mini',
messages: [{ role: 'user', content: 'Hello' }],
})
console.log(response.choices[0].message.content)
Swap openai in the path for any other provider slug — groq, mistral,
openrouter, deepseek — and use whatever that provider calls its model. The
gateway forwards model verbatim.
Streaming
Unchanged. The gateway relays the provider’s SSE stream.
const stream = await client.chat.completions.create({
model: 'gpt-4o-mini',
messages: [{ role: 'user', content: 'Hello' }],
stream: true,
})
for await (const chunk of stream) {
process.stdout.write(chunk.choices[0]?.delta?.content ?? '')
}
Handling a guardrail block
A request your policy refuses never reaches the provider. It comes back as an
OpenAI-shaped 403, which the SDK throws as OpenAI.PermissionDeniedError.
type FirewallError = { code?: string; blocked_by?: string; message?: string }
try {
const response = await client.chat.completions.create({
model: 'gpt-4o-mini',
messages: [{ role: 'user', content: prompt }],
})
} catch (err) {
if (err instanceof OpenAI.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
}
Read code to tell your organisation refused this apart from the provider
refused this:
code | Meaning |
|---|---|
request_blocked | A content guardrail matched. blocked_by names the rule; violations lists every rule that matched. |
provider_denied | Your organisation’s policy does not allow this provider. |
model_denied / model_not_allowed | Your organisation’s policy does not allow this model. |
provider_key_missing | No provider key is stored in the vault for this provider. |
pii_redact rule rewrites the
matched spans in your prompt to the literal [REDACTED] and lets the request
proceed. Nothing throws; you simply get a completion about redacted text. It
surprises people, so it is worth knowing before you spend an afternoon debugging
a model that “ignored” part of the prompt.Tool / function calling
Tools pass through untouched — tools, tool_choice, and the tool_calls you get
back are the provider’s own. Guardrails inspect the request you send, and that
includes the tool messages you send back with results. A tool that reads a secret
and returns it into the next turn produces a request the firewall sees, and can
block or redact.
Gotchas
apiKeyis your Vulnetix key. An OpenAI key here is the most common401.- The SDK reads
OPENAI_BASE_URLandOPENAI_API_KEYfrom the environment when you omit the options, sovulnetix ai-firewall install shellcan route an existing script with no code change. The Vercel AI SDK does not — see Vercel AI SDK. err.erroris the parsederrorobject from the body.blocked_byandviolationsare Vulnetix additions and are not in the SDK’s TypeScript types, so cast as above rather than fighting the compiler.- Every 403 subclass extends
OpenAI.APIError, which carriesstatus, soerr instanceof OpenAI.APIError && err.status === 403works if you prefer it.