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:
Code | Meaning |
|---|---|
request_blocked | A content guardrail matched. blocked_by names the rule. |
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. |
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
WithAPIKeytakes your Vulnetix key. An OpenAI key here is the most common401. The provider key lives in the vault.openai.NewClientreturns aClientvalue, not a pointer — assign it with:=and pass&clientif a helper wants a pointer.apierr.RawJSON()returns the rawerrorobject from the body, which is whereblocked_byandviolationslive.apierr.DumpResponse(true)is useful when you are debugging a base URL rather than a policy.option.WithBaseURLmust include the/v1suffix.