Per-request metadata

Every successful response from the gateway carries a top-level lowrouter_metadata field:

JSON
{
  "id": "chatcmpl-...",
  "choices": [...],
  "usage": {...},
  "lowrouter_metadata": {
    "provider": "openai",
    "region": "se-arn",
    "energy_wh": 0.0021,
    "carbon_gco2e": 0.00057,
    "carbon_intensity_gco2_per_kwh": 270,
    "estimation_methodology": "ecologits_formula",
    "routing_mode": "auto",
    "routing_reason": "lowest_carbon_intensity",
    "routing_profile": "standard",
    "eu_sovereign": false,
    "fallback_occurred": false,
    "providers_attempted": ["openai"]
  }
}

Two kinds of values appear in this block, and they don’t carry the same weight:

  • Recorded: facts about the routing decision LowRouter itself made: provider, region, routing_mode, routing_reason, fallback_occurred, providers_attempted, requested_alias. The region is the one whose endpoint we called; what that guarantees about data residency beyond the routing itself is defined by the upstream provider’s own terms. fallback_reason, requested_auto and eu_sovereign are recorded too.
  • Estimated: modeled numbers: energy_wh, carbon_gco2e, carbon_intensity_gco2_per_kwh. Produced per the methodology from annual grid averages, and absent rather than fabricated when we can’t classify the request.

Field reference

provider

The upstream that actually served the request. Matches an id in GET /providers (a discovery endpoint) and the provider segment of a model ID.

region

The region the upstream served from, as a UN/LOCODE (se-arn, sg-sin, fr-par), or unknown for a model with a single global endpoint whose serving location the provider does not disclose. The region is the one encoded in the resolved model ID’s locode; the billing key global is never reported as a place. An unknown region still gets a carbon estimate, accounted at the worldwide average and labelled as such in estimation_methodology, so a session on such a route never records zero carbon.

energy_wh

Total energy estimated for the request, in watt-hours. Computed from the resolved model’s per-token energy coefficients and the request’s token counts. See methodology.

carbon_gco2e

Total CO₂e estimated for the request, in grams. Derived from energy_wh, the data-center overhead (PUE 1.20), and the grid intensity for the serving region.

carbon_intensity_gco2_per_kwh

The grid carbon intensity used for the serving region, in grams of CO₂e per kWh.

estimation_methodology

How the energy figure the estimate rests on was produced. Present when an estimate was made and its method is recorded, and never without carbon_gco2e, so it cannot label a number that is not there. Two values today:

ValueMeaning
ecologits_formulaPer-token energy from the EcoLogits model of the resolved model’s architecture.
calculated_from_sizeThe model’s published (or inferred) parameter count run through the same formula, used when EcoLogits has no entry for it.

Either way the grid intensity applied to that energy is the one in carbon_intensity_gco2_per_kwh. When that intensity is an average rather than the serving region’s own figure, the value says so: ecologits_formula; grid: GLOBAL-AVERAGE for the worldwide average (every route whose region is unknown), …; grid: US-AVERAGE or …; grid: EU-AVERAGE for a country or bloc average standing in for a region without its own row. The basis is the same one /v1/models publishes as the carbon_metrics key for that route, so the listing and the receipt agree. An average is never presented as a regional figure. See methodology for what each value implies about the confidence of the number.

routing_mode

auto when the router chose the route for you (an auto/<creator>/<model> request), explicit when you named the provider and region yourself.

routing_reason

Why the router landed where it did. For an auto-routed request this is one of eu_sovereign_preferred, lowest_carbon_intensity, lowest_cost, round_robin, or only_route. When your routing policy lists the corresponding criterion, it can also be eu_hosted_preferred, cloud_act_free_preferred or europe_preferred. It names the step of the profile in force that decided; routing_profile beside it names which profile that was. See routing.

routing_profile

The routing profile the route was chosen under: standard for an account that never configured one, otherwise the profile’s name (the key’s assigned profile if it has one, else the profile the account marked as its default). Present only on auto-routed requests: with an explicit or aliased ID you chose the route yourself, so no profile applied. Resolve the name on /app/routing to see the ordered criteria and hard gates routing_reason was decided under.

fallback_occurred

true if the route changed after selection. An auto/ ID may fall through its published candidates in order when a route fails, so this reads true whenever a later candidate served the request. It also reads true when a fallback chain served the request with a different model, in which case requested_model below names the route you would otherwise have got. An explicit ID with no chain is a pin and is never substituted, so it always reads false.

Handle it in live code rather than treating it as a historical artifact: a request that fell through is billed against the pricing row of the route that actually served it, and may have run in a different jurisdiction than the first-ranked route would have. See what happens on failure for the full trade, and fallback_reason below for why the first route did not serve.

fallback_reason

Why the route changed, present only when fallback_occurred is true. One of:

ValueMeaning
primary_timeoutThe route did not answer within its deadline.
primary_rate_limitedThe route rate-limited us (429).
primary_unavailableThe route reported itself unavailable or overloaded (502, 503).
primary_errorAny other upstream failure, including ones we could not classify.
context_lengthYour prompt did not fit the model’s context window, and a fallback model with a bigger one served it.

