Router Metadata

Surface routing decisions on every response with a single opt-in header

Experimental

Router metadata is experimental. The openrouter_metadata response shape is unstable: fields and pipeline stage types may be added, renamed, removed, or change semantics at any time, without a deprecation cycle. Do not pin production tooling to specific field names or values yet.

OpenRouter’s router runs every request through a multi-stage pipeline: it picks a provider, may compress context, may run guardrails, may invoke server-side tools, and may retry against fallbacks. By default, none of that is visible on the response.

Router metadata is a per-request opt-in that adds an openrouter_metadata field to successful responses, capturing exactly what the router did. It’s intended for debugging routing decisions, attributing latency or cost, and auditing pipeline behavior.

Enabling Router Metadata

Opt in by sending the X-OpenRouter-Experimental-Metadata request header with the value enabled:

$curl https://openrouter.ai/api/v1/chat/completions \
> -H "Authorization: Bearer {{API_KEY_REF}}" \
> -H "Content-Type: application/json" \
> -H "X-OpenRouter-Experimental-Metadata: enabled" \
> -d '{
> "model": "openai/gpt-4o-mini",
> "messages": [{ "role": "user", "content": "Hello" }]
> }'

Accepted Values

The header accepts the following values, matched case-insensitively:

ValueBehavior
enabledSurface openrouter_metadata on the response.
disabledDo not surface metadata. Equivalent to omitting the header.

Any other value (including misspellings, empty strings, and unknown levels) falls back to disabled. The default behavior — when the header is absent — is disabled.

Supported Endpoints

Router metadata is wired into every public completion route:

  • /api/v1/chat/completions (OpenAI Chat Completions)
  • /api/v1/messages (Anthropic Messages)
  • /api/v1/responses (OpenAI Responses)
  • /api/v1/completions (legacy text completions)

Both streaming and non-streaming requests carry the field when opted in. For streaming responses, openrouter_metadata is delivered on the final chunk before data: [DONE] (Chat Completions / Responses) or as part of the terminal message_stop event (Anthropic Messages).

Response Shape

When opted in, successful responses include an openrouter_metadata object alongside the rest of the response payload:

1{
2 "id": "gen-...",
3 "model": "openai/gpt-4o-mini",
4 "choices": [...],
5 "usage": {...},
6 "openrouter_metadata": {
7 "requested": "openai/gpt-4o-mini",
8 "strategy": "direct",
9 "region": "iad",
10 "summary": "available=1, selected=OpenAI",
11 "attempt": 1,
12 "is_byok": false,
13 "endpoints": {
14 "total": 1,
15 "available": [
16 {
17 "provider": "OpenAI",
18 "model": "openai/gpt-4o-mini",
19 "selected": true
20 }
21 ]
22 },
23 "attempts": [
24 { "provider": "OpenAI", "model": "openai/gpt-4o-mini", "status": 200 }
25 ],
26 "pipeline": [
27 {
28 "type": "context_compression",
29 "name": "context-compression",
30 "data": {
31 "engine": "middle-out",
32 "input_type": "messages",
33 "original_count": 42,
34 "compressed_count": 30
35 }
36 }
37 ]
38 }
39}

Field Reference

FieldTypeDescription
requestedstringThe model slug (or alias) the client sent. May differ from the provider/model that actually served the request.
strategystringRouting strategy used: direct, auto, free, latest, alias, fallback, pareto, bodybuilder.
regionstring | nullEdge region that handled the request, when available.
summarystringHuman-readable one-liner describing the routing decision (e.g. candidate count, selected provider).
attemptinteger1-indexed attempt number that succeeded. Greater than 1 means earlier attempts failed and fell back.
is_byokbooleanWhether the request used a Bring-Your-Own-Key provider key.
endpointsEndpointsMetadataSnapshot of endpoint candidates considered, and which one was selected.
paramsRouterParamsOptional. Router-level parameters that influenced selection (e.g. quality_floor, throughput_floor).
attemptsAttempt[]Optional. Per-attempt provider/model/status when the router retried against fallbacks.
pipelinePipelineStage[]Optional. Plugins that materially altered the request or response (compression, guardrails, healing, server tools, etc.).

