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 pathUpstream
POST /proxy/openai/v1/chat/completions api.openai.com/v1/chat/completions
POST /proxy/anthropic/v1/messages api.anthropic.com/v1/messages

Headers

HeaderRequiredDefaultNotes
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.