The values are deliberately coarse: they name the class of failure that moved the route without relaying an upstream provider’s raw error text. primary_error is the catch-all, so a fallback always explains itself even when the cause is unrecognized.

This is a sibling of routing_reason, not a replacement for it. routing_reason keeps meaning “why the first route was picked”, and this explains why the route changed afterwards.

providers_attempted

The list of providers tried for this request, in order.

requested_alias

Present only when the request addressed a model alias ("model": "alias/big"). Carries the alias as sent (alias/big), while the response’s top-level model field carries the full model ID the alias resolved to. Absent on non-aliased requests.

requested_auto

Present only when the request used the auto/ namespace ("model": "auto/mistralai/mistral-large"). Carries the ID exactly as sent, shorthand included, while the response’s top-level model field carries the resolved four-segment ID that ran. The two together show what you asked for and what served it. Absent on explicit and aliased requests.

requested_model

Present only when a fallback chain served the request with a different model. Carries the four-segment route the request would otherwise have used, while the top-level model field names what actually answered.

Without it a substitution is invisible: model tells you what ran, and you would have to keep your own record of what you asked for to notice the two differ. Absent on every request served by the model you named, including one that failed over to another provider of that same model, because that is a route change, not a model change.

The tokens, cost and carbon on the response belong to the model that answered, so this is the field to compare them against.

eu_sovereign

Whether the route that served the request satisfies the sovereign pair: an EU-sovereign provider entity serving from an EU/EEA region. Both halves are required, so neither an EU-controlled company serving from the US nor a US-controlled company serving from Frankfurt qualifies.

Present on every response, whichever way you addressed the model, because the pair is a property of the route that ran, not of how you named it. What it tells you differs by mode:

  • On an auto/ request, sovereignty is a preference. false means no sovereign route existed for that model and the request degraded down the priority order rather than failing. See sovereignty is a preference.
  • On an explicit or aliased request, it attests the route you pinned. This is the mode to use when sovereignty is a hard constraint: the pin fails loudly rather than falling through, and this field is the receipt that the route it ran on was the sovereign one.

false also covers a route whose region cannot be classified (a provider’s flat global endpoint names no country to test), because an unattestable region must not read as an attested one.

Worked scenario: “why did this request go to us-ash?”

Your dashboard shows one row served from us-ash in a week of otherwise-EU traffic. The metadata of that response explains it:

JSON
"lowrouter_metadata": {
  "provider": "openai",
  "region": "us-ash",
  "routing_mode": "auto",
  "requested_auto": "auto/openai/gpt-4.1",
  "routing_reason": "lowest_carbon_intensity",
  "eu_sovereign": false,
  "providers_attempted": ["openai"]
}

Read it as a trace: you sent auto/openai/gpt-4.1, so the route was LowRouter’s to choose, and eu_sovereign: false says plainly that no sovereign route existed for that model. Auto-routing prefers EU-sovereign providers in EU/EEA regions, but falls through rather than failing when a model has none. routing_reason then names what decided among what was left. Nothing was misrouted, and nothing was silently substituted: providers_attempted lists one provider.

If a US route is unacceptable for that workload, this is the signal to move it from auto/ to a pinned model ID or an alias: an explicit route fails loudly instead of falling back outside your constraint, and it carries the same eu_sovereign attestation, so the pin proves itself on every response rather than only at the moment you chose it. See routing for the pinning syntax.

Headers

The gateway sets an X-Request-ID header on every response. Use it to correlate a request with your own logs or when reporting an issue.

Streaming

For streamed requests, lowrouter_metadata arrives on the final chunk (the one before [DONE]), carrying the same fields as the non-streaming block, next to the one complete usage object with the request’s cost. Earlier chunks don’t include it. See first completion → streaming.

On /v1/messages

The Anthropic-native endpoint carries the same block, with three more fields: cost, currency and remaining_balance. On the OpenAI-compatible surface those live on usage; the Messages API’s usage object is Anthropic’s fixed shape, so here they ride the receipt instead. On a non-streaming response it is a top-level field. On a stream it rides the message_delta event, next to usage, since nothing in it is known before the request settles. See Claude Code.

When metadata is partial

The eco fields (energy_wh, carbon_gco2e, carbon_intensity_gco2_per_kwh) can be absent when:

  • The resolved model’s parameter count is unknown and we’d rather omit the number than fabricate one.
  • The grid table holds no figure at any tier for the serving region, not even a worldwide average. An unknown region on its own does not blank them: it is accounted at the worldwide average and labelled as such.
  • The request consumed no tokens (e.g. a non-completion response).
  • The upstream returned an error mid-stream that prevented usage accounting.

When they’re missing, the dashboard shows the row with a — for the carbon column and a note linking to the methodology page. The routing fields (provider, region, routing_mode, …) are still present.

Privacy

The lowrouter_metadata block contains nothing about prompt or response content, only the resolved route and the metric estimates. It is safe to log on the client side, and we log it ourselves.