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 + pathSurfacePurpose
POST /{provider}/{org}/v1/chat/completionschatChat completions, streaming or not
POST /{provider}/{org}/v1/responsesresponsesThe OpenAI Responses API (Codex)
POST /{provider}/{org}/v1/messagesmessagesThe Anthropic Messages API (Claude Code, Claude Desktop)
POST /{provider}/{org}/v1/messages/count_tokensmessagesToken counting
GET /{provider}/{org}/v1/modelsanyThe model catalog, filtered by your policy
GET /{provider}/{org}/v1/noncesanySigned nonces for a client’s sealed blocks (see Nonces)
POST /kiro/{org}/v1/generateAssistantResponsekiroKiro’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/ListAvailableModelskiroKiro’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.

Warning

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

LimitValue
Request body8 MiB — exceeded returns 413 request_too_large
Upstream timeout120s 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:

  • Authorization and x-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.

HeaderValue
X-Vulnetix-Firewall-Request-IdA UUID for this request, on every response, refusals included. Quote it to support.
X-Vulnetix-Firewall-Decisionallow, redact, flag or block: the guardrail outcome, set once the guardrails have run.
X-Vulnetix-Firewall-RulesThe names of the rules that matched, percent-encoded and comma-separated. At most 8 names or 512 bytes.
X-Vulnetix-Firewall-RedactionsHow many redacting rules matched.
X-Vulnetix-Firewall-StrippedHow many tools or MCP servers a strip rule removed.
X-Vulnetix-Firewall-NonceOnly 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, directive or diagnostics block 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_integrity with action block refuses a request carrying a tampered block, with code delimiter_tampered; with flag it records one.

The outcome is reported in X-Vulnetix-Firewall-Nonce (see response headers).