curl & raw HTTP

There is no SDK here, so there is nothing to configure: you change the URL you POST to and the credential you send. This page is also the reference for any language without a client library — the gateway speaks the provider’s own wire format, so any HTTP client will do.

Configure

The CLI prints a runnable smoke test with your organisation UUID substituted in:

vulnetix ai-firewall snippet --lang sh --sdk curl --provider openai

Or by hand:

curl -s "https://guardrails.vulnetix.com/openai/$VULNETIX_ORG_UUID/v1/chat/completions" \
  -H "Authorization: Bearer $VULNETIX_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "model": "gpt-4o-mini",
    "messages": [{"role": "user", "content": "Hello"}]
  }'

The credential is your Vulnetix API key, not a provider key. Either header works, on every route:

Authorization: Bearer $VULNETIX_API_KEY
x-api-key: $VULNETIX_API_KEY

x-api-key exists because the Anthropic SDK sends it by default. Use whichever your HTTP client makes easiest.

The routes

ProviderURL
openaihttps://guardrails.vulnetix.com/openai/{orgUuid}/v1/chat/completions
openai (Responses API)https://guardrails.vulnetix.com/openai/{orgUuid}/v1/responses
anthropichttps://guardrails.vulnetix.com/anthropic/{orgUuid}/v1/messages
everything elsehttps://guardrails.vulnetix.com/{providerSlug}/{orgUuid}/v1/chat/completions
Note Writing the URL out in full, as here, there is no /v1 asymmetry to worry about — that quirk only exists because the Anthropic SDK appends /v1 to whatever base URL you give it. With curl you always type the /v1 yourself.

Model names pass through verbatim: gpt-4o-mini on openai, openai/gpt-4o-mini on openrouter, claude-sonnet-5 on anthropic.

Streaming

Add "stream": true and read the SSE stream. Use --no-buffer so curl prints events as they arrive rather than at the end.

curl -s --no-buffer "https://guardrails.vulnetix.com/openai/$VULNETIX_ORG_UUID/v1/chat/completions" \
  -H "Authorization: Bearer $VULNETIX_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"model":"gpt-4o-mini","messages":[{"role":"user","content":"Hello"}],"stream":true}'

Handling a guardrail block

A refused request never reaches the provider. It returns HTTP 403 with a body in the dialect of the route you called. On an OpenAI route:

{
  "error": {
    "message": "request blocked by AI firewall policy: guardrail \"No connection strings\" matched",
    "type": "policy_violation",
    "code": "request_blocked",
    "blocked_by": "No connection strings",
    "violations": [
      {
        "policy_uuid": "…",
        "policy_name": "No connection strings",
        "rule_type": "blocked_pattern",
        "action": "block",
        "detail": "…"
      }
    ]
  }
}

On an Anthropic route the same block arrives in Anthropic’s shape:

{
  "type": "error",
  "error": {
    "type": "permission_error",
    "code": "request_blocked",
    "message": "request blocked by AI firewall policy: …",
    "blocked_by": "No connection strings"
  }
}

Script against error.code, never against the message text:

codeStatusMeaning
request_blocked403A content guardrail matched.
provider_denied403Policy does not allow this provider.
model_denied / model_not_allowed403Policy does not allow this model.
provider_key_missing403No key in the vault for this provider.
unsupported_api404Wrong surface for this provider — the message names the right one.

Checking it in a shell script:

body=$(mktemp)
status=$(curl -s -o "$body" -w '%{http_code}' \
  "https://guardrails.vulnetix.com/openai/$VULNETIX_ORG_UUID/v1/chat/completions" \
  -H "Authorization: Bearer $VULNETIX_API_KEY" \
  -H "Content-Type: application/json" \
  -d "$payload")

if [ "$status" = "403" ]; then
  code=$(jq -r '.error.code' "$body")
  rule=$(jq -r '.error.blocked_by // "n/a"' "$body")
  printf 'AI firewall refused this request: %s (rule: %s)\n' "$code" "$rule" >&2
  exit 1
fi
Note Redaction returns 200, not 403. A pii_redact rule rewrites the matched spans in your prompt to the literal [REDACTED] and forwards it. There is no error and no header to check — you get an ordinary completion, about redacted text.

Tool / function calling

Send tools and tool_choice exactly as the provider documents them; they are forwarded untouched. Guardrails inspect the request body you send, which includes the tool-result messages you send back on the next turn.

Gotchas

  • A 200 with a completion when you expected a 403 usually means the request did not go through the gateway at all. Check the host in your URL.
  • The error envelope always matches the route you called, never the provider. An Anthropic-route request gets an Anthropic-shaped error even when the failure is “this provider does not serve the Messages API”.
  • Do not put the API key in the URL or in shell history — read it from an environment variable, as above.