Router Headers
The Router reads and writes the headers below: a public set on its responses, a debug trace, the request headers clients send, and internal headers no client sees. This reference is generated from the Router's header registry, src/semantic-router/pkg/headers; run make headers-generate after changing it. Header names are case-insensitive.
Who sees which headers
| Surface | Headers | Who sees them |
|---|---|---|
public | Response headers | Every client, on each response a header applies to. |
debug | x-vsr-trace | Only a caller global.router.headers.debug allows, on a request that asks with x-vsr-debug: true. |
control | Request headers | Sent by clients to steer or identify a request. |
internal | Internal headers | No client: they pass between the Router, the gateway and authenticator in front of it, and providers, and no response the Router writes carries them. |
Hops name where a header travels: request (client to Router), response (Router to client), provider request (Router to provider) and provider response (provider to Router). A header reaches a provider only when its hops include provider request, as Reaches providers shows for the request headers; the Router removes the others from every provider-bound request. Since is the release the header first shipped in.
Response headers
Every response of the routing pipeline carries the headers that apply to it: answers from the upstream model, response-cache hits, and the responses the Router produces itself, such as a block, a fast response or a routing error. Responses of the Router's own handlers, such as GET /v1/models, carry x-vsr-config-version only.
| Header | Since | Description |
|---|---|---|
x-vsr-schema-version | v0.4 | Response-header contract revision; currently 3. |
x-vsr-response-path | v0.4 | How the response was produced: upstream, cache, fast_response, looper, blocked, rate_limited, error or fallback. |
x-vsr-config-version | v0.5 | Version of the configuration snapshot that served the request. |
x-vsr-config-hash | v0.4 | SHA-256 of the active runtime document, on routing previews and GET /api/v1/config. |
x-vsr-warnings | v0.5 | Warnings about the response, as an RFC 8941 list: scan_partial when a request-stage guard rule read only part of a long input, guard_overloaded on the retryable error answered instead of a refusal or a forward that rules unread on an overloaded decision runtime decide, the codes of the response_guard rules that annotated the answer, then each protocol translation loss with its action, reason and field. |
x-vsr-selected-recipe | v0.4 | Recipe the effective entrypoint selected; omitted for concrete backend models. |
x-vsr-selected-decision | v0.1 | Decision the decision engine selected; omitted when no decision matched. |
x-vsr-selected-model | v0.1 | Model alias the Router selected. |
x-vsr-replay-id | v0.2 | Router Replay record id, when the record was persisted. |
x-vsr-cost | v0.4 | Usage priced with the served model's configured pricing; buffered responses only. |
x-vsr-cost-currency | v0.4 | Currency of x-vsr-cost, from pricing.currency. |
x-ratelimit-limit | v0.2 | Limit of the rate-limit window that rejected the request. |
x-ratelimit-remaining | v0.2 | Requests left in that window. |
x-ratelimit-reset | v0.2 | Unix time the window resets at. |
x-request-id | v0.1 | Request correlation id, forwarded to providers; the native gateway adds one when absent, and the management API echoes it. |
x-content-type-options | v0.4 | nosniff on Router Replay management responses. |
x-envoy-upstream-service-time | v0.5 | Envoy's upstream time on responses in Envoy mode; gateway-parity checks ignore its value. |
x-envoy-overloaded | v0.5 | true on the 503 a full circuit breaker returns, as Envoy sets it. |
A response that answered from the upstream model:
HTTP/1.1 200 OK
content-type: application/json
x-vsr-schema-version: 3
x-vsr-config-version: 7
x-vsr-response-path: upstream
x-vsr-selected-recipe: default
x-vsr-selected-decision: complex-request
x-vsr-selected-model: reasoning-model
x-vsr-replay-id: replay_01J...
- Read
x-vsr-schema-versionbefore parsing the other headers; the current value is3. Responses of the Router's own handlers never carry it, so its absence there is expected. - A request keeps the configuration version it started on, even when a hot reload activates another one while it runs.
x-vsr-config-versioncounts the activations of one Router, which keeps its own configuration history; to check that replicas serve the same configuration, compare theactive_runtime_hashthatGET /api/v1/config/hashreports. - Recipe names scope local signal, projection, decision, cache, replay, metric, and learning or session identities. Use
x-vsr-selected-recipetogether with the local decision and signal names when correlating a response with Insights or metrics.
The trace
| Header | Since | Description |
|---|---|---|
x-vsr-trace | v0.5 | Routing details as an RFC 8941 dictionary of at most 4 KB, for a caller global.router.headers.debug allows. |
x-vsr-trace carries what else the Router knows about a request, such as the matched signals, the decision's confidence and the algorithm. It is an RFC 8941 dictionary of at most 4096 bytes, whose members are omitted when they don't apply. A string percent-encodes % and every byte outside printable ASCII, so decode it after unquoting. When the encoded trace would exceed 4096 bytes, the Router leaves out members from the lowest priority up and ends the dictionary with truncated.
x-vsr-trace: confidence=0.91, algorithm=static, routing_latency_ms=3.2, signals=("context:long-context" "projection:use-reasoning-model"), session_phase=tool_loop
Who may turn on the trace
The trace names the signal and guard rules a request matched, the decision ranking and the Router Learning reasons, so a client can't turn it on by itself. A client asks for it with x-vsr-debug: true, and global.router.headers.debug decides who may; the Router ignores x-vsr-debug from everyone else.
debug | Who gets the trace |
|---|---|
none (default) | Nobody. |
any | Every client that sends x-vsr-debug: true. Use it for development and tests, or behind a gateway that removes the header from untrusted clients. |
authenticated | Only the users and groups in debug_principals, as the identity headers your authenticator sets assert them (global.services.authz.identity). |
global:
router:
headers:
debug: authenticated
debug_principals:
users: [oncall-bot]
groups: [platform-sre]
authenticated trusts the identity headers. A standalone Router keeps them only on a listener with identity.trust_headers, and refuses authenticated when no listener trusts them. Behind an Envoy-based gateway, the gateway must remove the identity headers that clients send. The Dashboard Playground, vllm-sr route probe and sr-bench ask for the trace; when the caller isn't allowed, they show the public set only.
Members
Highest priority first:
| Member | Owner | Description |
|---|---|---|
config_hash | benchmark | SHA-256 of the runtime document that served a config-bound evaluation request. |
inference_calls | benchmark | Provider calls a config-bound evaluation request made. |
model_usage | benchmark | Per-model usage receipt of a config-bound evaluation request, as JSON. |
confidence | routing | Model-derived score of the selected decision, such as 0.91. |
algorithm | routing | Selector or executor that chose the model after the decision matched, such as static. |
routing_latency_ms | routing | Time spent choosing the model, in milliseconds. |
fallback_attempts | fallback | Attempts cross-model fallback made, the primary included, when a fallback candidate served the response. |
on_unknown | routing | Decisions whose unknown result rules.on_unknown resolved, each with its policy. |
client_protocol | protocol | Client wire protocol, such as anthropic. |
upstream_protocol | protocol | Provider wire protocol, such as openai. |
effective_input_tokens | automatic_output | Rendered input tokens of the final automatic-output dispatch, chat template included. |
effective_max_output_tokens | automatic_output | Output token limit of the final automatic-output dispatch, reasoning included. |
signals | signals | Matched signal rules as type:name, and type:rule:option for a choice rule without labels; projections are projection:name. |
scan_coverage | signals | Guard rules that read their input in windows, as type:name with coverage (full, partial or none) and, for partial, the read and windows counts. |
context_tokens | signals | Token count the context signal measured. |
category | classification | Domain category the classifier chose, such as math. |
reasoning | classification | Reasoning mode chosen for the request: on or off. |
modality | routing | Output kinds of the selected decision's output contract, such as (image) or (text image); omitted for a decision that declares none. |
system_prompt | system_prompt | Present when a system_prompt plugin injected text into the request. |
cache_similarity | response_cache | Similarity score of the response-cache lookup. |
retention | retention | The matched decision's retention directive: the drop, ttl_turns, keep_current_model and prefer_prefix it sets. |
prompt_cache | prompt_cache | Outcome of the prompt_cache plugin, with its reason and the inserted and preserved marker counts. |
tools | tools | Outcome of the tools plugin's selection, with its candidates, kept and latency_ms. |
response_guard | response_guard | Outcome of the selected decision's response_guard, such as blocked, with buffered when the Router held a streamed answer until the guard read it. |
session_phase | router_learning | Protection phase: user_turn, tool_loop or provider_state. |
learning | router_learning | Router Learning methods, each with its action, scope and reason. |
looper | looper | Models the Looper called, with its algorithm, final model, iterations, latency_ms and token counts. |
ranking | routing | The matched decision the selected one beat, with the reason that settled it. |
truncated | response_contract | Present when members were left out to keep the trace within 4 KB. |
confidenceis absent when the decision rests on policy leaves, aggregates several evidence leaves, resolves through an error policy, or is a catch-all.fallback_attemptscounts the same way in both gateway modes and is omitted when the primary answered.rankingis omitted when only one decision matched; parsedecision_rankingin Router Replay for the full ranking.learninglists the Router Learning methods with theiraction. Show the actions as user-facing phrases such astool/protocol pinned,model switched, orlearning bypassed; fresh conversation or session-start diagnostics are usually useful only in debug views, as neutral status text.effective_input_tokensandeffective_max_output_tokensappear together on a successful upstream response when automatic output was resolved, in the initial headers of streaming and buffered responses alike. Explicit output limits, cache hits, skipped processing, and Looper responses omit them. Their values come from the final dispatch after request changes and backend rendering; they are not estimates or the model's configured maximum context length.config_hash,inference_callsandmodel_usageare the receipts of a config-bound evaluation request, one sent withx-vsr-bench-expected-config-hash.retentionshows the fields a matched decision's retention directive set, so operators can verify policy wiring; clients should not use them as commands.prompt_cacheis content-free: it reports counts and outcomes, never the cached text itself, and it never claims a cache hit or provider-side savings.
Projection scores and full projection traces stay in the Router Replay record rather than the trace. Use x-vsr-replay-id to inspect them in the Dashboard or through the authenticated Router management API; public inference listeners do not serve replay records.
Warnings
x-vsr-warnings is an RFC 8941 list of warning codes, omitted when none applies. The codes of the response_guard rules that annotated the answer come first, in rule order, then the protocol translation losses. Per-warning detail, such as hallucination spans or jailbreak confidence, stays in the replay record.
| Code | Meaning |
|---|---|
scan_partial | A request-stage guard rule (jailbreak, safety or pii) read only some of a long input's windows, or none, and the request was routed on what it read. |
hallucination | A response_guard rule over a hallucination signal flagged the answer. |
unverified_factual | The answer was factual, but a hallucination rule had no context to check it against. |
response_jailbreak | A response_guard rule over a response-stage jailbreak signal flagged the answer. |
response_guard | A response_guard rule over any other signal flagged the answer. |
protocol | Protocol translation changed or dropped part of the request, with its action, reason and field. |
truncated | Protocol warnings left out to keep the header within 4 KB, with their count. |
Request headers
Clients send these to steer or identify a request. See Session identification for how the Router picks the session a request belongs to.
| Header | Reaches providers | Since | Description |
|---|---|---|---|
x-vsr-session-id | no | v0.5 | Session id the client pins; without it the Router reads the headers global.router.headers.session_id_headers names, in order. |
x-session-id | yes | v0.3 | Client session id session_id_headers accepts by default; providers receive it. |
x-claude-code-session-id | yes | v0.3 | Conversation id Claude Code sends, which session_id_headers accepts by default after x-session-id; providers receive it. |
x-conversation-id | yes | v0.4 | Client conversation id; Router Learning scope: conversation protects it, and providers receive it. |
x-vsr-memory | no | v0.5 | off keeps Router-managed memory out of the request. |
x-vsr-skip-processing | no | v0.3 | true opts the request out of Router processing when global.router.skip_processing.enabled is set. |
x-vsr-debug | no | v0.4 | true asks for x-vsr-trace on the response; honored only for a caller global.router.headers.debug allows. |
x-vsr-cache-control | no | v0.4 | Response-cache directives for the request, when the plugin's request_controls allow them. |
x-vsr-compression-control | no | v0.4 | Context-compression directives for the request, when the plugin's request_controls allow them. |
x-vsr-protection-release | no | v0.5 | Any non-empty value asks router-learning protection for a fresh decision on the request's protection key; global.router.learning.protection.release.header can rename it. |
x-vsr-bench-expected-config-hash | no | v0.5 | Configuration hash an evaluation request requires; another active configuration fails it with 412. |
x-vsr-bench-max-inference-calls | no | v0.5 | Provider-call limit of an evaluation request; only 1 is supported. |
Internal headers
These pass between the Router, the gateway and authenticator in front of it, and providers. They are not part of the client contract, and no response the Router writes carries them. The Router drops a client's value of every x-vsr- header except the request headers above and x-vsr-delegated-credential, which the gateway in front must strip from clients, and drops every x-vsr- header a provider returns.
Identity
These name an authenticated caller. A listener that doesn't trust identity headers drops the ones a client sends (see Gateway modes), and no identity header reaches a model provider.
| Header | Since | Description |
|---|---|---|
x-authz-user-id | v0.2 | Authenticated user id; identity.user_id_header names another header. |
x-authz-user-groups | v0.2 | Authenticated user's groups, comma-separated; identity.user_groups_header names another header. |
x-authz-team-id | v0.4 | Authenticated team id; partitions the response cache. |
x-authz-tenant-id | v0.4 | Authenticated tenant id; partitions the response cache. |
x-vsr-outcome-principal | v0.4 | Opaque attested identity that isolates outcome-ingest rate limits; never persisted. |
Credentials
Credentials, by name only. None reaches a client, and a provider receives only the credential its auth sets: a per-user key only as that credential, and never a client's own credential header.
| Header | Hops |
|---|---|
x-user-openai-key | request |
x-user-anthropic-key | request |
x-user-azure-openai-key | request |
x-user-bedrock-key | request |
x-user-gemini-key | request |
x-user-vertex-ai-key | request |
x-user-minimax-key | request |
x-user-cloudflare-workers-ai-key | request |
x-vsr-delegated-credential | request |
x-vsr-internal-auth | request |
authorization | request, provider request |
api-key | request, provider request |
x-api-key | provider request |
x-goog-api-key | provider request |
api-subscription-key | provider request |
ocp-apim-subscription-key | provider request |
Routing and transport
| Header | Hops | Since | Description |
|---|---|---|---|
x-vsr-route-model | request, provider request | v0.5 | Route key: the model whose route the gateway sends the provider request on; the Router drops a client's value and sets its own. |
x-vsr-upstream-host | provider response | v0.5 | Endpoint Envoy reports on the provider response for KV registry writes; removed before the client. |
x-vsr-kv-source-pod | provider request | v0.4 | Address of the pod that holds the source model's KV cache. |
x-vsr-kv-cache-id | provider request | v0.4 | Opaque id of the source KV block. |
x-vsr-kv-mapper-id | provider request | v0.4 | Mapper artifact for the source and target model pair. |
x-vsr-systemone-backend | request, provider request | v0.5 | Marks a System One request one Router forwards to another, which refuses to forward it again. |
x-vsr-outcome-source | request | v0.4 | Outcome provenance a trusted control plane attests to the management API. |
x-forwarded-for | request, provider request | v0.5 | Client address chain; the native gateway's access log records it, and providers receive it. |
x-forwarded-proto | request, provider request | v0.5 | Scheme of the client connection; the native gateway sets it when absent, as Envoy does. |
x-envoy-upstream-rq-timeout-ms | request, provider request | v0.5 | Route timeout the Router sets from the decision's reliability block. |
x-envoy-upstream-rq-per-try-timeout-ms | request, provider request | v0.5 | Per-try timeout the Router sets from the decision's reliability block. |
x-envoy-max-retries | request, provider request | v0.4 | Retry count the Router sets from the decision's reliability block, and 0 on evaluation requests. |
x-envoy-retry-on | request, provider request | v0.5 | Retry conditions the Router sets from the decision's reliability block. |
x-envoy-retriable-status-codes | request, provider request | v0.5 | Retriable status codes the Router sets from the decision's reliability block. |
x-envoy-hedge-on-per-try-timeout | request, provider request | v0.4 | false on evaluation requests, so a per-try timeout never hedges. |
x-envoy-internal | request | v0.5 | Envoy's internal-request marker; the edge drops it from client requests. |
x-envoy-retriable-header-names | request | v0.5 | Envoy retry control; the edge drops it from client requests. |
x-envoy-retry-grpc-on | request | v0.5 | Envoy retry control; the edge drops it from client requests. |
x-envoy-upstream-alt-stat-name | request | v0.5 | Envoy stats control; the edge drops it from client requests. |
x-envoy-upstream-rq-timeout-alt-response | request | v0.5 | Envoy timeout control; the edge drops it from client requests. |
x-envoy-expected-rq-timeout-ms | request | v0.5 | Envoy timeout control; the edge drops it from client requests. |
x-envoy-force-trace | request | v0.5 | Envoy tracing control; the edge drops it from client requests. |
x-envoy-ip-tags | request | v0.5 | Envoy IP tagging control; the edge drops it from client requests. |
x-envoy-original-url | request | v0.5 | Envoy redirect control; the edge drops it from client requests. |
x-envoy-ratelimited | provider response | v0.5 | Marks a provider response an Envoy rate limit rejected; the native upstream can retry it. |