Proxy Endpoint
Change one URL. SignalVault sits in front of OpenAI chat completions and Anthropic messages, logging and checking every call — no SDK required.
The proxy is the fastest integration path: point your existing OpenAI or Anthropic client at SignalVault and every request is automatically audited and checked against your guardrail rules.
OpenAI
Set baseURL to
https://api.signalvault.io/proxy/openai/v1
and add your SignalVault key as a default header. Everything else stays the same.
import OpenAI from 'openai';
const client = new OpenAI({
apiKey: process.env.OPENAI_API_KEY,
baseURL: 'https://api.signalvault.io/proxy/openai/v1',
defaultHeaders: {
'X-SignalVault-Key': 'sk_live_...',
},
});
const response = await client.chat.completions.create({
model: 'gpt-6-astra',
messages: [{ role: 'user', content: 'Hello!' }],
});
Anthropic
Set baseURL to
https://api.signalvault.io/proxy/anthropic
and add your SignalVault key as a default header. Leave out /v1: the Anthropic SDK adds /v1/messages itself.
import Anthropic from '@anthropic-ai/sdk';
const client = new Anthropic({
apiKey: process.env.ANTHROPIC_API_KEY,
baseURL: 'https://api.signalvault.io/proxy/anthropic',
defaultHeaders: {
'X-SignalVault-Key': 'sk_live_...',
},
});
const message = await client.messages.create({
model: 'claude-sonnet-5',
max_tokens: 1024,
messages: [{ role: 'user', content: 'Hello!' }],
});
Supported endpoints
| Proxy path | Upstream |
|---|---|
| POST /proxy/openai/v1/chat/completions | api.openai.com/v1/chat/completions |
| POST /proxy/anthropic/v1/messages | api.anthropic.com/v1/messages |
Headers
| Header | Required | Default | Notes |
|---|---|---|---|
| X-SignalVault-Key | Yes | — | Your SignalVault API key (sk_live_...) |
| Authorization | OpenAI: yes | — | Your provider key as Bearer sk-.... Accepted for Anthropic too, as Bearer sk-ant-..., and it wins if both headers are present. |
| x-api-key | Anthropic: one of the two | — | Your Anthropic key. This is what the Anthropic SDK sends when you set apiKey, so the standard client works unchanged. Ignored on the OpenAI route. |
| X-SignalVault-Environment | No | your key's environment |
Must match the environment your API key was created for — the key is authoritative, and a value that disagrees is ignored. Create a key per environment to separate them. |
| X-SignalVault-Metadata | No | {} |
JSON string stored in event metadata (e.g. user ID, session ID) |
What happens to your provider key
The proxy reads your provider key from the request, uses it for that one upstream call, and passes the provider's response back. It is not written to the database, and it is not part of the event SignalVault stores: audit events hold the prompt, the response and the metadata, never your credentials. Nothing is cached between requests, so revoking a key at your provider takes effect immediately.
Your SignalVault key is separate and is stored only as a hash — see Security for how both are handled.
Streaming
Streaming is fully supported. Pass stream: true as normal.
SignalVault pipes SSE chunks straight through to your client and logs the full response once the stream
completes.
For streamed OpenAI calls, token usage and cost are only recorded if the request asks
for them: send stream_options: {"include_usage": true}.
OpenAI omits usage from a stream otherwise, and the proxy does not add the flag for you, so the event
is logged with zero tokens and zero cost. Both SignalVault SDKs set it for you. Anthropic streams
carry usage either way.
Budgets are enforced here
Unlike the events API, the proxy refuses a call once a budget is exceeded, because it is the component actually making the provider request. The check runs after the guardrails and before the call goes upstream, and returns 402:
HTTP 402
{
"error": {
"message": "Monthly spend budget exceeded. Adjust your budget in app settings to continue.",
"type": "budget_exceeded",
"code": "monthly_usd"
}
}
code is
monthly_usd or
daily_tokens. See
Budget controls.
Blocked requests
When a guardrail rule with action = block or
action = redact matches the prompt,
the proxy returns a 400 before forwarding to the upstream provider. The response uses the standard OpenAI error shape so existing error-handling code works without changes:
HTTP 400
{
"error": {
"message": "Request blocked by SignalVault guardrail (contains_pii). Adjust the rule in your dashboard.",
"type": "invalid_request_error",
"code": "content_policy_violation",
"violations": [
{"rule_id": "...", "rule_type": "contains_pii", "action": "block", "severity": 5}
],
"dashboard_url": "https://signalvault.io/dashboard/apps/APP_ID/rules"
}
}
In proxy mode, redact rules block the request rather than forwarding a redacted version to the provider.
The violation is still logged and visible in your dashboard.
Tool call logging
When a response contains tool calls — Anthropic tool_use blocks or
OpenAI tool_calls — the proxy extracts each one and records it as an
agent.tool_call event in your audit log, with no code change. The
response itself is passed through to your application unchanged.
These events appear alongside the parent ai.request and
ai.response pair, with the tool name, its arguments and how long
it took.
Tool call logging works for both streaming and non-streaming responses for both Anthropic and OpenAI.
Tool use guardrails
Your rules are also evaluated against the tool calls the model wants to make — before your
application receives the response. This covers both providers and both modes: Anthropic
tool_use blocks and OpenAI
tool_calls, streaming and non-streaming.
On a non-streaming response, a rule with
action = block replaces the response with a
400 before your code can act on the tool call:
HTTP 400
{
"error": {
"message": "Tool call blocked by SignalVault guardrail (contains_secret) (tool: send_email). Adjust the rule in your dashboard.",
"type": "invalid_request_error",
"code": "tool_use_blocked",
"violations": [
{"rule_id": "...", "rule_type": "contains_secret", "action": "block", "severity": 7,
"tool_name": "send_email"}
],
"dashboard_url": "https://signalvault.io/dashboard/apps/APP_ID/rules"
}
}
On a streaming response the status line and earlier chunks have already been sent, so
there is no status left to change. The stream instead ends with a synthetic refusal in the provider's
own shape — for OpenAI an assistant delta reading
Tool call blocked by SignalVault guardrail. with
finish_reason: "stop", and for Anthropic a text block naming the
rule and the tool — and the blocked tool call is never emitted. Your client sees a complete stream that
contains no tool call, so code that only reads text deltas keeps working.
A redact rule on a tool call rewrites the arguments in place and lets
the call through, rather than refusing it. Either way the violation is recorded.
Proxy events look like SDK events in your dashboard — same request/response pairing, same rule types,
same export format. What differs is enforcement: the proxy is making the call, so it can refuse one.
A redact match and an exceeded budget both stop a proxied
request, while through the SDKs they are recorded and the call proceeds.