Guardrails is a drop-in proxy. Your existing AI code doesn't change — you change one URL and one key, and every call is audited.
We wrote a plain-English setup guide that explains everything step by step — including a copy-paste prompt you can give to Claude, ChatGPT, or Copilot and let the AI make the change for you. If anything goes wrong, removing one line instantly puts your app back the way it was.
Download the Easy Setup GuideUsing Claude, ChatGPT, Cursor, or Copilot? Paste this prompt and replace the two keys — the assistant finds the right file and makes the change:
Find where this project creates its AI client (OpenAI, Anthropic, Gemini, or any OpenAI-compatible SDK). - If it uses the OpenAI SDK (or an OpenAI-compatible one): set the base URL to https://aegisguardrails.com/v1, set the API key to <my Guardrails key>, and add a default header X-Provider-Key: <my existing AI provider key>. - If it uses the Anthropic SDK: set the base URL to https://aegisguardrails.com/anthropic and add default headers X-Audit-Key: <my Guardrails key> and X-Provider-Key: <my Anthropic key>. Don't change anything else.
Sign up at aegisguardrails.com/signup. Your GUARDRAILS_API_KEY will be shown in your dashboard.
One OpenAI-compatible endpoint serves every major model. Use the official OpenAI SDK — or anything that speaks the OpenAI chat-completions format — and switch models just by changing model. Claude requests are translated to Anthropic's API and back automatically, so every reply comes back in the same OpenAI shape.
Two changes: the base URL becomes https://aegisguardrails.com/v1, and the API key becomes your Guardrails key. Your provider key goes in an X-Provider-Key header — or save it once in the console under Settings → AI Provider Keys and leave the header out.
import OpenAI from 'openai'
const client = new OpenAI({
baseURL: 'https://aegisguardrails.com/v1',
apiKey: process.env.GUARDRAILS_API_KEY, // your gr_ key
defaultHeaders: {
'X-Provider-Key': process.env.OPENAI_API_KEY, // optional if saved in the console
'X-Actor-Id': getCurrentUserId(), // optional
},
})
const reply = await client.chat.completions.create({
model: 'gpt-4o-mini', // or 'claude-sonnet-4-6', 'gemini-2.5-flash', 'grok-2', …
messages: [{ role: 'user', content: 'Hello' }],
})from openai import OpenAI
import os
client = OpenAI(
base_url="https://aegisguardrails.com/v1",
api_key=os.environ["GUARDRAILS_API_KEY"],
default_headers={"X-Provider-Key": os.environ["OPENAI_API_KEY"]},
)
reply = client.chat.completions.create(
model="gemini-2.5-flash",
messages=[{"role": "user", "content": "Hello"}],
)curl https://aegisguardrails.com/v1/chat/completions \
-H "Authorization: Bearer $GUARDRAILS_API_KEY" \
-H "X-Provider-Key: $OPENAI_API_KEY" \
-H "Content-Type: application/json" \
-d '{"model":"gpt-4o-mini","messages":[{"role":"user","content":"Hello"}]}'Guardrails works out the provider from the model name. Common prefixes are recognised automatically; for anything else, put the provider in front with a slash — e.g. deepseek/deepseek-chat or openai/my-fine-tune. GET /v1/models lists the common models.
| Provider | Recognised automatically | Explicit prefix |
|---|---|---|
| OpenAI | gpt-*, o1 / o3 / o4…, chatgpt-* | openai/ |
| Anthropic | claude-* | anthropic/ or claude/ |
| Google Gemini | gemini-* | gemini/ or google/ |
| xAI Grok | grok-* | grok/ or xai/ |
| Meta Llama | llama*, meta-llama* | llama/ or meta/ |
| DeepSeek | deepseek-* | deepseek/ |
| Alibaba Qwen | qwen* | qwen/ or alibaba/ |
The provider key you send must belong to the provider of the model you pick. To mix providers from one client, save each provider's key in the console and drop the X-Provider-Key header — Guardrails picks the right one per call.
Set stream: true exactly as you would against the provider. Tokens are relayed the moment they arrive, and when the stream finishes Guardrails rebuilds the full reply and audits it like any other call — same hash, signature and on-chain anchor.
const stream = await client.chat.completions.create({
model: 'gpt-4o-mini',
stream: true,
messages: [{ role: 'user', content: 'Write a haiku' }],
})
for await (const chunk of stream) {
process.stdout.write(chunk.choices[0]?.delta?.content ?? '')
}| Models | Streaming |
|---|---|
| OpenAI, Gemini, Grok, Llama, DeepSeek, Qwen | Supported — on /v1 and on their provider routes |
| Claude (Anthropic) | Not yet — the reply arrives in one piece. Use stream: false for Claude models for now. |
| Azure OpenAI, AWS Bedrock | Not yet — the reply arrives in one piece. |
stream_options: { include_usage: true }.Already using a provider's own SDK? Point it at that provider's route instead and add two headers. Nothing else changes.
// Anthropic SDK
const client = new Anthropic({
apiKey: process.env.ANTHROPIC_API_KEY,
baseURL: 'https://aegisguardrails.com/anthropic',
defaultHeaders: {
'X-Audit-Key': process.env.GUARDRAILS_API_KEY,
'X-Provider-Key': process.env.ANTHROPIC_API_KEY,
'X-Actor-Id': getCurrentUserId(), // optional
'X-Session-Id': getSessionId(), // optional
},
})
// OpenAI SDK, provider route (same idea for /grok, /gemini, /deepseek, /qwen, /llama)
const client = new OpenAI({
apiKey: process.env.OPENAI_API_KEY,
baseURL: 'https://aegisguardrails.com/openai',
defaultHeaders: {
'X-Audit-Key': process.env.GUARDRAILS_API_KEY,
'X-Provider-Key': process.env.OPENAI_API_KEY,
},
})| Base URL | Provider | Extra headers |
|---|---|---|
| /anthropic | Anthropic (Messages API) | — |
| /openai | OpenAI | — |
| /gemini | Google Gemini (OpenAI-compatible) | — |
| /grok | xAI Grok | — |
| /llama | Meta Llama API | — |
| /deepseek | DeepSeek | — |
| /qwen | Alibaba Qwen (Model Studio) | — |
| /azure | Azure OpenAI | X-Azure-Resource, X-Azure-Deployment, X-Azure-API-Version (optional) |
| /aws | AWS Bedrock | X-AWS-Access-Key-Id, X-AWS-Secret-Access-Key, X-AWS-Region; model in the body or X-AWS-Model-Id |
There's no Guardrails SDK to install — you keep the client you already use. What works today:
/openai. Tested end to end, including streaming./anthropic route, non-streaming.ChatOpenAI, the Vercel AI SDK's OpenAI-compatible provider, LlamaIndex, LiteLLM — should work through /v1 with the same base URL, key and header as above. These aren't tested by us yet, so check a call shows up in your console before going live.Not supported yet: the OpenAI Responses and Embeddings APIs, Gemini's native SDK (use Gemini through /v1 or /gemini instead), and streaming for Claude, Azure and Bedrock.
Every non-streamed response includes an _audit field:
const reply = await client.chat.completions.create({ ... })
console.log(reply._audit)
// {
// event_id: 'a3f8b21c-...',
// hash: '3a7bd3e2...',
// ledger_status: 'pending' // → 'committed' after ~30s
// }On a streamed response the same object arrives as an SSE comment line just before data: [DONE]. SDKs skip comment lines, so nothing breaks; if you read the raw stream yourself, look for the line that starts with : _audit:
data: {"id":"chatcmpl-…","choices":[{"delta":{"content":"lo"}, …}]}
: _audit {"event_id":"a3f8b21c-…","hash":"3a7bd3e2…","ledger_status":"pending"}
data: [DONE]Guardrails sits in the path of every AI call you make, so the first question worth asking is: what happens if Guardrails itself has a problem? The answer is fail open — your AI call is never blocked by an audit-layer failure.
Internally, each call has two stages: forwarding the request to your AI provider, then fingerprinting, signing, and storing the audit record. Those stages fail differently, and we handle them differently:
When the audit pipeline is the part that failed, the _audit field tells you so instead of silently going missing:
// Audit pipeline failure — your AI response is still returned in full
{
..., // your normal AI response, unaffected
"_audit": {
"event_id": null,
"hash": null,
"ledger_status": "audit_failed",
"warning": "Guardrails could not record this call due to an internal error.
Your AI response was still returned. This call is not covered
by your audit trail."
}
}We recommend checking _audit.ledger_status for "audit_failed"if you want to alert on gaps in your audit coverage — but you never need to handle it defensively the way you'd handle a real API error, because your application keeps working either way.
There is one failure mode this can't cover: if our service is fully unreachable (network partition, DNS issue), no request reaches us at all. For that case, the same one-line design that makes installation easy also makes a fallback easy — point your client at the provider directly on catch:
// Optional client-side fallback if Guardrails is unreachable
let response
try {
response = await guardrailsClient.messages.create({ ... })
} catch (err) {
if (isConnectionError(err)) {
response = await directAnthropicClient.messages.create({ ... }) // unaudited fallback
} else {
throw err
}
}Any record can be publicly verified — no login required:
GET https://aegisguardrails.com/verify/{event_id}
// Response
{
"eventId": "a3f8b21c-...",
"hash": "3a7bd3e2...",
"platformSigValid": true,
"customerSigValid": true,
"onChain": true,
"blockHeight": 41823,
"tampered": false
}Every record is already signed by our platform key. For a second, independent signature that neither side can forge alone, generate a signing key pair in your console under Settings → Signing Key. The private key is generated in your browser, downloaded straight to your machine, and never sent to us — only the public key is registered with your account.
After a proxied call, you already have _audit.hash. Sign that hash with your private key (P-256 / ECDSA, SHA-256, IEEE-P1363 signature encoding) and submit it back — this is meant to run in your own backend, not the browser:
import { createSign, createPrivateKey } from 'crypto'
// privateKeyJwk is the JSON file downloaded when you generated your signing key
const key = createPrivateKey({ key: privateKeyJwk, format: 'jwk' })
const sign = createSign('SHA256')
sign.update(Buffer.from(response._audit.hash, 'hex'))
sign.end()
const signature = sign.sign({ key, dsaEncoding: 'ieee-p1363' }).toString('hex')
await fetch(`https://aegisguardrails.com/events/${response._audit.event_id}/cosign`, {
method: 'POST',
headers: { 'X-Audit-Key': process.env.GUARDRAILS_API_KEY, 'Content-Type': 'application/json' },
body: JSON.stringify({ signature }),
})Once submitted, GET /verify/{event_id} reports customerSigValid: true alongside the platform signature — a regulator can confirm both independently, without ever needing your private key.
Everything above audits AI calls specifically. If you just need to anchor a hash you've already computed — a contract revision, a decision log entry, an export of records — POST /anchorseals it on the same Verilink'ed ledger, Merkle-batched and publicly verifiable exactly like an AI call:
POST https://aegisguardrails.com/anchor
X-Audit-Key: <your Guardrails key>
Content-Type: application/json
{ "hash": "3a7bd3e2...", "label": "contract-v4-signed" }
// Response
{ "event_id": "a3f8b21c-...", "hash": "3a7bd3e2...", "ledger_status": "pending" }The same GET /verify/{event_id} endpoint verifies it. This is the basis of Aegis Verify — useful anywhere you need a tamper-proof timestamp on something, independent of any AI provider.
Set a webhook URL in the console under Settings → Alerts to get notified when usage crosses 50/80/100% of your plan, or when a call's audit record couldn't be saved. Any Slack incoming-webhook URL works directly — the payload includes a top-level text field, which is all Slack reads:
{
"text": "Guardrails: you've used 80% of your plan's monthly call limit (40,000/50,000).",
"event": "usage_threshold",
"pct": 80
}| Header | Required | Description |
|---|---|---|
| X-Audit-Key | Yes* | Your Guardrails API key. *On /v1, Authorization: Bearer <Guardrails key> works instead — that is what the OpenAI SDK sends. |
| X-Provider-Key | Unless saved | Your AI provider's API key, forwarded to the provider. Optional if you've saved that provider's key in the console. |
| X-Actor-Id | No | User or service account ID — appears in the audit log |
| X-Session-Id | No | Groups a multi-turn conversation together |
| X-AI-System-Id | No | Which registered AI system this call belongs to — turns on its Policy Engine rules |
Azure and Bedrock take a few extra headers — see Provider routes.