Per-request metadata
Every successful response from the gateway carries a top-level
lowrouter_metadata field:
{
"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_autoandeu_sovereignare 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:
| Value | Meaning |
|---|---|
ecologits_formula | Per-token energy from the EcoLogits model of the resolved model’s architecture. |
calculated_from_size | The 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:
| Value | Meaning |
|---|---|
primary_timeout | The route did not answer within its deadline. |
primary_rate_limited | The route rate-limited us (429). |
primary_unavailable | The route reported itself unavailable or overloaded (502, 503). |
primary_error | Any other upstream failure, including ones we could not classify. |
context_length | Your 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.falsemeans 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:
"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.
