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
| Provider | URL |
|---|---|
openai | https://guardrails.vulnetix.com/openai/{orgUuid}/v1/chat/completions |
openai (Responses API) | https://guardrails.vulnetix.com/openai/{orgUuid}/v1/responses |
anthropic | https://guardrails.vulnetix.com/anthropic/{orgUuid}/v1/messages |
| everything else | https://guardrails.vulnetix.com/{providerSlug}/{orgUuid}/v1/chat/completions |
/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:
code | Status | Meaning |
|---|---|---|
request_blocked | 403 | A content guardrail matched. |
provider_denied | 403 | Policy does not allow this provider. |
model_denied / model_not_allowed | 403 | Policy does not allow this model. |
provider_key_missing | 403 | No key in the vault for this provider. |
unsupported_api | 404 | Wrong 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
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
200with a completion when you expected a403usually 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.