Elixir

Elixir has no first-party OpenAI SDK. This page uses openai_ex, the community-maintained client most Elixir projects use. It is built by piping a client struct through configuration functions, so routing through the firewall is one extra pipe — with_base_url/2 — plus the Vulnetix key instead of a provider key.

Install

# mix.exs
def deps do
  [{:openai_ex, "~> 0.9"}]
end

Configure

alias OpenaiEx.Chat
alias OpenaiEx.ChatMessage

org = System.fetch_env!("VULNETIX_ORG_UUID")

openai =
  System.fetch_env!("VULNETIX_API_KEY")   # Vulnetix key, not a provider key
  |> OpenaiEx.new()
  |> OpenaiEx.with_base_url("https://guardrails.vulnetix.com/openai/#{org}/v1")

chat_req =
  Chat.Completions.new(
    model: "gpt-4o-mini",
    messages: [ChatMessage.user("Hello")]
  )

{:ok, response} = Chat.Completions.create(openai, chat_req)
IO.puts(get_in(response, ["choices", Access.at(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; pass stream: true and consume the chunk stream as you already do.

{:ok, chat_stream} = Chat.Completions.create(openai, chat_req, stream: true)

chat_stream.body_stream
|> Stream.flat_map(& &1)
|> Enum.each(fn %{data: d} ->
  d |> get_in(["choices", Access.at(0), "delta", "content"]) |> IO.write()
end)

Handling a guardrail block

A request your policy refuses never reaches the provider. It returns an OpenAI-shaped 403, which openai_ex returns as {:error, %OpenaiEx.Error{}} — the struct carries status_code, code, and the decoded body.

case Chat.Completions.create(openai, chat_req) do
  {:ok, response} ->
    handle(response)

  {:error, %OpenaiEx.Error{status_code: 403, code: code, body: body}} ->
    rule = get_in(body, ["error", "blocked_by"])

    case code do
      "request_blocked" ->
        {:error, "AI firewall blocked this request (rule: #{rule})"}

      # provider_denied, model_denied, model_not_allowed, provider_key_missing
      _ ->
        {:error, "AI firewall policy refused this call: #{code}"}
    end

  {:error, err} ->
    {:error, err}
end

code is what tells 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_deniedPolicy does not allow this provider.
model_denied / model_not_allowedPolicy does not allow this model.
provider_key_missingNo provider key in the vault for this provider.
Note Redaction is silent — you get {:ok, …}. A pii_redact rule rewrites the matched spans in your prompt to the literal [REDACTED] and forwards the request. There is no error tuple to match on; the completion simply concerns redacted text.

Tool / function calling

Tools are forwarded untouched. Guardrails inspect the request you send, which includes the tool-result messages you add to the message list before the next call.

Gotchas

  • OpenaiEx.new/1 takes your Vulnetix key. A provider key here is the most common 401.
  • with_base_url/2 must include the /v1 suffix.
  • Chat.Completions.create!/2 raises instead of returning a tuple — if you use the bang variant, rescue OpenaiEx.Error and read the same status_code and code fields.
  • This is a community library. If you use a different one, the two settings are the same: point the base URL at …/{orgUuid}/v1 and send the Vulnetix key.