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 the DVARA AI governance platform. Every model call it makes is
then governed and billed as one DVARA workspace.
/v1/messages is in DVARA Open Source and the Enterprise platform from 1.8.5.
It runs without a licence key. 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 1.8.5 Gateway that Claude Code can reach.
- 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 one limit per provider, 200,000 tokens for
Anthropic, not the model's own window. With the default hard threshold of 90%,
a request over about 180,000 estimated tokens is refused with
400 context_window_exceeded, or trimmed if the workspace sets a pruning
strategy. Long Claude Code sessions can reach this. Partial workaround: set the
workspace's context Hard Threshold to 100%. That serves requests up to
200,000 tokens and no further. 1.8.6 fixes it. See
Releases and upgrading.
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: 2023-06-01 header is required, and so is max_tokens:
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
the Token Usage and Cost pages 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": "a3f1c9e2-5b7d-4e8a-9c1f-2d6b8e4a7f30"
}
| Status | Code | What it means | What to do |
|---|---|---|---|
| 400 | unsupported_api_version | The anthropic-version header is missing or is not 2023-06-01 | Send anthropic-version: 2023-06-01. Claude Code does. |
| 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 provider's context limit | See the known issue 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.
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. Point them at the DVARA MCP Gateway to govern them.
- 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.