Go

The official openai-go SDK is configured with request options. You add two — option.WithBaseURL and option.WithAPIKey — and the rest of your code is unchanged. The gateway holds your provider key server-side, so the only credential this binary needs is the Vulnetix API key.

Install

go get github.com/openai/openai-go

Configure

The CLI prints the snippet with your organisation UUID already substituted in:

vulnetix ai-firewall snippet --lang go --sdk openai --provider openai

Or by hand:

package main

import (
	"context"
	"fmt"
	"os"

	"github.com/openai/openai-go"
	"github.com/openai/openai-go/option"
)

func main() {
	client := openai.NewClient(
		option.WithBaseURL("https://guardrails.vulnetix.com/openai/"+os.Getenv("VULNETIX_ORG_UUID")+"/v1"),
		option.WithAPIKey(os.Getenv("VULNETIX_API_KEY")), // Vulnetix key, not an OpenAI key
	)

	resp, err := client.Chat.Completions.New(context.Background(), openai.ChatCompletionNewParams{
		Model: "gpt-4o-mini",
		Messages: []openai.ChatCompletionMessageParamUnion{
			openai.UserMessage("Hello"),
		},
	})
	if err != nil {
		panic(err)
	}
	fmt.Println(resp.Choices[0].Message.Content)
}

Swap openai in the path for any other provider slug and use 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.NewStreaming(context.Background(), openai.ChatCompletionNewParams{
	Model:    "gpt-4o-mini",
	Messages: []openai.ChatCompletionMessageParamUnion{openai.UserMessage("Hello")},
})
for stream.Next() {
	chunk := stream.Current()
	if len(chunk.Choices) > 0 {
		fmt.Print(chunk.Choices[0].Delta.Content)
	}
}
if err := stream.Err(); err != nil {
	panic(err)
}

Handling a guardrail block

A request your policy refuses never reaches the provider. It returns an OpenAI-shaped 403, which the SDK surfaces as *openai.Error. The SDK parses the standard fields (Code, Message, Type) for you; blocked_by is a Vulnetix addition, so read it out of RawJSON().

import (
	"encoding/json"
	"errors"
	"net/http"
)

resp, err := client.Chat.Completions.New(ctx, params)
if err != nil {
	var apierr *openai.Error
	if errors.As(err, &apierr) && apierr.StatusCode == http.StatusForbidden {
		var detail struct {
			BlockedBy  string `json:"blocked_by"`
			Violations []struct {
				PolicyName string `json:"policy_name"`
				RuleType   string `json:"rule_type"`
				Action     string `json:"action"`
			} `json:"violations"`
		}
		_ = json.Unmarshal([]byte(apierr.RawJSON()), &detail)

		switch apierr.Code {
		case "request_blocked":
			return fmt.Errorf("AI firewall blocked this request (rule %q): %s", detail.BlockedBy, apierr.Message)
		case "provider_denied", "model_denied", "model_not_allowed", "provider_key_missing":
			return fmt.Errorf("AI firewall policy refused this call (%s): %s", apierr.Code, apierr.Message)
		}
	}
	return err
}

apierr.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.
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 — there is no error to handle. A pii_redact rule rewrites the matched spans in your prompt to the literal [REDACTED] and forwards the request. err is nil, you get an ordinary completion, and the only sign is that the model is talking about redacted text.

Tool / function calling

Tools, ToolChoice, and the ToolCalls you get back are all forwarded untouched. Guardrails inspect the request you send, which includes the tool-result messages you append and send on the next turn — so a tool that returns a secret produces a request the firewall can block or redact.

Gotchas

  • WithAPIKey takes your Vulnetix key. An OpenAI key here is the most common 401. The provider key lives in the vault.
  • openai.NewClient returns a Client value, not a pointer — assign it with := and pass &client if a helper wants a pointer.
  • apierr.RawJSON() returns the raw error object from the body, which is where blocked_by and violations live. apierr.DumpResponse(true) is useful when you are debugging a base URL rather than a policy.
  • option.WithBaseURL must include the /v1 suffix.