API reference
Base: https://guardrails.vulnetix.com
Authentication
Every inference and listing endpoint requires your organisation’s Vulnetix API key, in either header:
Authorization: Bearer <vulnetix-api-key>
x-api-key: <vulnetix-api-key>
Both are accepted on every route, so OpenAI SDKs (which send the first) and Anthropic SDKs (which send the second) both work without configuration.
This is not a provider key. Provider keys live in the vault and are injected by the gateway. Your credential is stripped before the request goes upstream.
Endpoints
| Method + path | Surface | Purpose |
|---|---|---|
POST /{provider}/{org}/v1/chat/completions | chat | Chat completions, streaming or not |
POST /{provider}/{org}/v1/responses | responses | The OpenAI Responses API (Codex) |
POST /{provider}/{org}/v1/messages | messages | The Anthropic Messages API (Claude Code, Claude Desktop) |
POST /{provider}/{org}/v1/messages/count_tokens | messages | Token counting |
GET /{provider}/{org}/v1/models | any | The model catalog, filtered by your policy |
GET /{provider}/{org}/v1/nonces | any | Signed nonces for a client’s sealed blocks (see Nonces) |
POST /kiro/{org}/v1/generateAssistantResponse | kiro | Kiro’s API. The gateway mints the Kiro token from the vault’s Kiro sign-in, and the response is a binary AWS event stream |
GET /kiro/{org}/v1/ListAvailableModels | kiro | Kiro’s live model list, filtered by your model policy |
GET /healthz | — | Liveness. No auth. |
GET /readyz | — | Readiness. No auth. |
{provider} is a provider slug. {org} is your
organisation UUID.
Anthropic clients use a base URL with no /v1 — the SDK appends it:
https://guardrails.vulnetix.com/anthropic/{orgUuid}
Everything else takes the /v1 form. See
base URLs.
{provider} may also be an organisation’s own custom provider, which only that organisation can address; an unknown slug and another organisation’s slug get the same 404.
A provider only serves the surfaces it supports. Asking for another is a 404
unsupported_api. See the provider catalog.
Request bodies
Bodies are passed through. The firewall types only the few fields it must reason
about — model, the messages, and stream — and preserves every other top-level
field verbatim. temperature, tools, response_format, max_tokens,
reasoning, cache_control, and anything a provider adds tomorrow all survive the
round trip untouched.
The only modification the firewall ever makes is a redaction rewriting matched text.
Streaming
Set stream: true (or the surface’s equivalent). The response is relayed as
Server-Sent Events, frame by frame, as it arrives. Nothing is buffered or
reassembled.
Streaming works on every surface. A Kiro response is always an AWS event stream (application/vnd.amazon.eventstream), relayed byte for byte.
count_tokens
POST /{provider}/{org}/v1/messages/count_tokens runs the full guardrail
pipeline.
That is deliberate: the endpoint ships your entire prompt upstream, so exempting it
would be a trivial way to walk a block rule around the firewall. A prompt that would
be blocked on /v1/messages is blocked here too.
It is not an inference, so it is never written to the inference log, even with logging enabled.
GET /v1/models
Served from the model catalog, filtered by your organisation’s
model policy. It never calls the provider, so it
needs no stored provider key — only an explicit provider deny blocks it.
Rendered in the shape the provider’s surface implies. See the models endpoint.
Limits
| Limit | Value |
|---|---|
| Request body | 8 MiB — exceeded returns 413 request_too_large |
| Upstream timeout | 120s for non-streaming requests. Streams are not bounded by it. |
There are no rate limits and no quotas. See limitations.
Headers
Forwarded upstream: everything your client sends, except the ones below.
Never forwarded:
Authorizationandx-api-key— your Vulnetix credential. It authenticates you to us and must never reach a third party. The provider gets its own key from the vault instead.Accept-Encoding— stripped so the gateway can read the response to count tokens. Compression still happens; it is negotiated by the gateway.- Hop-by-hop headers, per RFC 7230.
Defaulted, not overridden: a provider’s credential may specify headers that carry
no key — anthropic-version, for instance. Those are defaults: the gateway sets them
only if you did not. If you deliberately request a newer API version, yours wins.
Never forwarded, either way: any X-Vulnetix-* header. Your client’s (such as
X-Vulnetix-Nonce-Mode) is for the gateway, and a provider’s is dropped from its
response so it cannot pose as ours.
Response headers
Every response the gateway answers carries what it did. The values are facts: a decision, rule names, counts and an id. They never include the text a rule matched.
| Header | Value |
|---|---|
X-Vulnetix-Firewall-Request-Id | A UUID for this request, on every response, refusals included. Quote it to support. |
X-Vulnetix-Firewall-Decision | allow, redact, flag or block: the guardrail outcome, set once the guardrails have run. |
X-Vulnetix-Firewall-Rules | The names of the rules that matched, percent-encoded and comma-separated. At most 8 names or 512 bytes. |
X-Vulnetix-Firewall-Redactions | How many redacting rules matched. |
X-Vulnetix-Firewall-Stripped | How many tools or MCP servers a strip rule removed. |
X-Vulnetix-Firewall-Nonce | Only when the request carried sealed blocks: mode=enforce|observe;verified=N;unknown=N;tampered=N;stripped=N. See Nonces. |
On a stream the headers arrive with the first byte, before any token.
Legacy form
POST /{provider}/v1/chat/completions
X-Organisation-UUID: <org uuid>
The organisation may be supplied as a header instead of a path component. It still works, but the URL form is preferred because most LLM clients cannot send custom headers — which is the entire reason the org is in the path.
Errors
Full taxonomy, in both dialects: error reference.
Health
GET /healthz — the process is up.
GET /readyz — the process is up and the database is reachable. Returns 503
otherwise.
Neither requires authentication.
Nonces
GET /{provider}/{org}/v1/nonces?count=16
This endpoint issues nonces a harness such as Belai uses to seal the blocks it writes into a conversation:
<kind nonce="…" integrity="sha256-of-content">…</kind>
It is authenticated like the inference routes, gated by the provider allow/deny
list, and never calls upstream. count is 1–64 (default 16). The answer is:
{"nonces": ["…"], "count": 16, "expires_at": "2026-09-28T10:00:00Z"}
A nonce is signed, not stored. It is random bytes plus an expiry (24 hours), authenticated with an HMAC keyed by your principal’s own secret. Any gateway replica can verify it, nothing about it is written anywhere, and a nonce issued to one principal never verifies for another.
On each inference the gateway checks every sealed block of the harness’s known
kinds (system, agent, plan, goal, tools, skills, hooks, attachment,
exploration, directive, diagnostics). Each block is one of:
- verified: the nonce was issued to you and is unexpired, and the integrity hash matches;
- unknown: the nonce was never issued to you, or has expired;
- tampered: the integrity hash does not match, or an
attachment,directiveordiagnosticsblock has none.
What happens then depends on the client:
- Observe (the default): blocks are counted and nothing is removed. A client that mints its own nonces is never broken.
- Enforce: a client that used only this gateway’s nonces sends
X-Vulnetix-Nonce-Mode: enforce. Unknown and tampered blocks are then stripped before the request is inspected or forwarded, so a forged “system” block pasted into a file or a web page never reaches the model. - A guardrail of rule type
delimiter_integritywith actionblockrefuses a request carrying a tampered block, with codedelimiter_tampered; withflagit records one.
The outcome is reported in X-Vulnetix-Firewall-Nonce (see
response headers).