Ruby
The official openai gem takes both settings in its constructor. You change
base_url and api_key, and the rest of your code — models, messages, tools,
streaming — is untouched. The provider key stays in the vault; this process only
carries the Vulnetix API key.
Install
bundle add openai
Configure
require "openai"
org = ENV.fetch("VULNETIX_ORG_UUID")
client = OpenAI::Client.new(
base_url: "https://guardrails.vulnetix.com/openai/#{org}/v1",
api_key: ENV.fetch("VULNETIX_API_KEY") # Vulnetix key, not an OpenAI key
)
chat = client.chat.completions.create(
model: "gpt-4o-mini",
messages: [{ role: "user", content: "Hello" }]
)
puts chat.choices[0].message.content
Swap openai in the path for any other provider slug and pass that provider’s own
model string — the gateway forwards model verbatim.
Streaming
Unchanged. The gateway relays the provider’s SSE stream.
stream = client.chat.completions.stream_raw(
model: "gpt-4o-mini",
messages: [{ role: "user", content: "Hello" }]
)
stream.each do |chunk|
print chunk.choices[0].delta.content
end
Handling a guardrail block
A request your policy refuses never reaches the provider. It returns an
OpenAI-shaped 403, which the gem raises as
OpenAI::Errors::PermissionDeniedError.
begin
chat = client.chat.completions.create(
model: "gpt-4o-mini",
messages: [{ role: "user", content: prompt }]
)
rescue OpenAI::Errors::PermissionDeniedError => e # HTTP 403
error = e.body.is_a?(Hash) ? (e.body["error"] || e.body) : {}
code = error["code"] # request_blocked, model_denied, ...
rule = error["blocked_by"] # the rule that stopped it
raise "AI firewall blocked this request (rule: #{rule})" if code == "request_blocked"
raise
end
code is what tells 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 | Policy does not allow this provider. |
model_denied / model_not_allowed | Policy does not allow this model. |
provider_key_missing | No provider key in the vault for this provider. |
Every HTTP failure is a subclass of OpenAI::Errors::APIError, so rescue that if
you want a single handler; PermissionDeniedError is its 403 case.
pii_redact rule rewrites the
matched spans in your prompt to the literal [REDACTED] and forwards the request.
You get an ordinary completion back, about redacted text.Tool / function calling
Tools are forwarded untouched — the tools you pass and the tool calls you get
back are the provider’s own. Guardrails inspect the request you send, which
includes the tool-result messages you append before the next call.
Gotchas
api_key:is your Vulnetix key. An OpenAI key here is the most common401.- There are two widely used gems with similar names. This page uses the official
openaigem (OpenAI::Client.new(api_key:, base_url:)). If your project uses the communityruby-openaigem instead, the equivalent knobs areOpenAI::Client.new(access_token:, uri_base:)— same two settings under different names. That gem appends the API version itself, so giveuri_basethe URL without/v1; what matters either way is that the request lands on…/{orgUuid}/v1/chat/completions. base_url:must include the/v1suffix.