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:

codeMeaning
request_blockedA content guardrail matched. blocked_by names the rule; violations lists every rule that matched.
provider_deniedYour organisation’s policy does not allow this provider.
model_denied / model_not_allowedYour organisation’s policy does not allow this model.
provider_key_missingNo provider key is stored in the vault for this provider.
Note Redaction is silent — it is not an error. A 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

  • apiKey is your Vulnetix key. An OpenAI key here is the most common 401.
  • The SDK reads OPENAI_BASE_URL and OPENAI_API_KEY from the environment when you omit the options, so vulnetix ai-firewall install shell can route an existing script with no code change. The Vercel AI SDK does not — see Vercel AI SDK.
  • err.error is the parsed error object from the body. blocked_by and violations are 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 carries status, so err instanceof OpenAI.APIError && err.status === 403 works if you prefer it.