The full schema is documented under OpenRouterMetadata in the OpenAPI spec, including SDK type definitions for TypeScript and other generated clients.

Pipeline Stages

The pipeline array records every plugin that materially affected the request. A plugin only emits a stage when it actually ran; a no-op plugin (e.g. context compression that found the input already fit the budget) is omitted. Today’s stage types include:

typename valuesWhat it tells you
guardrailcontent-filter, moderation, lakera, model-armorflagged: bool, plus engine-specific verdict (decision, confidence_level, matched_entity_types, etc.).
pluginweb-search, file-parserPlugin-specific telemetry (e.g. result counts for web search, page count for file parsing).
server_toolsserver-toolsMode (native / sdk) and the list of tools invoked.
response_healingresponse-healingMode (json_schema / json_object), whether healing improved the response, lengths.
context_compressioncontext-compressionEngine used, input type (messages / prompt), original vs. compressed counts.

Multiple plugins can share a type. To find a specific guardrail (say, the content filter), iterate the array and match on both type === 'guardrail' and name === 'content-filter'. The full set of guardrail-level plugins emits type: 'guardrail' so you can filter all of them together (pipeline.filter(s => s.type === 'guardrail')) without enumerating individual plugins.

The list grows over time. Treat unknown stage types as opaque — data is a free-form record by design so plugins can attach plugin-specific telemetry without a schema bump.

Cache Hits

Cache hits never include openrouter_metadata. Both streaming and non-streaming cache replays strip the field so clients cannot pin behavior on stale routing data. This is intentional: the metadata you see on a cache miss may not reflect the routing that produced the cached payload.

Error Responses

Opt-in error responses surface openrouter_metadata at the top level of the error envelope, mirroring the success-path placement (sibling of error rather than nested inside it). This applies to all four routes — Chat Completions, Messages, Responses, and legacy Completions — and to both streaming and non-streaming requests. The same opt-in rules apply: send X-OpenRouter-Experimental-Metadata: enabled and the snapshot is included on failure; omit it and it isn’t.

1{
2 "error": {
3 "code": 404,
4 "message": "No allowed providers are available for the selected model"
5 },
6 "openrouter_metadata": {
7 "requested": "openai/gpt-4o-mini",
8 "strategy": "direct",
9 "attempt": 0,
10 "endpoints": {
11 "total": 1,
12 "available": [
13 {
14 "provider": "OpenAI",
15 "model": "openai/gpt-4o-mini",
16 "selected": false
17 }
18 ]
19 }
20 }
21}

A few things to know:

  • attempt reflects how far the router got. A value of 0 means the request never reached a provider — typically because every candidate was filtered out before submission (e.g. provider.only excluded the last endpoint, or an allowed-providers / max-price filter rejected everything). Values ≥ 1 mean every attempted provider failed and fallbacks were exhausted.
  • No endpoint is marked selected on failure. None of the endpoints.available[].selected flags are true because no endpoint actually served a 200.
  • Internal-error masking still applies. Responses with a 500 status are scrubbed to a generic message, and openrouter_metadata is omitted from those envelopes by design — we don’t surface internal routing details on errors whose cause is already hidden. Other 5xx classes (502, 503, 504, 529) still include the metadata when the client opted in.
  • Some failure modes won’t carry it. Authentication / rate-limit failures and other errors that fire before the router has usable routing state (for example, validation rejections at the API edge) will not include the field. If you need post-mortem routing context for a request that completed past the API edge but before the router materialised state, fetch the generation record via GET /api/v1/generation using the X-Generation-Id response header.

Stability

Router metadata is experimental. The openrouter_metadata response shape is unstable — fields and pipeline stage types may be added, renamed, removed, or change semantics at any time, without a deprecation cycle. Treat the payload as best-effort debugging telemetry, not as a stable contract.

The X-OpenRouter-Experimental-Metadata opt-in header is the supported way to enable the feature, but the header name and accepted values may also change while the feature is experimental.

If you consume the field in code, decode it permissively (treat unknown fields and stage types as opaque) and be prepared to update on every release.