Govern Claude Code with DVARA
Claude Code sends every model call to Anthropic directly. Your security team
sees none of it: no policy, no PII check, no budget, no audit record. From 1.8.5
DVARA serves the Anthropic Messages API at POST /v1/messages, so you can point
Claude Code at DVARA. Every model call it makes is
then governed and billed as one DVARA workspace.
/v1/messages is available from 1.8.5.
On the Enterprise platform, workspace budgets,
IP access rules and the Flightdeck views apply to it too.
Point Claude Code at DVARA
You need three things:
- A running DVARA Gateway, 1.8.5 or later, that Claude Code can reach. The
2026-10-21API version needs 1.12.0 or later. - An Anthropic provider on that Gateway (
ANTHROPIC_API_KEYset on the Gateway), or a route for each model id Claude Code asks for. - A DVARA workspace API key. On the Enterprise platform, create it in
Flightdeck. In DVARA Open Source, generate it with
--generate-key. If the key has scopes, it needscompletions:write.
Set these where Claude Code runs. Replace <dvara-workspace-key> with your
DVARA key, not an Anthropic key:
export ANTHROPIC_BASE_URL=https://gateway.acme.example.com # the Gateway, no /v1
export ANTHROPIC_API_KEY=<dvara-workspace-key> # sent as x-api-key
# or: export ANTHROPIC_AUTH_TOKEN=<dvara-workspace-key> # sent as Authorization: Bearer
export CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC=1 # optional: no telemetry or update calls
ANTHROPIC_BASE_URLhas no/v1. Claude Code adds/v1/messagesitself.- Either key variable works. DVARA reads the key from
Authorization: Beareror, when that header is absent, fromx-api-key. It is the same key either way and has the same rights. WithANTHROPIC_API_KEY, an interactive session asks once to approve the key. - The DVARA key wins over a saved login. Set it even if Claude Code is signed in to a Claude subscription. See What DVARA does not govern.
Then start Claude Code as usual and ask it something.
Make sure the model ids route
Claude Code asks for Anthropic model ids. It also asks for a small background
model. Each id must be one the Gateway can route: an id served by its Anthropic
provider (any model that starts with claude), or an id with its own route.
If not, the call is refused with 400 no_provider.
Pin the ids Claude Code uses to ones your Gateway serves:
export ANTHROPIC_MODEL=<main-model-id>
export ANTHROPIC_DEFAULT_OPUS_MODEL=<opus-model-id>
export ANTHROPIC_DEFAULT_SONNET_MODEL=<sonnet-model-id>
export ANTHROPIC_DEFAULT_HAIKU_MODEL=<haiku-model-id> # the background model
The context window check uses the model's own window where DVARA knows it:
model_limits in gateway.yaml (dvara.llm-gateway.model-limits), or the
model catalogue on the Enterprise platform. For a model neither knows, it uses
Anthropic's declared window of 200,000 tokens. With the default hard threshold
of 90%, a request over 90% of that window is refused with
400 context_window_exceeded, or trimmed if the workspace sets a pruning
strategy. In 1.8.5 the check always used 200,000 tokens for Anthropic. See
Anthropic in Provider Setup.
What happens to a request on an Anthropic route?
On a route to Anthropic, DVARA sends the request on as Claude Code sent it:
every field, message role, content block and the anthropic-beta header. It
changes the request only where governance changes it, such as redacted text or
the routed model. Anthropic's reply, plain or streamed, comes back the same
way.
- Extended thinking works. The thinking settings and signed thinking blocks pass through unchanged. Thinking is output, so PII rules and guardrails read it.
anthropic-betapasses through. DVARA also writes it to theGATEWAY_RESPONSEaudit record asanthropic_beta.- Usage and cost come from Anthropic's own usage block, including cache reads, cache writes and reasoning tokens.
- A content block type DVARA does not read is passed on and counted in
gateway_anthropic_opaque_blocks_total.
On a route to another provider, DVARA translates the request. A request with
extended thinking is refused with 400 unsupported_capability, and the message
names the provider. Fields that only tune Anthropic, such as cache_control or
a field DVARA does not model, are left out and counted in
gateway_anthropic_fields_dropped_total. If your route sends Claude Code to
another provider, turn thinking off with CLAUDE_CODE_DISABLE_THINKING=1. Some
of the newest models do not allow that.
On every route, DVARA refuses with 400 unsupported_capability anything the
provider would act on outside the Gateway: mcp_servers, container, server
tools such as web search or code execution, and metadata keys other than
user_id.
Send a test call
You can call the endpoint yourself to check the setup. The
anthropic-version header is required, and so is max_tokens. DVARA serves
2023-06-01 and, from 1.12.0, 2026-10-21, the version the current Anthropic
SDKs send. Under 2026-10-21, temperature, top_p and top_k are refused,
as Anthropic refuses them:
curl -s https://gateway.acme.example.com/v1/messages \
-H "x-api-key: <dvara-workspace-key>" \
-H "anthropic-version: 2023-06-01" \
-H "content-type: application/json" \
-d '{
"model": "<sonnet-model-id>",
"max_tokens": 200,
"messages": [{"role": "user", "content": "Reply with one word: ready."}]
}'
A successful call returns Anthropic's message shape:
{
"id": "msg_01H8kQ3nV2pX7rT4yW9bZ6cD",
"type": "message",
"role": "assistant",
"model": "<sonnet-model-id>",
"content": [{"type": "text", "text": "Ready."}],
"stop_reason": "end_turn",
"stop_sequence": null,
"usage": {"input_tokens": 14, "output_tokens": 4}
}
The response carries an X-Trace-ID header. Look it up in Flightdeck's audit
view: the call has a GATEWAY_RESPONSE record, and its tokens and cost show on
Cost Dashboard (platform roles) or Usage and Token Usage (workspace
roles) under the key's workspace.
To count tokens without calling a model, send the same body to
POST /v1/messages/count_tokens. It answers {"input_tokens": 14}. On an
Anthropic route the number is Anthropic's count; otherwise it is DVARA's
estimate. A count is not billed, books no usage and is not counted as a call
against the unlicensed call band. It still runs workspace status, policy,
prompt templates, PII and guardrails, and the rate limit counts it as a
request.
What does a refusal look like?
On /v1/messages and /v1/messages/count_tokens, every refusal comes back in
Anthropic's error envelope, so Claude Code can show it. The status and the code
are the same as on /v1/chat/completions. A wrong key:
{
"type": "error",
"error": {
"type": "authentication_error",
"message": "Invalid API key.",
"code": "invalid_api_key"
},
"request_id": "a3f1c9e25b7d4e8a9c1f2d6b8e4a7f30"
}
request_id is the trace id, the same value as the X-Trace-ID header.
| Status | Code | What it means | What to do |
|---|---|---|---|
| 400 | unsupported_api_version | The anthropic-version header is missing, or is neither 2023-06-01 nor (from 1.12.0) 2026-10-21 | Send one of the two. Claude Code does. Before 1.12.0 only 2023-06-01 is served, so a client on a current Anthropic SDK is refused. |
| 400 | invalid_request | From 1.12.0, under 2026-10-21: the request sets temperature, top_p or top_k, has a document in an assistant message, or citations on a user's text | Leave them out, as Anthropic requires for that version |
| 400 | no_provider | No provider or route serves the model id | Pin the model variables above, or add a route |
| 400 | unsupported_capability | Thinking on a non-Anthropic route, or a field DVARA refuses on every route | Turn thinking off, or route the model to Anthropic |
| 400 | context_window_exceeded | The request is over the model's context window, or the provider's when DVARA does not know the model's | Shorten the session, or set the model's window in model_limits. See the note above |
| 401 | invalid_api_key | The key is not a DVARA key, or a Claude subscription credential reached DVARA | Set a DVARA workspace key |
| 403 | api_key_scope | The key has scopes and lacks completions:write | Add the scope, or use another key |
| 403 | session_killed | On the Enterprise platform, someone killed this agent session | Start a new session |
Policy, PII BLOCK, guardrail and budget refusals use the codes in
Error handling.
What does DVARA govern for Claude Code?
DVARA governs the model calls. Each one runs the same controls as
/v1/chat/completions: policy, PII rules, guardrails (on the system text
too), budgets, rate limits, call caps, cost records and the signed audit chain.
On the Enterprise platform, a workspace's IP allowlist or denylist applies to a
call that sends its key in x-api-key, as it does for a bearer key.
Claude Code's own prompt carries an email address, so the email detector
matches on every request. With the default PII action, LOG, that is an audit
entry and nothing else. A workspace whose action is REDACT or TOKENIZE
edits Claude Code's own prompt before it reaches the model. BLOCK refuses
every call. For a workspace that runs Claude Code, keep the email action at
LOG, or expect that edit. See PII detection.
Govern the MCP tools Claude Code calls
Everything above governs Claude Code's model calls. Its MCP tool calls are separate: Claude Code calls each MCP server you add to it directly. Add the DVARA MCP Gateway as its MCP server instead, and DVARA applies your workspace's policy to every tool call, redacts personal data in results and records each one.
claude mcp add --transport http dvara http://localhost:8080/mcp \
--header "Authorization: Bearer <dvara-workspace-key>"
claude mcp list should show dvara: http://localhost:8080/mcp (HTTP) - ✔ Connected.
DVARA lists each server's tools with the server's id in front, so in Claude
Code the MCP quickstart's tools
appear as mcp__dvara__demo-tools__get_order_status and so on. Asked to look up
customer C-42 and then delete them, Claude Code gets:
Customer C-42: Jane Doe, [REDACTED_EMAIL], [REDACTED_PHONE_NUMBER],
card on file [REDACTED_CREDIT_CARD], plan Enterprise.
delete_customer is irreversible and not allowed in this workspace
The delete never reaches the MCP server. Both calls are in Flightdeck under MCP → Tool Calls (platform roles) or Agents → MCP Tool Calls (workspace roles).
What does DVARA not govern?
- Claude Code's local actions. File edits, shell commands and git run on the developer's machine and never reach DVARA.
- MCP servers Claude Code calls directly. Add them to DVARA instead, as in Govern the MCP tools Claude Code calls.
- A Claude subscription. Claude Code signed in to a Claude subscription,
with no DVARA key set, uses its own credential. DVARA cannot govern that use;
if that credential reaches the Gateway, it is refused with
401. Set a DVARA key as shown above. - Claude Code on Amazon Bedrock or Google Vertex AI. With
CLAUDE_CODE_USE_BEDROCKorCLAUDE_CODE_USE_VERTEX, Claude Code does not use/v1/messages, so it cannot be pointed at DVARA.
Where to go next
- Data-plane API overview
for what
/v1/messagesgoverns and how it streams. - Agent sessions to give a Claude Code session one session id across model, tool and agent calls.
- Authenticate data-plane calls for key scopes.