# LowRouter — Full Documentation > LowRouter is one gateway to many LLM providers and models, compatible with the OpenAI API. Choose the provider, model, and region that handles each request — or name just the model with `auto//` and let LowRouter pick the greenest eligible route — and get the provider, region, and gCO₂eq back on every response. Most AI gateways hide which provider handled your request. LowRouter does the opposite: every response carries `lowrouter_metadata` revealing the upstream provider, region, grid carbon intensity, and gCO₂eq emitted — and your dashboard breaks down the cost of every call. You choose the provider, model, and region, name just the model with `auto//` and let LowRouter pick the greenest eligible route for it (preferring EU-sovereign providers in EU/EEA regions, and modeled from annual grid averages, not live grid data), or point your code at an alias and repoint it later without any code change. Custom auto-routing policies (like a per-key carbon budget) are coming soon. Operated by Carbonifer SAS (Nantes, France). OpenAI-compatible, so you can switch back any time with no proprietary protocol. One entry point to many providers across 40+ regions, and one bill instead of one per provider. Grid carbon data: Ember Climate country averages (CC-BY 4.0), refined by regional grid operators (PJM, CAISO, ERCOT, BPA, EirGrid, RTE, National Grid ESO, Hydro-Québec, AEMO) where they publish finer permissively-licensed data. Energy modeling via EcoLogits. This file concatenates every page of LowRouter's public documentation, followed by the API reference. Source: /docs. --- # Philosophy # Philosophy LowRouter exists because running an application on top of large language models forces choices that are usually invisible: which provider serves the request, where their hardware sits, what the request actually costs in energy and carbon, and what happens when one provider goes down. These four pages set out the position we take on those choices. They are not marketing copy. They describe what we measure, what we choose not to measure, and why those decisions sometimes lead us to slower, narrower, or more expensive defaults than the rest of the market. - [Why LowRouter exists](why-lowrouter) - [Sustainability-first](sustainability-first) - [Sovereignty and transparency](sovereignty-and-transparency) - [Principles in practice](principles-in-practice) --- # Why LowRouter exists # Why LowRouter exists LLM inference is now a default building block. Most teams that ship features on top of it end up writing the same gateway twice: once to abstract the provider, again to track usage and bills. That gateway is load-bearing — it sees every prompt and every response — but it is rarely treated as a product. It is glue. LowRouter is that gateway as a product, with two opinions baked in. ## Opinion one: the footprint of a request is part of its cost Most billing dashboards show tokens and dollars. LowRouter also reports the energy a request consumed and the grams of CO₂e the inference is estimated to have produced. Both numbers are estimates — see [methodology](../sustainable-ai/methodology) for the formula and its limits — but having them visible changes how the request is thought about. A request with a known carbon number is a request a developer can actually choose differently. We do not claim that every request is "green" or that the estimate is exact. We claim it exists, that the formula is documented, and that the inputs are auditable. ## Opinion two: routing should be explicit and sovereign When you pick a model in most gateways, you pick a *brand*. The brand hides who actually serves the tokens — which provider, which region, which hardware tier. That hiding is convenient until something matters: a region requires data residency, a provider has an outage, a contract requires a specific operator. LowRouter exposes the route. Every response says which provider served the request and from which region, and the dashboard lets operators choose policies (prefer-region, prefer-low-carbon, prefer-cheapest, fixed-provider) that map to those constraints. Delegating the route is one field away — an `auto//` ID — and so is taking it back, with an explicit four-segment one. ## What LowRouter is not It is not an inference engine. The actual work happens at OpenAI, Anthropic, Mistral, and other providers. We forward, we measure, we account. It is not a benchmarking tool. The dashboard does not rank models on quality. We expose what we can measure faithfully — usage, latency, energy, carbon — and leave subjective judgements to you. It is not a free service. The credits model is documented in [credits and billing](../guides/credits-and-billing). When the costs of running this kind of infrastructure are made invisible, the sustainability story becomes hollow; we'd rather charge what running it actually costs. ## Who it's for - **Developers** who want one endpoint and one bill across multiple providers, plus enough metadata to debug and improve their app. - **Operators** who need data residency, audit trails, and a clear picture of which provider served what. - **Sustainability and compliance teams** who want a defensible number for the AI footprint of their organisation, not a marketing pledge. If that is not you, that is fine. The dashboard and these docs are public for a reason — read what we measure and how, and decide whether the trade-offs fit. --- # Sustainability-first # Sustainability-first "Sustainable AI" is doing a lot of marketing work right now. This page describes what the phrase means inside LowRouter — concretely, what we measure, what we report, and what we have decided not to claim. ## What we measure For every inference request, we estimate two numbers: - **Energy per output token** (Wh), derived from the model's active parameter count using the [EcoLogits methodology](https://ecologits.ai/0.4/methodology/llm_inference/). - **Grid carbon intensity** (gCO₂e/kWh) for the region the provider serves the request from, sourced from Ember Climate annual averages, refined by regional grid-operator data where available. The product gives us **gCO₂e per 1,000 tokens** for the request. The exact formula and the confidence we attach to each estimate are in the [methodology page](../sustainable-ai/methodology). These numbers are exposed: - On every API response, in the `lowrouter_metadata` block. - On the dashboard, aggregated by day, model, provider, and region. - On the public model browser, as a comparable estimate per model. ## What we don't measure (yet) - **Training emissions.** We report inference only. Training is a separate, larger, and harder-to-attribute footprint, and folding it into per-request numbers is misleading. - **Hardware embodied carbon.** GPU manufacturing has a real footprint; we don't yet have a defensible per-token number for it. - **Real-time grid mix.** We use annual averages by region. Live carbon-aware routing is a future feature, not a current one. - **Embedding workloads.** Encoder-only models have a different compute profile and our formula does not yet model them well. We list these limits because they are part of what an honest number looks like. The sustainable-AI page repeats them next to every chart. ## Constraints that fall out of this position A few platform decisions follow from taking energy and carbon seriously: - **The site itself is on a tight transfer budget.** Pages like this one ship under 100 KB total. The docs site renders server-side with no client framework. Heavy interactive widgets are not added without a reason. - **The default route is auto-mode**, not "the largest model." If a smaller model can do the job for a fraction of the energy, we'd rather it be the default. - **Providers and regions with very high grid intensity are deprioritised** in routing unless explicitly pinned by the caller. See [provider routing](../models/routing). - **We are slower to add features than we'd like.** Every new background job, every dashboard widget, every fancy visualisation is a per-user energy cost; we add them when they earn the cost. ## What this is not A pledge that any single request is green. A claim that the estimate is exact. An assertion that running an LLM through LowRouter is meaningfully better for the planet than running it directly. What it is: a measured number, an open formula, and a default that prefers the smaller model and the cleaner grid when other constraints allow. --- # Sovereignty and transparency # Sovereignty and transparency Two of the practical reasons teams move from a single LLM provider to a gateway are sovereignty (where the request goes) and transparency (what's actually happening). Both deserve concrete answers, not adjectives. ## Sovereignty LowRouter's control plane is operated from the European Union by Carbonifer SAS. The data plane — the providers that actually run the inference — covers multiple regions: EU (Mistral, Anthropic via EU endpoints, Bedrock EU), US (OpenAI, Anthropic via US endpoints, Bedrock US), and a growing set of regional Vertex AI deployments. When you send a request without a region preference, the router picks based on availability and the carbon-intensity heuristic described in [provider routing](../models/routing). The chosen region is reported in the response. When you need data residency: - Pin a region by encoding it in the model ID — append a UN/LOCODE as the last segment, e.g. `vertex/anthropic/claude-opus-4.6/sg-sin`. - Region resolution fails closed: if the model isn't served in the requested region, the request returns an error rather than silently falling back to another region. We do not run inference on user prompts inside our own infrastructure beyond what is required to forward and account for them. Logs are documented in [usage accounting](../guides/usage-accounting). ## Transparency Every API response includes, in its `lowrouter_metadata` block, a `provider` field with the upstream that served it and a `region` field with the region it served from, alongside an `X-Request-ID` header you can correlate against your own logs. The dashboard shows the same data per request, per day, and aggregated per model. The carbon and energy numbers shown on the dashboard are produced by the formula in [methodology](../sustainable-ai/methodology). The formula's coefficients, the model parameter counts we use, and the grid-intensity values we apply are documented and dated. When a value changes — for instance, when a new grid-intensity dataset replaces an older one — the change is reflected in the dashboard with the date it took effect. The platform is operated by a small team. There is no army of unaccounted-for logging or analytics services. The third-party services involved are listed in the [privacy policy](/privacy). ## How to verify - Read [methodology](../sustainable-ai/methodology) and audit the formula. - Send a request with an `auto//` ID, then re-send it pinned to a specific provider/region via the model ID. Compare the `provider`, `region`, and eco fields in the `lowrouter_metadata` block. - Open the corresponding entries on the dashboard and confirm the numbers match. If something doesn't reconcile, that's a bug, not a feature — please [file an issue](https://github.com/carbonifer/lowrouter/issues). --- # Principles in practice # Principles in practice Principles are easy to declare and harder to live with. This page is the short version of how the previous three translate into product behaviour you can observe. ## Routing | Decision | What we do | |----------|------------| | Default route | None — the `model` field is required. `auto//` keeps the model your choice and ranks routes on sovereignty, then carbon, then price. | | Pinning | Any caller can pin model, provider, and region per request, or per API key. | | Failover | A provider outage routes to the next eligible option in the same region. We never fail across regions silently. | | Carbon weight | Routing is biased toward lower-carbon regions when other constraints allow. The bias is configurable and the weight is documented in [routing](../models/routing). | ## Pricing | Decision | What we do | |----------|------------| | Pricing model | Pre-paid credits. The price per 1K tokens for each model is shown on the dashboard before you call it. | | Mark-up | A flat platform fee on top of the upstream provider's price. Documented per model. | | Free tier | None. Trying things out costs the same as production usage. | | Refunds | A failed request that produced no upstream charge does not consume credits. | The full pricing rules are in [credits and billing](../guides/credits-and-billing). ## Data handling | Decision | What we do | |----------|------------| | Prompt logging | We log token counts, model, provider, region, and timing. We do not log prompt or response content. | | Retention | Token-level usage is retained for 13 months for billing and auditing. Aggregates are kept longer. | | Export | Usage history can be exported as CSV from the dashboard. | | Subprocessors | Listed in the [privacy policy](/privacy). | If you need a Data Processing Agreement, contact us through the channel listed on the [legal page](/impressum). ## Operations | Decision | What we do | |----------|------------| | Status page | Linked from the dashboard footer. | | Incident communication | Public post-mortems for incidents that affected billing or routing decisions. | | API stability | Breaking changes are versioned (`/v1`). Additive changes are documented in the changelog. | ## What "no" looks like - We do not auto-upsell to larger models. The defaults aim at the smallest model that produces an acceptable response. - We do not show comparative quality charts between providers. We are not the right venue to make those judgements; tools that benchmark outputs against reference suites are. - We do not stockpile features for the sake of feature parity. New endpoints and dashboards are added when there is a defensible reason. --- # Getting started # Getting started Four short pages that take you from a clean machine to a working request and a populated dashboard: 1. [Create an account](account) — sign-up, email verification, and the identity model. 2. [Create your first API key](api-keys) — API keys, scoping, and safe storage. 3. [Run your first completion](first-completion) — a single `curl` call, what comes back, and the eco metadata. 4. [Tour the dashboard](dashboard-tour) — credit balance, usage, top models, and the eco impact widget. If something doesn't work, the [FAQ](../faq) covers the questions we hear most. For everything else, the support email is on the [legal page](/impressum). --- # Create an account # Create an account LowRouter accounts are individual: one email, one identity, one credit balance. Team accounts and shared workspaces are on the roadmap; until they ship, share access via separate API keys rather than shared credentials. ## Sign up Go to [/register](/register) and fill in: - **Email** — used for verification, sign-in, and billing receipts. - **Password** — a strong, unique one. We do not enforce a maximum length; we do enforce a minimum that follows current OWASP guidance. - **Country** — used to compute the right tax treatment on invoices. You can correct it later from the settings page. Alternatively, sign in with a federated identity provider listed on the register page. Federated sign-in does not change anything about how your data is stored — see [the privacy policy](/privacy) for the full picture. ## Verify your email After registration we send a verification link. The link is valid for 24 hours. Until you verify, you can sign in but you cannot create API keys or top up credits — guarding against typos and disposable-mailbox sign-ups. If the email doesn't arrive, check spam, then use **Resend verification** on the sign-in page. If it still doesn't arrive, contact support — see the email on the [legal page](/impressum). ## Top up credits LowRouter is pre-paid. To send a request you need a non-zero credit balance. 1. Go to **Dashboard → Credits**. 2. Click **Add credits**. 3. Pick an amount and complete the Stripe-hosted checkout. Credits land in your account when Stripe confirms the payment, usually within a few seconds. The amount you top up is exclusive of VAT; the invoice that lands in your inbox afterwards has the VAT breakdown. The full pricing model is documented in [credits and billing](../guides/credits-and-billing). ## What's stored After sign-up the platform stores: - Your email address (sign-in, billing receipts). - A salted hash of your password (never the plaintext). - The country and any billing details you provided. - A unique numeric user ID used internally. We do not store any prompt or response content. Token counts, model IDs, provider IDs, regions, latencies, and the eco numbers are stored per request — see [usage accounting](../guides/usage-accounting) for the full schema. ## Next [Create your first API key →](api-keys) --- # Create your first API key # Create your first API key API keys authenticate every request to the gateway. They are bearer tokens — anyone holding the string can spend your credits — so the rest of this page is about creating, scoping, and rotating them safely. ## Create one 1. **Dashboard → Keys**. 2. Click **New key**. 3. Give it a name that describes where it will be used (`prod-server`, `local-dev`, `chatbox-personal`). The name appears in the usage history and helps you find the right key to rotate later. 4. Choose when it **expires**. Keys have a bounded lifetime — the default is **90 days**, matching the widely-used rotation baseline. Pick a shorter window for high-sensitivity integrations, a longer one (up to 12 months) for long-running workloads, or **Never** as a deliberate opt-out. An expired key stops authenticating and shows an `expired` badge in the dashboard so you know to rotate it. 5. Click **Create**. The full token shows once — copy it now. Tokens look like `sk-lr-...` and are 40+ characters. The dashboard only ever shows the prefix and last four characters again. ## Store it - **Production** — in your secret manager (Vault, AWS Secrets Manager, GCP Secret Manager, sealed Kubernetes secret, …). Never in source control. - **Local development** — in a `.env` file that is in `.gitignore`. - **Personal tools** — in the OS keychain, or in the tool's own encrypted store. Avoid pasting the token into chat applications or notes apps that sync to the cloud. A leaked key can be revoked from the dashboard at any time — see *Rotate or revoke* below — but it can spend credits in the seconds between the leak and the revocation. Treat keys like passwords. ## Use it The header is the standard `Authorization: Bearer`: ```bash curl https://api.lowrouter.ai/v1/chat/completions \ -H "Authorization: Bearer $LOWROUTER_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "model": "auto/mistralai/mistral-large-2512", "messages": [{"role": "user", "content": "Hello"}] }' ``` The `Authorization` header value is exactly `Bearer ` followed by the token — no quotes, no spaces around the equals. SDKs accept the token as the constructor's `apiKey`/`api_key` argument; see [integrations](../integrations/). ## Rotate or revoke - **Rotate** — create a second key, deploy it everywhere, then delete the old one. There is no built-in zero-downtime rotation; the pattern above gives you it without one. - **Revoke** — **Dashboard → Keys → Delete**. The token stops working on the next request, no caching delay. - **Expiry** — keys also lapse automatically at their expiry date (default 90 days). An expired key returns a `401 api_key_expired` error; create a fresh key and rotate it in. Expired keys stay listed (marked `expired`) as a reminder, but their secret is purged at rest. Rotate at least every 90 days, and immediately after any of: - A key was committed to a repository (even briefly). - A key was sent over an insecure channel. - A team member with access to the key left the organisation. - Unexpected usage shows up on the dashboard. ## Next [Run your first completion →](first-completion) --- # Run your first completion # Run your first completion This page sends one chat completion through the gateway, walks through what came back, and points at the things you'll come back to. ## Send the request Set your key in an environment variable so it doesn't end up in shell history: ```bash export LOWROUTER_API_KEY="sk-lr-..." ``` Then call the gateway: ```bash curl https://api.lowrouter.ai/v1/chat/completions \ -H "Authorization: Bearer $LOWROUTER_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "model": "auto/mistralai/mistral-large-2512", "messages": [ {"role": "user", "content": "In one sentence, what is a vector database?"} ] }' ``` An `auto//` ID names the model you want and leaves the route to us: among the providers and regions serving it, LowRouter prefers EU-sovereign routes, then the lowest-carbon one (modeled from annual grid averages — see the [methodology](../sustainable-ai/methodology)). You get the same model either way — only where it runs is decided for you. The `model` field is required. You can name the route yourself instead, for example `openai/openai/gpt-4.1/global` or `anthropic/anthropic/claude-haiku-4.5/global`, pinning a region with a UN/LOCODE as the fourth segment (e.g. `vertex/anthropic/claude-opus-4.6/sg-sin`), or call an alias you can repoint later without changing code. See [routing](../models/routing). Custom auto-routing policies (like a per-key carbon budget) are coming soon. ## What comes back The response is OpenAI-shaped. The fields you'll use most are: ```json { "id": "chatcmpl-01J9...", "object": "chat.completion", "created": 1714150000, "model": "openai/openai/gpt-4.1", "choices": [ { "index": 0, "message": { "role": "assistant", "content": "A vector database stores …" }, "finish_reason": "stop" } ], "usage": { "prompt_tokens": 18, "completion_tokens": 26, "total_tokens": 44, "cost": 0.000034, "currency": "EUR", "remaining_balance": 9.87 }, "lowrouter_metadata": { "provider": "openai", "region": "eu-west", "energy_wh": 0.0021, "carbon_gco2e": 0.00057, "carbon_intensity_gco2_per_kwh": 45.0, "estimation_methodology": "parameter-based", "routing_mode": "auto", "routing_reason": "lowest_carbon_intensity", "fallback_occurred": false, "providers_attempted": ["openai"] } } ``` The OpenAI-compatible parts (`id`, `choices`, `usage`, …) follow the standard OpenAI Chat Completions shape. LowRouter adds three billing fields to `usage`: - **`cost`** — what this request cost, in `currency`. This is the per-request figure: attribute it to a user, tenant, or workflow without scraping a dashboard or maintaining your own price table. - **`currency`** — ISO-4217 code for `cost` and `remaining_balance` (`EUR`). - **`remaining_balance`** — your credit balance after this request. Costs are billed in integer micros (millionths of a euro) and rounded up to the next micro, so a request never bills below what it cost us. On very small requests that rounding is visible: the amount you are charged can exceed the exact computed cost by up to €0.000001. The `lowrouter_metadata` block carries the LowRouter-specific fields: - **`provider`** — which upstream actually served the request. - **`region`** — the region the upstream served from. - **`energy_wh`** and **`carbon_gco2e`** — the energy and carbon estimate for this request. Read [methodology](../sustainable-ai/methodology) before quoting these numbers anywhere. - **`routing_mode`**, **`routing_reason`**, **`fallback_occurred`**, and **`providers_attempted`** — the routing trace: how the model was picked and whether a fallback was used. To look up the full record for a request later, open its transaction on the dashboard. ## Stream the response For interactive UIs, set `"stream": true` and read Server-Sent Events: ```bash curl https://api.lowrouter.ai/v1/chat/completions \ -H "Authorization: Bearer $LOWROUTER_API_KEY" \ -H "Content-Type: application/json" \ -N \ -d '{ "model": "auto/mistralai/mistral-large-2512", "stream": true, "messages": [{"role": "user", "content": "Count to 5 slowly"}] }' ``` The stream is a Server-Sent Events stream in the same format the OpenAI streaming API uses, so any OpenAI-compatible SDK consumes it unchanged. See [integrations](../integrations/) for per-SDK snippets. Streaming does not cost you cost attribution. The final chunk before `data: [DONE]` carries `lowrouter_metadata` and a `usage` object with the same `cost`, `currency`, and `remaining_balance` fields the non-streaming response returns: ```json { "object": "chat.completion.chunk", "choices": [], "usage": { "cost": 0.000034, "currency": "EUR", "remaining_balance": 9.87 }, "lowrouter_metadata": { "provider": "openai", "...": "..." } } ``` Token counts appear exactly once across the stream. If the upstream provider already sent a usage chunk with `prompt_tokens` / `completion_tokens` / `total_tokens`, this final chunk carries the money fields only rather than restating them — so read tokens from whichever chunk carries them, and cost from the final one. If a stream is cut short — you cancel it, your client times out, or the connection drops — you are still billed for the tokens the provider generated before the cut. Those requests are marked `interrupted` on the dashboard, in the CSV export, and on the generation record, so you can reconcile them against your own logs. ## Things that might surprise you - **The `model` in the response is the resolved four-segment ID**, not necessarily the string you sent. If you sent an `auto/` or `alias/` ID, the response tells you which route actually ran. - **`usage` reflects upstream tokens**, which may include caching discounts (for providers that support them). The credits charged on your dashboard match this `usage`. - **The carbon estimate is absent on requests we couldn't classify**, e.g. a model whose parameter count is unknown. The dashboard shows the same record without an eco number rather than a fabricated one. ## If it didn't work A 401, 402, or 404 here almost always means a key, balance, or model-id problem. The [errors reference](../guides/errors) lists every payload the gateway returns, with the one-line fix for each. ## Next [Tour the dashboard →](dashboard-tour) --- # Tour the dashboard # Tour the dashboard The dashboard is at [/dashboard](/dashboard). It's the operator view for your account: balance, usage, eco impact, and shortcuts to the things you'll change most often. ## The landing page When you first land, four widgets are visible above the fold: - **Credit balance** — the current credit balance in your account currency, with a button to top up. The big number is what's left; the small number is what's been spent in the current calendar month. - **Usage today** — total tokens and total cost so far today, broken out by model. Click any model to filter the rest of the page. - **Top models (last 30 days)** — a horizontal bar chart ranking the models you used most by token count. - **Eco impact** — the energy and carbon estimate for the same window, with a comparison to a baseline you choose (see *Eco panel* below). Below the widgets, the **Recent transactions** table shows the last ~50 generations: timestamp, model, provider, region, tokens, cost, and the carbon estimate. Click any row to see the full record on its own page. ## Drill into a transaction The transaction detail page shows the full request record: - The request ID (you can copy it). - Resolved model, provider, region, latency. - Token counts (prompt, completion, total). - Eco numbers with the methodology that produced them. - Routing trace: which providers were considered, which one was picked, and why (the cheapest, the lowest-carbon, the closest, …). Prompt and response *content* are not shown — we don't store it. ## Eco panel The eco impact widget compares the last 30 days of your usage against a **baseline**. The baseline is a hypothetical: "what would this same usage have looked like if I'd used model X instead?" It's a back-of-envelope check, not a guarantee. The number it shows is honest about uncertainty: - The value is computed from the same energy formula and grid intensities as every other carbon number on the platform. - The "you saved" framing only appears when the baseline you picked has a higher per-token energy estimate than your actual usage. If your usage was higher-energy than your baseline, the panel says so. - When confidence is low (small samples, models whose parameters are estimated rather than verified), the number is shown with a reduced-confidence indicator and the underlying caveats are linked inline. Pick or change the baseline from **Settings → Eco baseline**. ## Where everything else is - **Keys** — create, scope, rotate, revoke. See [api-keys management](../guides/api-keys). - **Credits** — top up, view receipts. See [credits and billing](../guides/credits-and-billing). - **Invoices** — billing history; same data as the email receipts but downloadable as PDF. - **Auto-routing** — set defaults for `auto//`: prefer-region, prefer-low-carbon, fixed-provider, and per-key overrides. See [routing](../models/routing). - **Settings** — profile, eco baseline, notification preferences, account deletion. ## Mobile The dashboard is usable on a phone — header collapses to a hamburger menu, charts switch to a single-column layout. It's not yet built for heavy operations on mobile (large CSV exports, multi-key bulk edits); those flows are still desktop-first. ## Next You're set up. The [User guides](../guides/api-keys) section goes deeper on the day-to-day operations, and the [integrations](../integrations/) section has copy-paste snippets for talking to the gateway over HTTP. --- # Guides # Guides The pages in this section cover the operations you'll come back to after onboarding: - [API key management](api-keys) — create, scope, rotate, revoke, and the policies that API keys can carry. - [Credits and billing](credits-and-billing) — how credits work, how pricing is structured, and how invoices are produced. - [Usage accounting](usage-accounting) — the transaction history, exports, and what's recorded per request. - [Dashboard deep-dive](dashboard-deep-dive) — every chart on the dashboard, how to read it, and how to filter it. These build on [Getting started](../getting-started/account); pages there cover the first-time setup, pages here cover the ongoing operation. --- # API key management # API key management [Getting started → API keys](../getting-started/api-keys) walks through creating a key. This page is the operator reference: every option an API key can carry, when to use it, and how to retire keys safely. ## Anatomy of an API key An API key is a token with metadata attached. The token authenticates the request; the metadata controls what that request is allowed to do. The metadata you can attach: | Field | Purpose | |-------|---------| | `name` | Human label, shown in usage history. | | `models` | Allowlist of model IDs. Empty = all models. | | `region` | Pin requests through this key to a region (e.g. `eu-west`). | | `daily_credit_limit` | Max credits this key can spend per UTC day. | | `monthly_credit_limit` | Max credits per UTC calendar month. | | `expires_at` | Auto-expiry timestamp, set from a preset at creation (30 days / 90 days / 6 months / 12 months / never; default 90 days). Past it, the key returns `401 api_key_expired` and its secret is purged at rest. | | `enabled` | Toggle without deleting. | All of these can be edited from **Dashboard → Keys → key name → Edit**. Edits take effect on the next request, no caching delay. ## Scoping patterns A few patterns we see often: **One key per environment per service.** The most common shape: `prod-api`, `staging-api`, `local-dev`. Each one is allowlisted to the models that environment actually uses. **One key per third-party integration.** If you give a token to a desktop client (ChatBox, Cline, an IDE agent, …), put it in its own key with a daily limit. The blast radius of a leaked key is then "yesterday's daily limit" instead of "everything." **One key per researcher / experiment.** When the same project runs multiple lines of experiments, separate keys make the dashboard's top-models chart instantly readable per experiment. **One key per region requirement.** When a particular workload must stay in `eu-west`, set the region on the key rather than on every request. The constraint travels with the key and a misconfigured client can't accidentally send the request elsewhere. ## Limits and what happens when they're hit When a request would push a key over its `daily_credit_limit` or `monthly_credit_limit`, the gateway returns `429 Too Many Requests` with an explanatory body: ```json { "error": { "type": "rate_limit_error", "code": "key_daily_limit_exceeded", "message": "API key 'prod-api' has reached its daily credit limit (5.00).", "param": null } } ``` The response includes `Retry-After` indicating when the limit resets (midnight UTC). Bumping a limit takes effect immediately for subsequent requests. ## Rotation The rotation pattern that does not require zero-downtime support from the gateway: 1. **Create** a second key with the same scope. 2. **Deploy** it everywhere the old key was used (config, secret manager, CI variables). 3. **Verify** the new key is in use by watching the usage history; the old key's request rate should drop to zero. 4. **Delete** the old key. Aim to rotate at least every 90 days, and immediately after any of the events listed in [Getting started → API keys](../getting-started/api-keys#rotate-or-revoke). ## Revocation A revoked key returns `401 Unauthorized` on the next request. There is no warning, no grace period, no caching delay. Revocation cannot be undone — if you revoke the wrong key, create a new one. The dashboard preserves the key's usage history after revocation. The token itself is discarded. ## Audit trail Every key creation, edit, rotation, and revocation produces an entry in **Settings → Audit log**. The log records: who acted, when, on which key, and what changed. Export as CSV for retention in your own audit pipeline. --- # Credits and billing # Credits and billing LowRouter is pre-paid. You top up a credit balance, the gateway debits that balance per request, and the balance is your single source of truth for spend. ## Credits A credit is a fractional unit of EUR: 1 credit = €0.01, so €5 of credits adds 500 credits. Credits and balances are always denominated in EUR, and checkout is in EUR. At checkout, a flat payment-processing and platform fee is added to the credit amount — the same for every payment method and region — and VAT is added on top where applicable. The all-in price is shown before you pay; every euro of credit is yours to spend in full once delivered. Credits do not expire. Refunds for accidental top-ups are handled case-by-case via the support email on the [legal page](/impressum) within 14 days. The current balance is shown on the dashboard, on the credits page, and in the response of every request as `usage.remaining_balance` (with `usage.currency`) in the response body. ## What a request costs The cost of a request is: ``` upstream_provider_price_per_token × tokens + platform_fee_per_token × tokens ``` Both components are quoted per 1M tokens, separately for prompt and completion. The prices visible on the model browser and the model pages already include the platform fee. The breakdown is also visible on each transaction's detail page. A few details worth knowing: - **Cached prompt tokens** (when an upstream provider supports prompt caching) are charged at the upstream's cached rate. The platform fee is unchanged. - **Failed requests** that produced no upstream charge consume zero credits. A 4xx from the upstream that did consume tokens (rare) is passed through to your bill. - **Streaming responses** are charged on the same usage numbers as a non-streaming response — total tokens, not per-chunk. ## Top up **Dashboard → Credits → Add credits**, then complete the Stripe-hosted checkout. Card and SEPA Direct Debit (EU accounts) are supported. The amount you select is exclusive of VAT. The invoice produced after payment shows the net amount, the VAT amount, and the gross total. VAT treatment follows the country and (where applicable) VAT number on your billing profile. ## Invoices After every successful top-up, an invoice is generated and emailed. The same invoices are downloadable as PDF from **Dashboard → Invoices**. If you operate on behalf of a company: 1. **Settings → Billing** — set the legal name, billing address, and VAT number. 2. Invoices issued from that point onwards carry the company details. 3. Past invoices can be re-issued with the corrected billing block on request via support. ## Pricing changes Upstream provider prices change. We update the prices on the model browser and in the routing engine within one business day of an upstream price change going live. The dashboard records the per-request price at the moment of the request, so historical bills are stable even when current prices change. The platform fee is published per model on the model browser. Material changes to the platform fee are announced at least 30 days in advance to the email on the account. ## What we don't bill for - Failed authentication, rate-limited requests, or key-limit hits — zero credits. - Health checks (`HEAD /docs`, `HEAD /v1/models`, etc.) — zero credits. - Dashboard browsing, key management, or any control-plane action — zero credits. ## Refunds Refunds for unspent credit balances are not processed automatically. Contact support if you need to wind down an account; we'll process the refund of the remaining balance to the original payment method, subject to a 14-day cooling-off limit on the most recent top-up under EU consumer law. ## Tax LowRouter is operated by Carbonifer SAS, a French entity. VAT is charged at the rate applicable to your billing country. EU B2B customers with a valid VAT number are subject to reverse charge (no VAT on the invoice). Non-EU customers receive an invoice without VAT. --- # Usage accounting # Usage accounting Every request through the gateway produces a record. This page is the schema and the access patterns. ## Per-request record The fields stored for each request: | Field | Description | |-------|-------------| | `generation_id` | Opaque, globally unique. Returned in the response and used to look up the record later. | | `created_at` | UTC timestamp when the gateway accepted the request. | | `api_key_id` | The API key used. Names are joined in for display. | | `user_identifier` | Identifier of the account the key belongs to. | | `model_id` | The model that served the request. When the caller sent an auto-routed or aliased ID (e.g. `auto/mistralai/mistral-large-2512`), this is the resolved route. | | `provider_id` | Upstream provider that served the request. | | `region` | Region the upstream served from. | | `prompt_tokens` | Token count of the input. | | `completion_tokens` | Token count of the output. | | `total_tokens` | Sum. | | `latency_ms` | First-byte latency for streaming, end-to-end for non-streaming. | | `request_duration_ms` | Total request duration measured at the gateway. | | `cost` | Amount debited for this request, in EUR. | | `energy_wh` | Estimated energy for the inference, in watt-hours. | | `carbon_gco2e` | Estimated CO₂e for the request, in grams. | | `routing_mode` | How the model was selected (e.g. explicit vs. auto). | | `routing_reason` | Why the router picked this model/provider. | | `fallback_occurred` | Whether a fallback provider served the request after a primary failed. | | `status_code` | HTTP status returned to the caller. | Prompt and response *content* are not stored. ## Where to read it - **API lookup** — a completion response carries an `id` (the same `chatcmpl-…`/`cmpl-…` value OpenAI clients already read). Pass it to `GET /v1/generation/{id}` for the full record, or to `GET /v1/metrics/{id}` for just the token/energy/carbon figures. Both require the same API key and only return records billed to that key's account. See **Looking up a generation** below. - **Dashboard → Recent transactions** — the last ~50 requests, with filtering by date range, model, provider, region, key. - **Transaction detail page** — the full record for a single request, accessed by clicking a row in the transactions table (the detail path is keyed on the transaction's `referenceId`). - **Export** — **Dashboard → Recent transactions → Export** produces a CSV of the filtered range, capped at 50,000 rows per export. Use this for programmatic queries, reconciliation, or piping into your own data warehouse. ## Looking up a generation Every completion response includes a top-level `id`. That value is the generation's lookup key — there is no separate `generation_id` field in the response body. ```bash # The id from a prior chat/completions response GENERATION_ID="chatcmpl-abc123" curl https://api.lowrouter.ai/v1/generation/$GENERATION_ID \ -H "Authorization: Bearer $LOWROUTER_API_KEY" ``` The record is scoped to the calling key's account: a key can only read generations billed to its own customer. An unknown id — or one that belongs to another account — returns `404`, so the id space can't be probed across accounts. For just the metrics (tokens, energy, carbon, duration) without the routing detail, use `GET /v1/metrics/{id}` with the same id. ## Aggregates The dashboard pre-computes a small set of aggregates and updates them on each request: - Tokens per day, per model. - Cost per day, per model. - Energy and carbon per day, per model. These aggregates power the charts. They are derived from the per-request records and are reproducible from a CSV export. ## Retention | Data | Retention | |------|-----------| | Per-request records | 13 months from `created_at`. | | Daily aggregates | 36 months. | | Audit log entries | 36 months. | | Account profile | For the lifetime of the account. | After retention expires the per-request rows are deleted and replaced by anonymised aggregates. Aggregates are kept for sustainability reporting and platform analytics; they cannot be used to reconstruct individual requests. You can request earlier deletion of all your usage records via the support email; the deletion is irreversible and may affect your ability to dispute past invoices. ## Reconciliation tips - The sum of `cost` over a day should equal the daily cost on the dashboard within a rounding tolerance. - The sum of `total_tokens` over a day grouped by model is what the upstream provider's usage report (if you have one) should show. - The carbon estimate is reproducible: given the same `model_id`, `region`, and `total_tokens`, recomputing with the formula in [methodology](../sustainable-ai/methodology) should yield the same gram count. If the numbers diverge more than rounding allows, that is a bug — [open an issue](https://github.com/carbonifer/lowrouter/issues). --- # Dashboard deep-dive # Dashboard deep-dive [Dashboard tour](../getting-started/dashboard-tour) is the lap-around. This page is the per-chart reference: what each one shows, what it's derived from, and the gotchas. ## Filters A date-range picker, a model picker, and a key picker sit at the top of the dashboard. Setting any filter reloads every widget on the page with the same filter applied. Filters are reflected in the URL so links are shareable. The default range is the last 30 days, ending today (UTC). Changing the range: - Updates the **Top models** ranking. - Updates the **Eco impact** comparison. - Restricts the recent-transactions table. It does **not** change your credit balance, which is always live. ## Credit balance widget - **Big number**: current balance. - **Small number**: spent in the current calendar month. - The colour of the band underneath is a heuristic: green if your current burn-rate would last the full calendar month, amber if not. The widget pulls live; clicking **Top up** opens the Stripe checkout. ## Usage today widget - **Top number**: total tokens today (UTC), all models. - **Bottom number**: total credits spent today. - **Bars**: per-model breakdown of today's tokens. Hover any bar for the per-model token and credit total. Click a bar to filter the rest of the dashboard by that model. ## Top models (last 30 days) - Horizontal bar chart, ranked by total tokens descending. - Each bar shows tokens; the value next to the bar shows credits. - The 30-day window is fixed regardless of the page-level filter so the ranking is stable across page loads. When fewer than five models have non-trivial usage, the chart shows just the ones with data rather than padding with empty bars. ## Eco impact widget The widget has three numbers and one comparison: - **Energy** — total Wh estimated for the filtered range. - **Carbon** — total gCO₂e estimated for the filtered range. - **Per 1K tokens** — the normalised value, useful for comparing across ranges of different sizes. - **Comparison** — the same usage replayed against your chosen baseline model. If your actual usage was lower-energy than the baseline, the widget reports the saving; if higher, it reports the gap. Pick or change the baseline from [Settings → Eco baseline](/dashboard/settings). The widget refuses to display a comparison when the underlying estimates' confidence is too low to be meaningful — the visible number becomes "—" with a note explaining why. ## Recent transactions table - Default sort: newest first. - Sortable columns: timestamp, model, tokens, cost, latency. - Filterable columns: model, provider, region, status code. - Clicking a row opens its detail page. ## Per-day usage chart Below the recent-transactions table, a stacked column chart shows tokens per day, stacked by model, for the filtered range. This is the chart to use when explaining usage growth or detecting a spike. The chart respects the page-level model filter — selecting a model up top filters the chart to that one. ## Per-day cost chart Same shape as the per-day usage chart but in credits. Use it for budgeting and burn-rate analysis. The two charts share the same time axis so they can be compared visually. ## Provider distribution A donut showing the share of requests served by each upstream provider in the filtered range. It's the fastest way to confirm a routing-policy change actually took effect. When a policy is supposed to keep traffic in a single provider but the donut shows multiple wedges, check the policy and the per-request `routing_mode`, `routing_reason`, and `fallback_occurred` fields for the requests that escaped. ## Mobile dashboard On a phone the dashboard collapses to a single column, the filters move into a slide-up panel, and the per-day charts become scrollable. The recent-transactions table becomes a vertical card list. Heavy operations (large CSV exports, multi-key edits) still want the desktop view. --- # Pricing and currency conversion # Pricing and currency conversion Most providers we route to publish their per-token prices in USD rather than EUR. To keep accounting and balances simple, **every balance and every charge is in EUR**, and we convert non-EUR provider prices to EUR once a day before they reach the catalogue. This page explains how that conversion works, why the displayed price on a USD-billed provider isn't quite the same as the headline USD figure, and what happens when our FX feed is unavailable. ## How the conversion is computed For each non-EUR provider price we ingest, the stored EUR value is: ``` stored_eur_per_1m_tokens = source_per_1m_tokens × source_to_eur_rate × (1 + fx_buffer_percent / 100) ``` - **`source_per_1m_tokens`** — the price the provider publishes in their billing currency (USD for most providers). - **`source_to_eur_rate`** — the daily rate that converts the provider's currency into EUR. The primary source is [Airwallex](https://www.airwallex.com/); if Airwallex is unavailable, we fall back to the reference rates published by the European Central Bank at the [eurofxref-daily.xml](https://www.ecb.europa.eu/stats/eurofxref/eurofxref-daily.xml) endpoint. - **`fx_buffer_percent`** — a fixed conversion markup applied on top of the FX rate, defaulting to **1 %**. The markup covers FX-spread drift between the day we fetched the rate and the day we settle with the provider, plus payment-rail conversion fees. For example, a provider publishing `$0.15 per 1M tokens` at a reference rate of `1 EUR = 0.90 USD` (so `1 USD = 1.111 EUR`) and a 1 % markup becomes: ``` 0.15 × 1.111 × 1.01 ≈ 0.1683 EUR per 1M tokens ``` That is what your balance is debited for every 1M tokens you spend on that model. ## Where you see the dual figures Providers that already bill in EUR (e.g. `/providers/scaleway`) render a single figure: their published EUR price, shown as-is with no conversion. Providers that bill in USD (most of them — OpenAI, Anthropic, Bedrock, Vertex, Groq, …) render both numbers: ``` $0.15/M (€0.17 billed) ``` The first figure is the upstream price you'd see on the provider's own pricing page. The second is the EUR value your balance is debited at, computed with the formula above. Hovering the price shows the FX rate, the markup, and when the conversion was last refreshed. ## Refresh cadence We pull FX rates once per ingest run, which runs daily. When the fallback ECB feed is in use, note that ECB publishes on TARGET business days at ~16:00 CET, and Saturday and Sunday reuse Friday's published rate — the standard ECB convention. Your debit at request time uses the most recent stored EUR value. **This means the price you see in the catalogue today may differ slightly from what was billed yesterday for the same number of tokens** — by the size of the FX move plus markup drift. ## When the FX feed is unavailable If we cannot reach our FX sources (both Airwallex and the ECB fallback) during an ingest run, we **soft-disable** the affected providers (the USD-billed ones that need conversion) until the feed recovers. While soft-disabled, those providers are visible in the catalogue but won't accept new requests, and an internal alert (`FX_INGEST_STALE`) notifies the team. We deliberately do not fall back to a hard-coded rate. A guessed rate is worse than visible unavailability — it can either silently under-charge our margin or over-charge customers, and neither is something we want to do quietly. ## What we don't claim - **We don't offer a rate lock.** The price you see today is the price we charge today; tomorrow's rate may differ. If you need a fixed price, the model's EUR figure is the one we honour at the moment of the request, not a quote held in advance. - **We don't pass through every provider price change in real time.** The catalogue reflects the most recent successful ingest, which is daily. A provider price change mid-day will land in the next run. - **We don't bill in any currency other than EUR.** EUR is our single operating currency: balances, charges, the price list, and checkout are all EUR. Providers that publish in USD are converted once a day as described above, which keeps a single ledger and a single set of accounting rules. ## Configuration For self-hosted operators, the conversion markup can be tuned via the `LOWROUTER_FX_BUFFER_PERCENT` environment variable (default `1.0`, clamped to `[0, 20]`). The change takes effect on the next ingest run. --- # Errors reference # Errors reference Every error the inference API returns uses one envelope: ```json { "error": { "type": "invalid_request_error", "message": "human-readable explanation", "code": "machine-readable code", "param": "the offending field, when there is one" } } ``` `type` is one of a small fixed set (`authentication_error`, `invalid_request_error`, `not_found`, `provider_error`, `provider_unavailable`, `internal_server_error`). `code` is more specific — branch on it in code, show `message` to humans. The messages below are the real strings the gateway produces, not paraphrases. ## 401 — authentication You'll see one of: ```json { "error": { "type": "authentication_error", "message": "Missing Authorization header", "code": "authentication_error" } } ``` ```json { "error": { "type": "authentication_error", "message": "API key not registered. Check the key was copied in full, or create a new one at /dashboard/keys. If it was working before, it may have been revoked.", "code": "api_key_not_registered" } } ``` A key whose *shape* rules it out gets a different code, because the fix is different — and we can tell without looking it up: ```json { "error": { "type": "authentication_error", "message": "API key is malformed: the key body is 20 characters, expected 43 — this is usually a copy-paste truncation. Re-copy the full token, or create a new key at /dashboard/keys.", "code": "api_key_malformed" } } ``` `api_key_malformed` covers a truncated or over-copied key (the body is not exactly 43 characters), a token that doesn't start with `sk-lr-`, and the retired `sk-bf-*` scheme: ```json { "error": { "type": "authentication_error", "message": "API key is malformed: this is a retired sk-bf-* key and the scheme is no longer accepted. Create a new key at /dashboard/keys.", "code": "api_key_malformed" } } ``` ```json { "error": { "type": "authentication_error", "message": "API key has expired. Create a new key in the dashboard.", "code": "api_key_expired" } } ``` **Fix:** send `Authorization: Bearer sk-lr-...` with a key from [the dashboard](../getting-started/api-keys). `api_key_malformed` means the token can't be one of ours as written — nearly always a copy-paste truncation, so re-copy the whole thing. `api_key_not_registered` means the shape is right but no account owns it: check you copied the whole key and are using the right account, or mint a new one; a key that used to work may have been revoked. `api_key_expired` means the key reached its expiry date: keys have a bounded lifetime (default 90 days), so create a fresh key in the dashboard and rotate it in — the expired one stays listed there as a reminder but can no longer authenticate. ## 402 — insufficient credits ```json { "error": { "type": "invalid_request_error", "message": "Insufficient credits. Balance: $0.0000. Your balance must be above zero to make requests. Please add credits to your account.", "code": "insufficient_credits" } } ``` The balance is checked before the request is forwarded, so a 402 never costs you tokens. Any positive balance admits a request; because usage is settled after the response, a single request can take the balance below zero, and further requests 402 until you top up. **Fix:** top up in [Credits & billing](credits-and-billing). ## 400 — malformed request Missing or empty `messages`: ```json { "error": { "type": "invalid_request_error", "message": "messages field is required and cannot be empty", "code": "invalid_request_error", "param": "messages" } } ``` A model id that doesn't match the public shape (`{provider}/{creator}/{model}[/{locode}]` — see [available models](../models/available)): ```json { "error": { "type": "invalid_request_error", "message": "model id must be {provider}/{creator}/{model}[/{locode}]: got 5 segments", "code": "invalid_request_error", "param": "model" } } ``` An alias name that violates the naming rule: ```json { "error": { "type": "invalid_request_error", "message": "alias name must be 1-64 chars: lowercase a-z, 0-9, '-' or '_', starting with a letter or digit (got \"My Alias\")", "code": "invalid_alias_name", "param": "model" } } ``` **Fix:** the message names the field; `param` confirms it. Compare against the request shape in [your first completion](../getting-started/first-completion). ## 404 — the route doesn't exist Unknown model on a provider: ```json { "error": { "type": "not_found", "message": "model \"anthropic/claude-opus-4.6\" is not offered by provider \"openai\"", "code": "not_found", "param": "model" } } ``` A model that is only served in specific regions, addressed with an explicit `/global` it doesn't have. (Omitting the region entirely does **not** produce this error — it resolves to the model's default region; see [how IDs resolve](../models/routing#how-ids-resolve). You only see this when you pin `global` explicitly.) ```json { "error": { "type": "not_found", "message": "model \"anthropic/claude-opus-4.6\" on provider \"vertex\" is served only in specific regions and has no global endpoint. Append a locode (e.g. \"vertex/anthropic/claude-opus-4.6/{locode}\") and check /providers/vertex for available regions, or omit the region to route to the model's default one", "code": "not_found", "param": "model" } } ``` An alias that doesn't exist on your account: ```json { "error": { "type": "not_found", "message": "alias \"alias/big\" does not exist on this account — create or repoint aliases in the dashboard at /dashboard/aliases", "code": "alias_not_found", "param": "model" } } ``` **Fix:** browse the [model catalog](../models/available) or `GET /v1/models` for exact ids; aliases are per-account, so a key from another account won't see yours. ## 503 — the route exists but can't serve you right now A region the model isn't served in. This is deliberate: we never silently substitute a different region — the region you pin is the region you get, even when a "nearby" one would work: ```json { "error": { "type": "provider_unavailable", "message": "model \"mistral/mistral-large\" is not served in region \"de-ber\" on provider \"mistral\" — check /providers/mistral for available regions", "code": "provider_unavailable", "param": "model" } } ``` A model we've marked unavailable says so with `"code": "model_unavailable"` and names the reason. When the route itself fails upstream, the request returns 503 with `"type": "provider_error"` naming the provider that failed. LowRouter does not silently retry on a different provider — an explicit ID is a pin and an auto-routed request commits to the winner of its ranking — so the provider you see is the provider you asked for, directly or by delegation. `lowrouter_metadata.providers_attempted` shows what was tried. **Fix:** pick a served region from `/providers/{provider}`, or point an [alias](../models/aliases) at an alternative route so clients don't need redeploying when a region is down. ### Daily quota exhausted When an upstream provider has spent its daily token quota for a route, retrying is pointless until the quota resets — so the gateway tells you exactly that, with `"code": "provider_quota_exhausted"` and a `Retry-After` header carrying the seconds until the reset (midnight UTC): ```json { "error": { "type": "provider_unavailable", "message": "Provider aws-bedrock has exhausted its daily token quota for bedrock/anthropic.claude-3-5-sonnet-20240620-v1:0 in jp-tyo. The quota resets at midnight UTC; retry after the Retry-After interval.", "code": "provider_quota_exhausted", "param": "model" } } ``` The region in the message is the locode the route is billed and reported under — the same one that appears as the fourth segment of a model ID — not the upstream provider's own region name. While the quota is exhausted the affected region also disappears from `/v1/models`, so a fresh listing won't hand you an ID that can't serve. Your pin is still honoured — nothing is substituted. **Fix:** honour `Retry-After` instead of hammering the route, or point the request (or an [alias](../models/aliases)) at another region or provider serving the same model. ## 429 — rate limited The only `429` the gateway itself emits is its per-IP limiter, with a `Retry-After` header: ```json { "error": { "type": "invalid_request_error", "message": "Rate limit exceeded. Too many requests from your IP address. Try again in 30 seconds", "code": "rate_limit_exceeded" } } ``` An *upstream provider's* rate limit is not passed through as a 429: it surfaces as one of the `503` provider failures above. **Fix:** honour `Retry-After` and back off with jitter — or route the workload across more than one provider, which is rather the point of a router. ## What you'll never get No silent substitutions: a failed constraint (region, jurisdiction, alias) is an error, not a quiet reroute to somewhere you didn't ask for. If you got a 2xx, the response's `lowrouter_metadata` tells you exactly who served it and where — see [per-request metadata](../models/per-request-metadata). --- # Integrations # Integrations LowRouter speaks the OpenAI Chat Completions API. Anything that talks to OpenAI talks to LowRouter — usually by changing the base URL and the API key. The pages in this section are short on purpose. Each one is a working snippet plus the two or three things that surprise people on first use. - [curl](curl) — the bare HTTP request, useful for debugging and as the source of truth. - [OpenAI SDK (Python)](openai-sdk-python) - [OpenAI SDK (TypeScript / JavaScript)](openai-sdk-typescript) - [ChatBox](chatbox) — desktop chat client. - [OpenCode](opencode) — terminal coding assistant. - [Cline](cline) — VS Code coding agent. - [Goose](goose) — Block's open-source agent. - [Generic OpenAI-compatible clients](openai-compatible) — the pattern for anything not on this list. The base URL for every integration is: ``` https://api.lowrouter.ai/v1 ``` The auth header is: ``` Authorization: Bearer $LOWROUTER_API_KEY ``` That's the whole deal. The rest is per-tool configuration. --- # curl # curl The simplest way to talk to the gateway. If something works in `curl` but not in your SDK, the SDK is the thing to debug. ## A non-streaming completion ```bash curl https://api.lowrouter.ai/v1/chat/completions \ -H "Authorization: Bearer $LOWROUTER_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "model": "auto/mistralai/mistral-large-2512", "messages": [ {"role": "user", "content": "In one sentence, what is a vector database?"} ] }' ``` The response is a standard OpenAI-shaped chat-completions object, with a `lowrouter_metadata` block carrying the provider, region, and eco numbers for the request. ## A streaming completion Use `-N` to disable curl's output buffering, and set `"stream": true` in the body: ```bash curl -N https://api.lowrouter.ai/v1/chat/completions \ -H "Authorization: Bearer $LOWROUTER_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "model": "auto/mistralai/mistral-large-2512", "stream": true, "messages": [{"role": "user", "content": "Count to 5 slowly"}] }' ``` The response is a Server-Sent Events stream, in the same format the OpenAI streaming API uses. ## Listing models ```bash curl https://api.lowrouter.ai/v1/models \ -H "Authorization: Bearer $LOWROUTER_API_KEY" ``` Returns the routable models with their per-token prices and basic metadata. Cache the result locally — it does not change between requests within a single user session. ## Pinning a provider and region Pin the route yourself by sending an explicit model ID instead of an `auto/` one. To pin a region, append a UN/LOCODE as the fourth segment of the model ID (`{provider}/{creator}/{model}/{locode}`); omit it to use the default region: ```bash curl https://api.lowrouter.ai/v1/chat/completions \ -H "Authorization: Bearer $LOWROUTER_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "model": "vertex/anthropic/claude-opus-4.6/sg-sin", "messages": [{"role": "user", "content": "Hello"}] }' ``` See [routing](../models/routing) for how regions and providers are selected. ## Looking up a generation later Every completion is recorded on the dashboard. Open the transaction view there to see the full record, including the eco numbers and the routing trace. --- # OpenAI SDK (Python) # OpenAI SDK (Python) The OpenAI SDK is the canonical client. It works with LowRouter unchanged once you set `base_url` and `api_key`. ## Install ```bash pip install openai ``` ## A non-streaming completion ```python import os from openai import OpenAI client = OpenAI( base_url="https://api.lowrouter.ai/v1", api_key=os.environ["LOWROUTER_API_KEY"], ) response = client.chat.completions.create( model="auto/mistralai/mistral-large-2512", messages=[ {"role": "user", "content": "In one sentence, what is a vector database?"} ], ) print(response.choices[0].message.content) ``` ## A streaming completion ```python stream = client.chat.completions.create( model="auto/mistralai/mistral-large-2512", messages=[{"role": "user", "content": "Count to 5 slowly"}], stream=True, ) for chunk in stream: delta = chunk.choices[0].delta.content if delta: print(delta, end="", flush=True) ``` ## Reading the eco metadata LowRouter's per-request metadata lives outside the OpenAI schema, so the typed SDK fields don't surface it. Read it from the raw response: ```python response = client.chat.completions.create( model="auto/mistralai/mistral-large-2512", messages=[{"role": "user", "content": "hi"}], ) extra = response.model_extra or {} meta = extra.get("lowrouter_metadata", {}) if meta: print(f"{meta['carbon_gco2e']:.4f} gCO2e via {meta['provider']} " f"({meta['region']})") ``` `response.model_extra` is the canonical Pydantic-v2 escape hatch for non-schema fields. On older SDK versions the attribute is `response.__pydantic_extra__`. ## Pinning a region There is no separate route field. Pin a region by appending a UN/LOCODE as the fourth segment of the model ID (`{provider}/{creator}/{model}/{locode}`); omit it to use the default region: ```python response = client.chat.completions.create( model="vertex/anthropic/claude-opus-4.6/sg-sin", messages=[{"role": "user", "content": "hi"}], ) ``` ## Async The async client follows the same pattern: ```python import asyncio from openai import AsyncOpenAI client = AsyncOpenAI( base_url="https://api.lowrouter.ai/v1", api_key=os.environ["LOWROUTER_API_KEY"], ) async def main(): r = await client.chat.completions.create( model="auto/mistralai/mistral-large-2512", messages=[{"role": "user", "content": "hi"}], ) print(r.choices[0].message.content) asyncio.run(main()) ``` --- # OpenAI SDK (TypeScript) # OpenAI SDK (TypeScript) ## Install ```bash npm install openai ``` ## A non-streaming completion ```ts import OpenAI from "openai"; const client = new OpenAI({ baseURL: "https://api.lowrouter.ai/v1", apiKey: process.env.LOWROUTER_API_KEY, }); const response = await client.chat.completions.create({ model: "auto/mistralai/mistral-large-2512", messages: [ { role: "user", content: "In one sentence, what is a vector database?" }, ], }); console.log(response.choices[0].message.content); ``` ## A streaming completion ```ts const stream = await client.chat.completions.create({ model: "auto/mistralai/mistral-large-2512", stream: true, messages: [{ role: "user", content: "Count to 5 slowly" }], }); for await (const chunk of stream) { const delta = chunk.choices[0]?.delta?.content; if (delta) process.stdout.write(delta); } ``` ## Reading the eco metadata The TypeScript types do not include LowRouter's extra fields. Cast or narrow when you read them: ```ts type LowRouterMeta = { provider: string; region: string; energy_wh: number; carbon_gco2e: number; carbon_intensity_gco2_per_kwh: number; estimation_methodology: string; routing_mode: string; routing_reason: string; fallback_occurred: boolean; providers_attempted: string[]; }; const r = await client.chat.completions.create({ /* ... */ }); const meta = (r as unknown as { lowrouter_metadata?: LowRouterMeta }) .lowrouter_metadata; if (meta) { console.log( `${meta.carbon_gco2e.toFixed(4)} gCO2e via ${meta.provider} (${meta.region})`, ); } ``` ## Pinning a region There is no separate route field. Pin a region by appending a UN/LOCODE as the fourth segment of the model ID (`{provider}/{creator}/{model}/{locode}`); omit it to use the default region: ```ts const response = await client.chat.completions.create({ model: "vertex/anthropic/claude-opus-4.6/sg-sin", messages: [{ role: "user", content: "hi" }], }); ``` ## Browser usage The OpenAI SDK warns against running with an API key in the browser because the key is then exposed to every page visitor. The same applies to LowRouter: keep your `LOWROUTER_API_KEY` server-side and proxy requests from a backend you control. If you need a signed, short-lived token for a browser client, server-side endpoint that mints one is the right shape. --- # ChatBox # ChatBox [ChatBox](https://chatboxai.app/) is an open-source desktop chat client. Configure it to use LowRouter as a custom OpenAI provider. ## Configure 1. Open **Settings → Model Provider**. 2. Click **Add custom provider**. 3. Fill in: - **Name**: `LowRouter` - **API Mode**: `OpenAI API Compatible` - **API Host**: `https://api.lowrouter.ai/v1` - **API Path**: `/chat/completions` - **API Key**: your `sk-lr-...` token 4. Save. ## Pick a model Under **Model**, choose **Custom model name** and enter a LowRouter model ID — `auto/mistralai/mistral-large-2512`, `openai/openai/gpt-4.1`, `anthropic/anthropic/claude-sonnet-4.5`, etc. The full list is on the [model browser](/models). ## Recommended setup - **Use a key dedicated to ChatBox.** Keep its `daily_credit_limit` small (e.g. 1 credit/day for a personal account). A leaked key bounded to "yesterday's daily limit" is much less painful than a leaked production key. - **Disable telemetry on the ChatBox side** if you care about not exposing prompt content to the ChatBox publisher's analytics. ChatBox itself does not see prompts in normal operation, but features like crash reporting can capture context. - **Stream replies on**. The desktop UX expects streaming and LowRouter supports it the same way OpenAI does. ## Troubleshooting - **401 / Unauthorized**: confirm the API key starts with `sk-lr-` and has not been revoked. Test with `curl` from [the curl page](curl) using the same key. - **404 / Not Found**: the **API Path** must be `/chat/completions`. Some ChatBox versions default to `/v1/chat/completions`, which becomes `https://api.lowrouter.ai/v1/v1/chat/completions` — drop the leading `/v1/`. - **Model not available**: look up the exact model ID on the [model browser](/models). Auto-complete in ChatBox is not always accurate. --- # OpenCode # OpenCode [OpenCode](https://opencode.ai/) is a terminal coding assistant. It expects an OpenAI-compatible endpoint, which is what LowRouter exposes. ## Configure OpenCode reads its config from `~/.config/opencode/opencode.json`. Point the OpenAI provider entry at LowRouter: ```json { "providers": { "openai": { "baseURL": "https://api.lowrouter.ai/v1", "apiKey": "sk-lr-..." } }, "defaultModel": "auto/mistralai/mistral-large-2512" } ``` Restart OpenCode after editing the file. ## Picking a model Inside OpenCode, run `/model` and pick from the list. If a model isn't listed, type the LowRouter model ID directly — any model on the [model browser](/models) is routable. For coding tasks, name a capable model and let LowRouter pick where it runs — `auto/mistralai/mistral-large-2512`. Choosing the model is still yours: - For long contexts: a model with ≥128K context window. The model browser tags context length per model. - For latency-sensitive iteration: an `*-mini` or `*-haiku-*` variant. - For careful reasoning: a top-tier reasoning model. ## Recommended setup - **Dedicated key with a daily limit.** OpenCode is interactive and it's easy to lose track of how many tokens you spent in an afternoon. A daily limit on the key bounds the surprise. - **Disable shell-execution tools by default.** OpenCode supports letting the model run shell commands; turn that off until you've reviewed the prompts the agent sends. Enable it per-session for the workflow that needs it. - **Stream on.** Default in OpenCode; mentioned for completeness. ## Troubleshooting - **Hangs on the first request**: confirm `baseURL` ends with `/v1` (no trailing slash). OpenCode appends `/chat/completions` itself. - **Model "not found"**: the model isn't in OpenCode's autocomplete list, but it is routable. Run `/model auto/mistralai/mistral-large-2512` to confirm the gateway is reachable, then use the explicit model ID. --- # Cline # Cline [Cline](https://cline.bot/) is a VS Code extension that runs a coding agent against an LLM provider. It supports any OpenAI-compatible endpoint. ## Configure 1. Install the **Cline** extension from the VS Code marketplace. 2. Open the Cline sidebar. 3. Click the gear icon, then **Settings**. 4. Under **API Provider**, pick **OpenAI Compatible**. 5. Fill in: - **Base URL**: `https://api.lowrouter.ai/v1` - **API Key**: your `sk-lr-...` token - **Model ID**: any LowRouter model ID, e.g. `anthropic/anthropic/claude-sonnet-4.5` or `auto/anthropic/claude-sonnet-4.5`. 6. Save and start a task. ## Recommended setup - **Separate key for Cline**. Cline can burn tokens fast on agentic tasks (read file → think → edit → re-read). A dedicated key with a per-day limit is a cheap insurance policy. - **Pin the route with an explicit model ID** for repeatable pricing. An `auto/` ID keeps the model fixed but may serve it from a different provider or region — same output, different per-token price — which can surprise you when you compare daily costs. - **Read the diff every time.** Cline produces real edits to your workspace. The dashboard's transaction-detail page shows exactly what was sent (token counts, model, cost) but not the prompt or the response — the source of truth is the diff in your editor. ## Troubleshooting - **"Model does not support tools"**: not every model exposes tool use. The model browser tags `tool_use: true` on supported models. Pick one that does — auto-routing never changes which model you get, so it cannot rescue a model that lacks tool support. - **"Context window exceeded"**: the file or selection you fed the agent is larger than the model's context. Switch to a longer-context model or trim the context. - **401**: confirm the API key. - **Latency feels off**: check the **provider** field on the transaction detail page. Cline doesn't expose it; LowRouter does. --- # Goose # Goose [Goose](https://block.github.io/goose/) is Block's open-source agent. It supports OpenAI-compatible providers via configuration. ## Configure Edit `~/.config/goose/config.yaml`: ```yaml GOOSE_PROVIDER: openai OPENAI_HOST: https://api.lowrouter.ai/v1 OPENAI_API_KEY: sk-lr-... GOOSE_MODEL: auto/mistralai/mistral-large-2512 ``` Or set them as environment variables before starting Goose: ```bash export GOOSE_PROVIDER=openai export OPENAI_HOST=https://api.lowrouter.ai/v1 export OPENAI_API_KEY=sk-lr-... export GOOSE_MODEL=auto/mistralai/mistral-large-2512 goose session ``` ## Picking a model Set `GOOSE_MODEL` to any LowRouter model ID. For agentic tasks (file reading, shell tools, multi-step reasoning), pick a model tagged with `tool_use: true` on the [model browser](/models). ## Recommended setup - **Dedicated key, daily limit.** Same reasoning as the other agents: agentic loops can run away. - **Limit the toolset Goose has access to.** Goose's `extensions` config lets you allow only the tools the workflow needs. Fewer enabled tools = fewer surprises. - **Set a step limit.** Goose has a max-step setting; cap it at a small number for unattended runs. ## Troubleshooting - **Goose immediately exits with a config error**: `OPENAI_HOST` does not include a trailing slash. Match the value above exactly. - **Tool calls fail silently**: verify the chosen model actually supports tool use (model browser, `tool_use: true`). Some smaller models don't. --- # Generic OpenAI-compatible clients # Generic OpenAI-compatible clients If a tool isn't on this list, it almost certainly works as long as it exposes two settings: **base URL** and **API key**. The pattern below is what to fill in. ## Settings to set | Setting | Value | |---------|-------| | Provider type | OpenAI Compatible (sometimes "Custom OpenAI" or "OpenAI API") | | Base URL | `https://api.lowrouter.ai/v1` | | API Key | your `sk-lr-...` token | | Path / endpoint | `/chat/completions` (most tools handle this automatically) | | Model | any LowRouter model ID — `auto/mistralai/mistral-large-2512`, `openai/openai/gpt-4.1`, … | ## What does *not* work - **Tools that hard-code `https://api.openai.com`** without a base-URL setting cannot be redirected. Some have a `OPENAI_API_BASE` environment variable that achieves the same thing. - **Tools that require a specific model ID format** (e.g. `gpt-4` with no provider prefix) need the model picker reconfigured to accept arbitrary strings — most have a "custom model name" field. - **Tools that send Anthropic-shaped requests on the OpenAI endpoint** will be rejected. LowRouter accepts OpenAI-shaped chat-completions requests only — send those instead. ## Confirming it works Before integrating, test from the command line that the tool's settings are right: ```bash curl https://api.lowrouter.ai/v1/chat/completions \ -H "Authorization: Bearer YOUR_KEY" \ -H "Content-Type: application/json" \ -d '{"model":"auto/mistralai/mistral-large-2512","messages":[{"role":"user","content":"hi"}]}' ``` If `curl` returns a completion, the tool will too — once it's configured with the same URL and key. ## Tools we know work without changes - **LangChain** (`OpenAI` and `ChatOpenAI` classes — set `openai_api_base` and `openai_api_key`). - **LlamaIndex** (`OpenAI` and `OpenAILike` LLMs). - **LiteLLM** (proxy and library — set `api_base` and `api_key`). - **Vercel AI SDK** (`createOpenAI` from `@ai-sdk/openai-compatible`). - **Continue.dev** (`provider: openai-aiohttp` with `apiBase`). - **LM Studio** (Server tab → custom backend). ## When to prefer the OpenAI path over a tool's native provider If a tool has both an "OpenAI" and an "LowRouter / OpenRouter / custom gateway" option, prefer the OpenAI Compatible one. It exposes the fewest surprises: the tool sends a standard chat-completions request, LowRouter resolves the route, and the response is in the shape the tool already expects. --- # Models & providers # Models & providers Three short pages on the routing layer: - [Available models](available) — what's on the platform, how to read the model browser, and the IDs you'll use in requests. - [Routing](routing) — what `auto//` does, how ties are broken, and what overrides do what. - [Per-request metadata](per-request-metadata) — the `lowrouter_metadata` block on every response, field by field. The dashboard's [model browser](/models) is the live, searchable view of the same data. --- # Available models # Available models The full catalogue lives on the [model browser](/models). It's generated from the same data the API exposes at `GET /models`, so the two agree by construction. ## How model IDs are formed A model ID has three parts, with an optional fourth for region pinning: ``` //[/] ``` Examples: - `openai/openai/gpt-4o-mini` - `anthropic/anthropic/claude-sonnet-4.5` - `vertex/anthropic/claude-opus-4.6/sg-sin` The `` segment is the upstream that serves the request; the `` segment is who created the model (these differ for re-hosted models); the `` segment is the model name, with version numbers written dotted (e.g. `claude-sonnet-4.5`). Two-part IDs are rejected — a request must carry at least the three-part form. The optional fourth segment is a [UN/LOCODE](https://unece.org/trade/cefact/unlocode-code-list-country-and-territory) that pins the request to a specific region (e.g. `sg-sin` for Singapore). Appending it is how you route a request through a particular region. Every ID listed here is callable as printed. If you omit the region, it resolves to the model's global endpoint or — for models served only in specific regions — to its lowest-carbon one; if you omit the version, it resolves to the most recent version of that same model. The response echoes the fully resolved ID. See [how IDs resolve](routing#how-ids-resolve) for the exact rules and for what stays strict. ## What each model card shows - **Display name** — the human-readable name, sometimes versioned. - **Provider** and **owner** — who serves it and who created it (these differ for re-hosted models, e.g. Llama on Mistral). - **Context window** — max input tokens. - **Pricing** — prompt, completion, and (where applicable) cached prompt rates per 1M tokens, in your account currency. - **Capabilities** — `tool_use`, `vision`, `structured_output`, `streaming`. Filter the catalogue by these. - **Eco data** — active parameter count and the energy estimate per 1K tokens. Both numbers come from the [methodology](../sustainable-ai/methodology). The confidence band (`accurate`, `medium`, `gross`) reflects how well-sourced the parameter count is. - **Regions** — where the upstream serves it (`eu-west`, `us-east`, …). ## Auto-routed IDs An `auto//` ID names the model you want and leaves the provider and region to the router — same weights, same output, only the route chosen for you. These appear in this listing alongside the explicit IDs for every model served by more than one route. See [routing](routing). When an auto-routed ID is used, the response's `model` field is the *resolved* four-segment ID. ## Lifecycle - **Added.** When an upstream releases a new model and we integrate it, it appears on the model browser. Brand-new models start with a `medium` or `gross` eco confidence band until the parameter count is verified. - **Deprecated.** When an upstream announces deprecation, the model card flags it with a `deprecated` badge and a sunset date. Routing still uses it until the sunset date. - **Removed.** After the sunset date, requests for the model return `model_deprecated`. A migration suggestion is included in the error body when we have one. ## Filtering the catalogue The model browser supports filtering by: - Provider - Context window - Capability flags - Eco confidence band - Price range The same filters are reflected in `GET /models` query parameters. --- # Routing # Routing Every request goes through the router. For an explicit model the router's job is small — resolve it to a healthy upstream that serves it. For an auto-routed model it also chooses which provider and region serve it. This page describes both. ## Three modes - **Explicit model.** You send a full model ID (see [available models](available)); the router resolves it to the provider and region encoded in that ID and sends it upstream. - **Auto-routed model (`auto//`).** You name the model; the router picks which provider serves it and from where. See [auto-routing a model](#auto-routing-a-model) below. - **Alias (`alias/`).** You send the name of one of your account's [model aliases](aliases); the router resolves it to the stored route and continues exactly like an explicit model. The `alias/` prefix (like `auto/`) is a reserved namespace — no provider can ever be named that. The `model` field is required. There is no per-request `route` object and no separate routing parameters — everything the router needs comes from the model ID you send and the model catalogue. To constrain provider or region, encode it in the model ID (below). **`lowrouter/auto` has been removed.** It used to let the router pick the *model* as well as the route, ranked on carbon per token — which meant the greenest answer was always the smallest model in the catalogue, a capability downgrade you had not asked for and could not see. Choosing the model is your decision; choosing the route is ours. Requests naming it now return `400` and point here. ## Auto-routing a model The same model is often served by several providers, in several regions, at different prices and on very different electricity grids. `auto//` lets you name the model and leave that choice to the router: ``` auto/mistralai/mistral-large-2512 ``` You get the same canonical model — same weights, same output. Only the route is decided for you. Auto-routed IDs appear in `GET /v1/models` alongside the explicit ones (see [available models](available)) for every model served by more than one route, so they are selectable in any OpenAI-compatible client. They are omitted when you filter that listing with `?jurisdiction=` — auto ranking is global and cannot promise to stay inside a facet, so a filtered catalogue lists only explicit IDs, which name their provider and region outright. ### The priority order The order is **fixed** and identical for every account — there is no policy to select. Each step only breaks ties left by the previous one: 1. **EU-sovereign routes first.** A route counts as sovereign only when *both* halves hold: the provider is an EU-sovereign company **and** the serving region is in the EU/EEA. An EU-sovereign company serving from a US datacenter does not qualify, and neither does a US-controlled provider serving from Frankfurt. 2. **Lowest grid carbon intensity.** A region whose grid intensity we have no data for ranks below every region we do — an unknown is treated as unknown, never as the greenest. 3. **Lowest price.** Because carbon is decided first, two identically-priced routes can never resolve to the dirtier grid. A route with no published price ranks below every priced one. 4. **Round-robin** across whatever is still tied. Routes a provider has taken out of service are excluded outright, at every step. ### Sovereignty here is a preference, not a guarantee If a model has no sovereign route at all, an auto request does **not** fail — it falls through to the greenest, then cheapest route available anywhere. `auto/anthropic/claude-sonnet-5` routes, to a non-EU provider. The response is what keeps this honest. `lowrouter_metadata.eu_sovereign` tells you whether the route you got was sovereign, alongside `provider` and `region`, and the `model` field carries the fully resolved four-segment ID. If you need sovereignty as a hard constraint rather than a preference, use an explicit ID — it fails loudly instead of falling back outside your constraint. `lowrouter_metadata.routing_reason` names the step that decided: `eu_sovereign_preferred`, `lowest_carbon_intensity`, `lowest_cost`, `round_robin`, or `only_route` when the model has just one route. ### What auto-routing does not do - It never changes **which model** you get — that is your choice, and the whole point of naming it. - It does not score latency or answer quality. - It does not consult your [aliases](aliases): `auto/` is its own namespace and always applies the order above. - A version shorthand still works — `auto/mistralai/mistral-large` resolves to the latest version — but a region does not: there is no 4th segment to write, because choosing the region is the point. ## Pinning a provider or region Region and provider are part of the **model ID**, not a separate field. The public ID has the form `{provider}/{creator}/{model}[/{locode}]`: | Goal | How | |------|-----| | Pin the provider | Use an explicit model ID — the first segment is the provider, e.g. `vertex/anthropic/claude-opus-4.6`. | | Pin the region | Append a UN/LOCODE as the 4th segment, e.g. `vertex/anthropic/claude-opus-4.6/sg-sin`. | | Default region | Omit the 4th segment; see [how IDs resolve](#how-ids-resolve) below. | If you request a region a model isn't served in, the request is rejected rather than silently served elsewhere — the region you pin is the region you get. ## How IDs resolve You do not have to write the full four-segment ID. What you leave out is filled in from the catalogue, most-specific first, and the response always tells you what you actually got. | You send | What happens | |---|---| | `mistral/mistralai/mistral-large-2512/eu` | Used exactly as written. | | `mistral/mistralai/mistral-large-2512` | **Region defaulted** — resolves to `/eu`. | | `mistral/mistralai/mistral-large` | **Version and region defaulted** — resolves to `mistral-large-2512/eu`. | | `anthropic/anthropic/claude-sonnet` | Resolves to the latest sonnet at `/global`. | **Missing version** resolves to the most recent version of that same model. The version is the only thing that moves: `claude-sonnet` resolves to the newest *sonnet*, never to an opus, and `mistral-large` never to a `mistral-small`. A shorthand that would be ambiguous between genuinely different models — `openai/openai/gpt-oss`, where `gpt-oss-20b` and `gpt-oss-120b` are separate models rather than two versions of one — is **rejected**, not guessed. **Missing region** resolves to `global` when the model has a global endpoint. When it doesn't, it resolves to the model's only region, or, where several exist, to the **lowest-carbon** one. A region whose grid intensity we don't have data for is never chosen over one we do — an unknown is treated as unknown, not as zero. The response's `model` field always carries the **fully resolved** four-segment ID, so a request is reproducible from its own response — copy it back and you pin exactly what ran. `lowrouter_metadata.region` reports the region independently. ### Pinning still fails loudly Resolution only ever fills in what you left out. It never overrides what you wrote: - A version that doesn't exist is an error, not a nudge to the nearest one. `mistral/mistralai/mistral-large-9999` → `404`. - A region the model isn't served in is an error, not a reroute. `mistral/mistralai/mistral-large-2512/us-iad` → `503`. - An explicit `/global` on a model with no global endpoint is an error, not a silent switch to a regional row. That asymmetry is deliberate. Defaults exist so the IDs in our own docs and model list are callable as printed; pins exist so that when you name a version or a jurisdiction, the thing you named is the thing that ran. Aliases are stricter still: an [alias](aliases) target must be a full, concrete `{provider}/{creator}/{model}/{locode}` triple, so a stored route can never drift under you. ## Worked scenario: EU-only first, lowest carbon second Say a data-residency commitment requires EU inference, and within that constraint you want the lowest carbon available. The order matters: sovereignty is a **constraint**, carbon is an **optimisation**, and the two are handled by different mechanisms. 1. **Encode the constraint.** Filter the [providers page](https://lowrouter.ai/providers) to EU providers, then compare per-token carbon across the models that remain in the [catalogue](https://lowrouter.ai/models). 2. **Pin the winner** with an explicit ID, e.g. `mistral/mistralai/mistral-large-2512/eu` — or better, point an [alias](aliases) at it, so you can re-run the comparison next quarter and repoint without touching client code. 3. **Verify, don't trust.** Check `lowrouter_metadata.region` on the responses; the pin is proven on every call. Note what you did *not* do: ask the router for "EU only". Auto-routing does not treat a jurisdiction as a hard constraint. `auto//` does *prefer* EU-sovereign routes above everything else, and reports whether it got one — but when a model has no sovereign route it falls back rather than failing, so it is an optimisation, not a commitment. Per-key jurisdiction restrictions are coming; until then the honest tool for a hard constraint is the explicit ID, which fails loudly rather than falling back outside it. ## What happens on failure LowRouter does not silently move your request to a different provider: an explicit ID is a pin, and an auto-routed request commits to the winner of the ranking. If that route fails, the request returns `503` naming the provider that failed rather than reporting a different one you never asked for. Substituting a provider would bill against another pricing row and serve from another jurisdiction than the ID promised. Which providers were attempted is reported on the response in `lowrouter_metadata.providers_attempted`, alongside `lowrouter_metadata.fallback_occurred`. ## What the router does not do - It does not choose the model — that is what naming it is for. - It does not benchmark output quality — it optimises for sovereignty, then carbon, then cost, not "is the answer good". - It does not accept a per-request `route` object or a `prefer_low_carbon` flag. Low carbon is already ranked ahead of price, so there is nothing to opt into. --- # Per-request metadata # 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": "eu-west", "energy_wh": 0.0021, "carbon_gco2e": 0.00057, "carbon_intensity_gco2_per_kwh": 270, "estimation_methodology": "ecologits-calculated", "routing_mode": "auto", "routing_reason": "lowest_carbon_intensity", "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. - **Estimated** — modeled numbers: `energy_wh`, `carbon_gco2e`, `carbon_intensity_gco2_per_kwh`. Produced per the [methodology](../sustainable-ai/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. Strings like `eu-west`, `us-east`, `us-west`. The region is the one encoded in the resolved model ID's locode. ### `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](../sustainable-ai/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` Identifies how the estimate was produced (e.g. an EcoLogits-based calculation). Present when an estimate was made. ### `routing_mode` `auto` when the router chose the route for you (an `auto//` 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`. See [routing](routing). ### `fallback_occurred` `true` if the route changed after selection. LowRouter does not silently substitute providers — an explicit ID is a pin and an auto-routed request commits to the winner of its ranking — so this reads `false` on current traffic. It stays on the response, and in your usage export, because historical rows can carry `true`. ### `providers_attempted` The list of providers tried for this request, in order. ### `requested_alias` Present only when the request addressed a [model alias](aliases) (`"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. ## Worked scenario: "why did this request go to us-east?" Your dashboard shows one row served from `us-east` in a week of otherwise-EU traffic. The metadata of that response answers it without guesswork: ```json "lowrouter_metadata": { "provider": "openai", "region": "us-east", "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](aliases): an explicit route fails loudly instead of falling back outside your constraint. See [routing](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. Earlier chunks don't include it. See [first completion → streaming](../getting-started/first-completion). ## 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 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; we do. --- # Model aliases # Model aliases An alias is a named pointer to a concrete route — a (provider, model, region) triple from the [model catalogue](available). You create alias `big` pointing at `aws-bedrock/mistralai/ministral-3-3b-instruct/br-gru`, then call the API with: ```json { "model": "alias/big", "messages": [{ "role": "user", "content": "Hello" }] } ``` The gateway resolves `big` to its stored route at request time and routes exactly as if you had sent the full model ID. When you change your mind about which model "big" is, repoint the alias in the dashboard — every client configured with `alias/big` picks up the new route on its very next request, with zero reconfiguration. That is the point: a coding agent or chat client gets configured once. Aliases are account-wide. Every API key on your account resolves the same aliases. ## Creating and managing aliases Three surfaces: - **Model catalogue.** Every provider/region row on a model page has an **Alias** button — point an existing alias at that exact route or create a new one from it. - **Dashboard → Aliases.** List, create, rename, repoint, and delete, with a copyable `alias/` for each. Each alias has a detail page with its usage stats and full change history. - **The API.** Manage aliases programmatically with the same API key you call inference with (below). ## Managing aliases via the API All endpoints take your regular `Authorization: Bearer ` and address aliases by name: | Method | Path | Does | |---|---|---| | GET | `/v1/aliases` | List aliases + quota | | POST | `/v1/aliases` | Create — body `{name, provider, canonical_id, locode}` | | GET | `/v1/aliases/{name}` | Get one alias | | PATCH | `/v1/aliases/{name}` | Rename (`name`) and/or repoint (full target triple) | | DELETE | `/v1/aliases/{name}` | Hard delete | | GET | `/v1/aliases/{name}/stats?days=30` | Usage totals + per model/provider/region breakdown | | GET | `/v1/aliases/{name}/history` | Change history, newest first | Create an alias and immediately use it: ```bash curl -X POST https://api.lowrouter.ai/v1/aliases \ -H "Authorization: Bearer $LOWROUTER_API_KEY" \ -H "Content-Type: application/json" \ -d '{"name": "big", "provider": "aws-bedrock", "canonical_id": "mistralai/ministral-3-3b-instruct", "locode": "br-gru"}' curl -X POST https://api.lowrouter.ai/v1/chat/completions \ -H "Authorization: Bearer $LOWROUTER_API_KEY" \ -H "Content-Type: application/json" \ -d '{"model": "alias/big", "messages": [{"role": "user", "content": "Hello"}]}' ``` Repointing takes the **full target triple** — send all of `provider`, `canonical_id`, and `locode`, not just the field you are changing. ### Target shapes `provider`, `canonical_id` and `locode` are **flat top-level fields**, as above. Two other shapes are also accepted, so a target copied straight out of a response works without reshaping: ```bash # nested object -d '{"name": "big", "target": {"provider": "aws-bedrock", "canonical_id": "mistralai/ministral-3-3b-instruct", "locode": "br-gru"}}' # slash-joined string, the same form the `model` field uses -d '{"name": "big", "target": "aws-bedrock/mistralai/ministral-3-3b-instruct/br-gru"}' ``` `locode` is optional in every shape and defaults to `global`. Where a flat field and a `target` value disagree, the flat field wins. ## Stats and history Every request through an alias is attributed to it: the stats endpoint (and the alias detail page in the dashboard) breaks usage down by the model, provider, and region the alias resolved to at the time — request counts, prompt/completion/total tokens, plus cost and carbon totals. Each breakdown row's `model` is the **canonical id** — the same string you set as the target, sent in the request, and see in `/v1/models` — so these rows join directly against your own logs. The human-readable name, when the catalogue has one, is a separate `display_name` field. Every create, rename, and repoint is recorded with its old and new values. Two things to know: - Stats follow the **alias**, not its name: renaming keeps history and stats; deleting and recreating a name starts fresh. - Attribution starts when this feature shipped — earlier traffic is not retroactively assigned. Alias names are 1–64 characters: lowercase letters, digits, `-` and `_`, starting with a letter or digit. Names are lowercased when created and when looked up, so `alias/Big` and `alias/big` are the same alias. Each account can hold **5 aliases** by default. The limit is visible on the aliases page; contact support if you need more. ## Resolution semantics - **Billed like the target.** An aliased request is billed at the target route's per-region pricing — exactly what an explicit request for that model ID would cost. The alias adds no fee. - **The response echoes the resolved model.** `model` in the response carries the resolved full model ID, not the alias — so logs and downstream tooling always see what actually served the request. The alias you sent is echoed in [`lowrouter_metadata.requested_alias`](per-request-metadata). - **Fail-closed.** If an alias does not exist (or was deleted), the request fails with `404 alias_not_found`. If an alias's target can no longer be routed — the model left the catalogue or its region is unavailable — the request fails with the same error the explicit model ID would produce. The gateway never silently substitutes a different model, provider, or region. - **Deletes are immediate.** Deleting an alias frees its name for reuse right away, and requests still using it get `404 alias_not_found`. If live clients depend on an alias, repoint it instead of deleting it. ## Errors | Case | Status | `error.code` | |---|---|---| | Malformed alias name in `model` | 400 | `invalid_alias_name` | | Alias does not exist on this account | 404 | `alias_not_found` | | Alias target no longer routable | 404 / 503 | the explicit-mode code for that failure | | Create: name already taken | 409 | `alias_exists` | | Create: account alias limit reached | 422 | `alias_limit_reached` | | Create/repoint: target invalid or unroutable | 422 | `invalid_alias_target` | ## Worked scenario: repointing through a provider outage Your coding agents all call `alias/big`, which points at `mistral/mistralai/mistral-large-2512/eu`. The provider has a bad afternoon and calls start failing with 503s. Instead of redeploying every client: 1. Pick a healthy substitute in the [catalogue](available) — say the same model served by another provider. 2. Repoint the alias (the full triple, as always): ```bash curl -X PATCH https://api.lowrouter.ai/v1/aliases/big \ -H "Authorization: Bearer $LOWROUTER_API_KEY" \ -H "Content-Type: application/json" \ -d '{"provider": "scaleway", "canonical_id": "mistralai/mistral-medium-3.5-128b", "locode": "fr-par"}' ``` 3. The very next `alias/big` request routes to the new target. When the outage ends, repoint back the same way. Billing follows the target from the next request on. And you traded reproducibility for flexibility: responses before and after the repoint came from different routes. That's why repointing is a named, recorded action — `GET /v1/aliases/big/history` shows exactly when it happened, and every response's `model` and `lowrouter_metadata` say who actually served it, so the audit trail survives the flexibility. ## What aliases are not - They are not a fallback or load-balancing mechanism — one alias points at exactly one route. - They do not pin pricing. Repointing an alias changes what you are billed to the new target's rate from the next request on. - They are per-account, not per-key. There is no way to give two API keys different meanings for the same alias name. --- # Sustainable AI # Sustainable AI Four pages that document the energy and carbon numbers shown elsewhere on the platform: - [Methodology](methodology) — the formula, the coefficients, and the confidence bands. - [Data sources](data-sources) — where the parameter counts and grid intensities come from. - [Limits and what we don't claim](limits) — the explicit out-of-scope list. - [Reduce your footprint](reduce-your-footprint) — concrete things to change in your application that move the dashboard's numbers. These pages are the longest in the docs site on purpose. The numbers are only useful with the caveats; the caveats need to be readable. --- # Methodology # Methodology *Last reviewed: 2026-07-22. Changes are listed in the [changelog](#changelog) at the bottom of this page.* LowRouter estimates the carbon footprint of every inference request using the formula and data sources described on this page. This is the reference document; the numbers on the dashboard, the model browser, and the API responses all come from it. ## What we report Two numbers per request: - **Energy** in watt-hours (Wh). - **Carbon** in grams of CO₂ equivalent (gCO₂e). The carbon number is also normalised to **gCO₂e per 1,000 tokens** so requests of different sizes are comparable. ## The formula ``` energy_wh = ((α × P_active) + β) × tokens carbon_g = energy_wh × PUE × grid_intensity_g_per_kwh / 1000 ``` Where: - **`P_active`** — number of active parameters during inference, in billions. For dense models this is the parameter count; for Mixture-of-Experts (MoE) models it's the parameters activated per token, not the total count. - **`α`** = 8.91 × 10⁻⁵ Wh per output-token-billion-param. - **`β`** = 1.43 × 10⁻³ Wh constant overhead per output token. - **`tokens`** — total tokens for the request (`prompt_tokens + completion_tokens`). - **`PUE`** = 1.20 — data-center Power Usage Effectiveness, the overhead for cooling and power distribution on top of the compute energy itself. Applied to every request. - **`grid_intensity_g_per_kwh`** — annual-average carbon intensity of the electricity grid in the region serving the request. The `/ 1000` in the carbon line is the Wh → kWh conversion: energy is carried in Wh (EcoLogits' native unit) while grid intensity is published per kWh. The energy formula is the [EcoLogits v0.4 inference model](https://ecologits.ai/0.4/methodology/llm_inference/). The grid-intensity values are Ember Climate annual country averages, replaced by regional grid-operator data where a supported operator publishes it — see [data sources](data-sources). ## Why this formula The EcoLogits model is published, peer-reviewed in spirit if not fully formally, and reproducible from public model parameter counts. It is not the only credible estimate but it is the one with the clearest derivation and the most active maintenance. Adopting it lets us compare numbers across providers using the same yardstick rather than reconciling each provider's bespoke estimate. ## Confidence bands Every estimate carries one of three labels: | Band | When | |------|------| | `accurate` | Model size verified by the provider or in the EcoLogits registry; recent grid data. | | `medium` | Model size from a credible third party (research paper, well-supported leak); grid data current. | | `gross` | Model size estimated from the model name or industry rumour; or grid data older than 12 months. | These bands are about *uncertainty in the inputs*, not about whether the formula itself is right. The formula has its own model-class limits documented on the [limits page](limits). The bands are **ordinal, not quantified**: `accurate` rests on better-grounded inputs than `medium`, which is better-grounded than `gross`. We deliberately do **not** publish a percentage error for each band. We have not run a validation study against measured energy consumption, and we are not willing to attach a number to a carbon figure without one — an invented error bar reads as a measured one. The dominant source of error is the model's active-parameter count, which enters the formula multiplicatively and asymmetrically, so a symmetric ± would misrepresent it even if we had measured it. When the band is `gross`, the dashboard widgets that aggregate carbon across many requests show a reduced-confidence indicator and link back to this page. ## Methodology versioning Every estimate stores the `methodology_version` that produced it (see [per-request metadata](../models/per-request-metadata)). The version captures: - The values of α and β. - The grid-intensity dataset version (Ember Climate country averages plus regional grid-operator data — see [data sources](data-sources)). - The model parameter-count dataset version. When any of these change, the version is bumped and the change is noted in the dashboard's footer with the date. Old generations are *not* retroactively recomputed — their `methodology_version` is the one in effect when the request was served. ## Worked example A request: - Resolved model: `openai/openai/gpt-4o-mini`. - Active parameters: 8B (this is the value we use; the provider has not officially confirmed it, so the band is `medium`). - Total tokens: 200. - Provider region: `eu-west`. - Grid intensity: 270 gCO₂e/kWh (Ember 2024 EU average). The EcoLogits v0.4 coefficients are in **watt-hours per output token**, and that is the unit energy is stored and reported in — no conversion sits between the published formula and `energy_wh`: ``` energy_wh_per_token = (α × P_active) + β = (8.91e-5 × 8) + 1.43e-3 = 0.002143 Wh/token energy_wh = 0.002143 × 200 = 0.43 Wh carbon_g = 0.43 × 1.20 × 270 / 1000 = 0.139 g carbon_per_1k_tokens = 0.139 × (1000 / 200) = 0.70 g ``` So a 200-token completion on `gpt-4o-mini` from `eu-west` is estimated at **0.43 Wh** and **~0.14 gCO₂e**, with `medium` confidence. These are the numbers your `eco` block would carry. If you find a discrepancy between this worked example and what the gateway returns, the gateway is the source of truth — please file an issue so we can fix the documentation. ## Everyday equivalences The dashboard and model pages translate a measured gCO₂e figure into everyday terms — kilometres driven, phone charges, tree-days. These are **illustrative framing, not part of the estimate**. Each one divides the measured emissions by a single published factor: | Equivalent | Factor | Source | |------------|--------|--------| | Phone charge | **12.4 gCO₂e** per full smartphone charge | [US EPA Greenhouse Gas Equivalencies Calculator](https://www.epa.gov/energy/greenhouse-gas-equivalencies-calculator-calculations-and-references) | | Car travel | **106.7 gCO₂e** per kilometre | [European Environment Agency](https://www.eea.europa.eu/en/analysis/indicators/co2-performance-of-new-passenger) | | Tree uptake | **60.3 gCO₂** per tree-day | [European Environment Agency](https://www.eea.europa.eu/articles/forests-health-and-climate-change) | ``` phone_charges = carbon_g / 12.4 car_km = carbon_g / 106.7 tree_days = carbon_g / 60.3 tree_years = tree_days / 365 ``` Some surfaces show **tree-years** (or `trees/yr`) instead of tree-days; those divide the tree-day count by 365 at the same per-day factor, so both readings trace back to the one published number. ### What each factor is, precisely - **Phone charge — 12.4 gCO₂e.** The EPA calculator's figure (1.24 × 10⁻⁵ metric tons per charge). Its derivation: a charge draws `28.446 Wh − (22 h × 0.411 W) = 19.404 Wh`, which at the 2022 US national weighted average rate for delivered electricity (1,405.3 lb CO₂/MWh, including transmission and distribution losses) gives 12.4 gCO₂e. The EPA page displays the energy rounded to 0.019 kWh but carries the unrounded value through; using the rounded figure yields 12.1 g, so we quote the unrounded one to keep the arithmetic reproducible. This is a **US grid** number; the same charge on a lower-carbon grid emits less. We use it because it is the most clearly documented per-charge figure available, not because US grid intensity is representative of our routing. - **Car travel — 106.7 gCO₂e/km.** The EEA's average for passenger cars **newly registered in the EU in 2024**. Two caveats a citing reader should know: it covers new registrations rather than the whole on-road fleet (older cars in service emit more), and type-approval values are known to understate real-world emissions — the ICCT has documented a widening gap, most sharply for plug-in hybrids. Treat this as a conservative floor, not a fleet-wide figure. - **Tree uptake — 60.3 gCO₂/day.** Derived from the EEA's statement that a mature tree takes up roughly 22 kg of CO₂ per year (22,000 ÷ 365). This is the weakest-sourced of the three: it is a widely-repeated rule of thumb rather than a result traceable to a specific field study, and real uptake varies substantially by species, age, and site. We keep it because it is the figure the public conversation uses, and we flag it rather than dressing it up. The factors live in one place in the codebase (`frontend/src/utils/carbonEquivalents.ts`) and this page is checked against that module by a test, so the numbers here and the numbers on the dashboard cannot drift apart. ## The full picture Read the [data sources](data-sources) page next for where each number in the formula comes from. The [limits](limits) page lists what we explicitly do not claim. ## Changelog - **2026-08-19** — Everyday equivalence factors published with their sources for the first time (#438). Two of the three were corrected in the process: the phone-charge factor moved from 8.22 g to the EPA's documented 12.4 g, and the car factor from 171 g/km to the EEA's 106.7 g/km for 2024 new registrations — the previous values did not match the bodies they were attributed to. The tree factor moved from 57.5 to 60.3 gCO₂/day to match the EEA's 22 kg/year figure exactly. Displayed equivalences changed accordingly; the underlying emissions estimates did not. The dashboard's carbon unit toggle carried a separate, third set of factors (121 gCO₂/km, 21 kg/tree/year); it now reads the same published values, so every surface answers a given measurement identically. - **2026-07-22** — Grid-intensity attribution corrected: country averages come from Ember Climate, refined by regional grid-operator data; the IEA figure is used for the global aggregate only. Worked example recomputed against the Ember 2024 EU average (270 gCO₂e/kWh, previously ~340). - **2026-07-06** — API parameters, endpoints, and metadata field names corrected to match the backend (#279). - **2026-04-29** — First published (#117). --- # Data sources # Data sources *Last reviewed: 2026-07-22. Changes are listed in the [changelog](#changelog) at the bottom of this page.* The carbon estimate is only as good as its inputs. This page lists each input, where it comes from, and how often we update it. ## Energy formula coefficients (α, β) - **Source**: [EcoLogits v0.4 — LLM inference methodology](https://ecologits.ai/0.4/methodology/llm_inference/). - **Values**: α = 8.91 × 10⁻⁵, β = 1.43 × 10⁻³ (Wh per output token). - **Updates**: when EcoLogits publishes a new methodology version with new coefficients, we evaluate it, bump the `methodology_version`, and note the change in the dashboard's footer with the effective date. The coefficients were derived from a regression across published benchmarks on a fleet of representative GPUs. They are an *average*; real hardware varies. ## Model active parameters - **Source priority**: 1. **EcoLogits registry** — models with verified architecture details (`accurate`). 2. **Provider documentation** — values published by the model creator (`accurate` or `medium`, depending on whether the statement is unambiguous). 3. **Research papers and credible leaks** — peer-reviewed architecture descriptions, technical reports (`medium`). 4. **Name-based estimates** — `llama-70b` → 70B (`gross`). - **Updates**: when a new model lands, we look up its parameter count in this priority order and tag the `accuracy` band accordingly. Re-evaluation happens monthly and on demand when a model's source upgrades. For Mixture-of-Experts models we use the **active parameter count** (parameters used per token), not the total parameter count. This distinction matters: a 600B-parameter MoE that activates 20B per token has the energy profile of a 20B dense model, not a 600B one. ## Grid carbon intensity - **Source**: [Ember Climate yearly electricity data](https://ember-climate.org/data/data-tools/electricity-data-explorer/) (CC-BY 4.0) — annual averages by country. Where a regional grid operator publishes finer-grained, permissively-licensed data, we use the regional value instead: RTE (France), PJM (US Mid-Atlantic), CAISO (US California), ERCOT (US Texas), BPA (US Pacific Northwest), EirGrid (Ireland), National Grid ESO (UK), Hydro-Québec, AEMO (Australia), among others. The [IEA figure](https://www.iea.org/data-and-statistics) is used for the `global` aggregate only. - **Per-region provenance**: every region's entry records its source and source URL in our config, so each number is traceable to the operator or dataset that published it. - **Aggregation**: where a region maps to multiple countries (e.g. `eu-west` covers FR, DE, NL, IE), we use a population-weighted average for the region. - **Updates**: annually, as each upstream publishes its new dataset. Switching dataset versions bumps the `methodology_version`. We do not use real-time grid carbon intensity (which would require per-request lookups against a service like ElectricityMap). It's on the roadmap; the trade-off is that real-time numbers introduce sampling noise we'd need to explain. Annual averages are coarse but boring, and "boring" is a feature in a methodology document. ### Sample values | Region | Approx. gCO₂e/kWh | Notes | |--------|-------------------|-------| | `eu-west` | ~280–340 | Population-weighted Western Europe average. | | `eu-north` | ~50–80 | Mostly hydro/nuclear (Sweden, Norway, Finland). | | `us-west` | ~250–320 | California heavy renewables, broader West mixed. | | `us-east` | ~370–450 | Higher fossil share. | | `india` | ~700–800 | Coal-dominant grid. | Specific values per region are in the dashboard's settings page; the table above is for orientation. ## Pricing data - **Source**: each upstream provider's published price list, refreshed daily. - **Updates**: within one business day of an upstream change going live. - **Storage**: the price applied at the moment of a request is stored on the generation record, so historical bills are stable. Pricing isn't strictly part of the carbon methodology, but it is part of the per-request routing decision (cost is the tie-break after carbon in auto mode) so the source is documented here for completeness. ## Everyday equivalence factors The "km driven / phone charges / tree-days" framing shown next to a carbon figure uses three published conversion factors: | Equivalent | Factor | Source | Vintage | |------------|--------|--------|---------| | Phone charge | 12.4 gCO₂e/charge | [US EPA Greenhouse Gas Equivalencies Calculator](https://www.epa.gov/energy/greenhouse-gas-equivalencies-calculator-calculations-and-references) | 2022 US grid | | Car travel | 106.7 gCO₂e/km | [European Environment Agency](https://www.eea.europa.eu/en/analysis/indicators/co2-performance-of-new-passenger) | 2024 EU new registrations | | Tree uptake | 60.3 gCO₂/tree-day | [European Environment Agency](https://www.eea.europa.eu/articles/forests-health-and-climate-change) | ~22 kg/tree/year | These convert a measured number into everyday terms; they are not inputs to the emissions estimate. The scope and known weaknesses of each factor — including that the car figure covers new registrations rather than the on-road fleet, and that the tree figure is a rule of thumb without a single underlying study — are set out under [everyday equivalences](methodology#everyday-equivalences) on the methodology page. ## What we deliberately don't include - **Hardware embodied carbon.** Manufacturing emissions for the GPUs serving inference are non-zero but we don't have a defensible per-token allocation. Until we do, omitting the number is more honest than guessing. - **Cooling overhead.** Data-centre cooling adds 10–30% to the energy used by compute (Power Usage Effectiveness, PUE). The EcoLogits formula incorporates an average overhead; provider-specific PUE refinements are pending more data. - **Network transport.** Energy used to move bytes between the gateway, the upstream, and the user is small relative to inference and is not counted. - **Training emissions.** Documented separately on the [limits page](limits). ## Changelog - **2026-08-19** — Everyday equivalence factors documented with their sources and vintages (#438); phone-charge and car factors corrected to match the bodies they cite. - **2026-07-22** — Grid-intensity source corrected: country averages come from Ember Climate (CC-BY 4.0), refined by regional grid-operator data; the IEA figure is used for the `global` aggregate only. Added the per-region provenance note. - **2026-07-06** — API parameters, endpoints, and metadata field names corrected to match the backend (#279). - **2026-04-29** — First published (#117). --- # Limits and what we don't claim # Limits and what we don't claim A defensible number needs a clear scope. This page is the scope. ## What the numbers cover - **Inference compute energy** for the model that served the request, using the EcoLogits v0.4 formula and the active-parameter count. - **Grid carbon intensity** of the region the upstream served from, using annual averages from Ember Climate and regional grid operators (see [data sources](data-sources)). - **One step of the response.** A single request, end-to-end. That's it. ## What the numbers do not cover ### Training emissions We report inference only. Training a frontier model has a much larger and harder-to-attribute footprint, and folding a "share of training" into per-request numbers depends on assumptions (how many requests will the model serve in its lifetime?) that are unverifiable. We'd rather under-report inclusively than make up a number. ### Hardware manufacturing GPUs have an embodied carbon footprint from manufacturing. There is not yet a defensible way to allocate it per token. Some methodologies amortise it across the GPU's expected lifetime; the amortisation depends on assumptions we don't have. ### Real-time grid mix We use annual averages by region. Live carbon-aware routing — picking the region whose grid is currently cleanest — is a roadmap feature, not a current one. ### Embedding workloads The EcoLogits formula was derived for decoder-only autoregressive models. Encoder-only embedding models have a meaningfully different compute profile. We currently *do not* report eco numbers for embedding requests; the response carries no `eco` block. Modelling embeddings properly is on the roadmap. ### Tool-call orchestration When a single user-facing operation requires multiple LLM calls (e.g. an agent that thinks-then-acts-then-thinks), each call gets its own number. The aggregate footprint of a multi-step operation is the sum of those numbers. We do not do that aggregation automatically; that is your application's job. ### Browser, mobile, and on-device inference Inference that doesn't go through the gateway doesn't appear in the dashboard. The numbers describe what we measure, not the totality of your AI footprint. ## What the numbers are *estimates of* - An estimate, not a measurement. We do not have a wattmeter on the upstream provider's GPU. - A model-class estimate, not a per-request measurement. Two requests for the same model with the same token count get the same number. - An average over hardware, not a number specific to the GPU generation that served your request. Newer hardware is generally more efficient; the formula does not yet reflect generation. ## What we explicitly will not say - "X grams CO₂e saved by using LowRouter." - We don't know what your counterfactual is. The eco-impact widget on the dashboard offers a comparison against a *baseline you choose*. That comparison is what it claims to be — a comparison against your chosen baseline. - "Carbon-neutral", "net-zero", or "sustainable" applied to any individual request. - The numbers we publish exist in service of *better-informed decisions*, not certifications. Certifications require an audit we are not the right party to perform. - "Independently verified" beyond what is true. - The EcoLogits methodology is published; the grid-intensity datasets (Ember Climate, the regional grid operators) are published. Our application of them is auditable from the source code. There is no third-party certification of the per-request numbers themselves. ## How to read the numbers - For ranking: comparing requests within a single `methodology_version` and `accuracy` band is meaningful. - For absolute claims: take the band into account. A `gross` number rests on an unverified parameter count, so treat it as an order-of-magnitude indication only; quoting it to three significant figures is a category error. We do not publish a quantified error bound for any band — see [methodology](methodology#confidence-bands) for why. - For reporting: the numbers are appropriate for an internal dashboard or a "best-effort estimate" line in a sustainability report. They are not appropriate as the basis for a public emissions disclosure without acknowledging the methodology and its uncertainty. ## Reproducibility Every estimate can be reproduced from public inputs: 1. Get `resolved_model`, `region`, `total_tokens`, and `methodology_version` from the generation record. 2. Look up the model's active parameter count in the [EcoLogits registry](https://ecologits.ai) for that version. 3. Apply the formula on the [methodology page](methodology) with the coefficients of that version. 4. Look up the grid intensity for that region in the grid-intensity dataset referenced by the version. 5. The result should match the stored `eco.carbon_g` to within floating-point rounding. If your reproduction diverges, that's a bug — please [file an issue](https://github.com/carbonifer/lowrouter/issues). --- # Reduce your footprint # Reduce your footprint The methodology gives you a number. This page is what to actually do about it. Each section is a lever, with the order of magnitude of its effect, and the trade-off it carries. ## Pick a smaller model when you can The largest single lever. Energy scales roughly linearly with active parameters (see the [methodology](methodology) formula). A 7B-active model is ~10× lower energy per token than a 70B model. When to use a smaller model: - Classification, extraction, and structured-output tasks. - High-volume background work (summarisation, tagging). - Anything where the output is verified by a downstream system. When **not** to: - Tasks that the smaller model fails at and your application has to retry on a larger one anyway. Two failed cheap calls + one big call > one big call. Pick a smaller explicit model when it can plausibly handle the request — a smaller model burns less energy per token. Try it on your traffic; if quality holds, keep it. ## Cache prompts where the upstream supports it Several providers offer prompt caching: a long system prompt sent repeatedly with different user messages is charged at a discount on the cached portion. Where supported, this cuts both cost and energy on the cached part. Practical: - Place stable instructions, examples, and reference material **first** in the messages array. - Place the variable part (the user's question) **last**. - Keep the stable prefix above the upstream's caching threshold (for example, ≥1024 tokens). The dashboard's per-transaction view shows `cached_tokens` when an upstream applied a cache hit. ## Trim prompts Energy scales linearly with `total_tokens`. A 50% prompt-length reduction is a 50% energy reduction for the prompt portion. - Drop preamble that doesn't change the model's behaviour. - Drop few-shot examples that the model no longer needs. - Compress reference material (use IDs instead of full descriptions when the model has been trained on them). This compounds with prompt caching: a shorter cached prefix is cheaper *and* faster to cache. ## Choose the cleaner region when residency permits The grid intensity in a hydro/nuclear-heavy region is far lower than in a coal-heavy one. To pick a clean region explicitly, encode its UN/LOCODE as the last segment of the model ID: ```json { "model": "vertex/anthropic/claude-opus-4.6/eu-north", "messages": [...] } ``` Or name the model and let the router choose the route for it with `auto/mistralai/mistral-large-2512`: among the eligible routes it prefers EU-sovereign ones, then the **lowest grid carbon intensity**, so the cleanest available route for that model is picked for you — no flag required. You keep the choice of model; only where it runs is decided for you. ## Bound completion length `max_tokens` lets you stop generation when "enough is enough". For classification or extraction, set it to the actual answer length plus a small margin. The carbon savings are linear with the saved tokens. Some prompts respond well to "Answer in one sentence." instructions; others ignore them. Both are worth trying — the first time you check the dashboard, you'll see if the average completion length actually came down. ## Rate-limit your retries A retry storm can multiply your footprint by 3–10× of the underlying call. Use exponential backoff with jitter on retries, cap the retry count, and **never** retry on a 4xx that is not a 408 timeout. ## Memoise If the same user asks the same question twice, an in-application cache returns the previous answer at zero gateway cost. This is the cheapest watt: the one not spent. A few patterns that work: - Hash the prompt (after normalisation) and cache the response by that hash. - Cache lookup tables generated by the model (taxonomies, slot schemas) and refresh them on a schedule, not per-request. - For chat, cache the last few responses in memory keyed by the full conversation; reuse when the user re-asks immediately. ## Aggregate where you can Many small completions cost more than one larger one with multiple items. Examples: - Classify a batch of 20 items in a single request rather than 20 requests. - Extract structured fields for a list of inputs in one structured- output call. Watch out for context-window limits and for the cost of re-prompting when one item in the batch fails — sometimes individual calls are cheaper net. ## Order of magnitude summary | Lever | Typical reduction | |-------|-------------------| | Smaller model | 5–10× per request | | Region pinning to clean grid | 3–8× on the carbon term | | Prompt caching | 30–80% on the cached portion | | Prompt trimming | linear with the % trimmed | | Memoisation of repeats | 100% on the cached call | | `max_tokens` bounding | linear with completion-tokens saved | | Aggregation / batching | 2–5× on overhead | These are independent — applying several stacks. The first two are where most teams start. ## Confirm with the dashboard After any of these changes, check the eco-impact widget on the dashboard for the same time window before/after. If the change you made should have reduced the per-1K-tokens carbon number and didn't, something is off — the dashboard's transaction-detail page tells you which model and provider actually served each request. --- # FAQ # Frequently Asked Questions Answers to the questions developers and operators ask most often. Each section is self-contained — link directly to the slug in your own docs if it's useful. ## What is LowRouter? LowRouter is one gateway to many LLM providers and models, compatible with the OpenAI API so it works with the SDKs and tools you already use. You top up credits once and choose which provider, model, and region handles your requests, and you can change that choice whenever you like. It's one entry point and one bill for many providers, and every response includes a `lowrouter_metadata` field exposing the provider, region, grid carbon, and cost of the call. ## Are you an LLM provider? No. LowRouter is a gateway, not a model host. Your request is forwarded to the upstream provider you chose — including EU-sovereign options like Mistral alongside providers such as OpenAI, Anthropic, and Google — streaming intact. We don't run our own models. ## Is LowRouter compatible with the OpenAI SDK? Yes. Set the `base_url` (Python) or `baseURL` (TypeScript) to `https://api.lowrouter.ai/v1` and use your LowRouter API key (it starts with `sk-lr-`). The SDK calls work unchanged. Detailed examples are on the [OpenAI SDK Python](integrations/openai-sdk-python) and [TypeScript](integrations/openai-sdk-typescript) pages. ## Can I reach Anthropic models? Yes — through the OpenAI-compatible endpoint, not the Anthropic SDK. The gateway speaks the OpenAI chat-completions protocol; there is no Anthropic-shaped (`/v1/messages`) endpoint. To use an Anthropic model, send a normal chat-completions request with an `anthropic/...` model ID, e.g. `vertex/anthropic/claude-opus-4.6`. See [available models](models/available). ## What are aliases? An alias is a named pointer you call as the model id, e.g. `alias/my-fast-model`. Repoint it to any provider, model, or region later without changing client code. Create and manage them under **Dashboard → Aliases**. ## Do you use my data (training, analytics, marketing)? No. We don't read, train on, analyse, or sell your prompts or responses. LowRouter simply routes the request to the provider you chose and returns the answer. We store only metadata about the call — the model, provider, region, token counts, latency, cost, and the eco estimate — so you get usage reports and stats. We do not store prompt or response content. We also propagate provider-level training opt-outs where the upstream supports them. The full per-request schema is in [usage accounting](guides/usage-accounting). ## Why do you list US and non-green providers at all? Because you can't choose responsibly without seeing the alternatives. LowRouter shows the region, jurisdiction, and carbon cost of *every* route — including the non-sovereign and carbon-heavy ones — so the comparison is honest and the choice is yours. Sometimes you need a specific US model; use it with eyes open, and see exactly what it emits. And by putting every provider on the same transparent scoreboard, we give them a reason to compete on it: the day a provider ships greener, more sovereign inference, it shows up here — measured, not marketed. ## How is the CO₂ estimate calculated? `energy = ((α × P_active) + β) × tokens`, then `carbon = energy × grid intensity`. Grid carbon intensity defaults to [Ember Climate](https://ember-energy.org/) annual country averages (CC-BY 4.0), refined by regional grid operators (PJM, CAISO, ERCOT, BPA, EirGrid, RTE, National Grid ESO, Hydro-Québec, AEMO) where they publish finer, permissively-licensed data. The energy coefficients come from [EcoLogits](https://ecologits.ai/) v0.4. The full formula, data sources, and confidence bands are on the [methodology page](sustainable-ai/methodology). We report ranges and assumptions; we don't claim spurious precision. ## Are the eco numbers real-time? No. The grid intensity is an annual regional average. Real-time carbon-aware routing is a roadmap feature, not a current one. See [limits](sustainable-ai/limits). ## What happens if a provider is down? The request returns 503, naming the provider that failed. LowRouter does not silently retry on a different provider: an explicit model ID is a pin, and an auto-routed request commits to the winner of its ranking — substituting a provider would bill against a different pricing row and serve from a different jurisdiction than the ID promised. `lowrouter_metadata.providers_attempted` shows what was tried. See [routing](models/routing). ## How is pricing structured? Pre-paid credits, pay-as-you-go. Token usage is billed at the upstream provider's price with **no per-call markup**. The only fee is a processing fee applied when you top up credits, covering payment processing and our margin — the exact amount is shown before you confirm. No per-seat charge, no monthly commitment. Failed requests that produced no upstream charge cost zero credits. Full details: [credits and billing](guides/credits-and-billing). ## Can I pin requests to a specific country or jurisdiction? Today you can filter the catalogue to EU-only providers and pin an alias to a specific provider, model, and region, then repoint it whenever you want without changing code. You can also encode the region in the model ID directly — append a UN/LOCODE as the last segment, e.g. `vertex/anthropic/claude-opus-4.6/sg-sin`; if a model isn't served in the region you request, the call is rejected rather than served elsewhere. Automatic per-request and account-level jurisdiction policies are coming soon. Details on the [routing](models/routing) page. ## What's the rate limit? Rate and budget limits are enforced per API key (the daily and monthly credit limits you set) alongside platform-level protection. Higher quotas are available on request via the email on the [legal page](/impressum). ## Can I use LowRouter from the browser? Not with the API key directly — that exposes the key to every page visitor. Mint short-lived tokens server-side and proxy requests through your backend. The pattern is the same as for OpenAI; both SDKs warn against `dangerouslyAllowBrowser`. See [OpenAI SDK TypeScript](integrations/openai-sdk-typescript). ## Are there free credits or a free trial? No. Trying things out costs the same as production usage. Top up the smallest amount that makes sense for an evaluation; remaining credit can be refunded within 14 days under EU consumer law (see [credits and billing](guides/credits-and-billing)). See [philosophy / principles in practice](philosophy/principles-in-practice) for why. ## Can I get an invoice with my company details? Yes. Set the legal name, billing address, and VAT number under **Dashboard → Settings → Billing**. Invoices issued from that point onwards carry the company details. Past invoices can be re-issued via support. See [credits and billing](guides/credits-and-billing). ## How do I look up a request after the fact? Open it on the dashboard under **Recent transactions**. Every response carries an `id` and an `X-Request-ID` header you can correlate against your own logs. The stored record includes the resolved model, the provider, the region, and the eco numbers. See [usage accounting](guides/usage-accounting) and [per-request metadata](models/per-request-metadata). ## Can I export my usage? Yes. The dashboard breaks usage down per model, provider, and key — tokens, upstream cost, and gCO₂eq — and exports to CSV. It gives finance and sustainability reporting a single source of truth, and the per-request carbon data can feed Scope 3 reporting. See [usage accounting](guides/usage-accounting). ## How do I rotate a leaked key? Create a new key, deploy it everywhere, then delete the old one. The deletion takes effect on the next request — no caching delay. Full guidance: [API key management](guides/api-keys). ## Why don't I see an eco estimate on some requests? When the resolved model's parameter count is unknown or unverified, we omit the `eco` field rather than fabricate a number. The [methodology](sustainable-ai/methodology) page explains the confidence bands; the [limits](sustainable-ai/limits) page covers the cases where eco is deliberately absent (embedding requests, agent steps with no tokens, mid-stream upstream errors). ## Is there an SLA? Production accounts have a posted SLA on the dashboard footer. The default account does not — best-effort. LowRouter is in public beta; features may change and interruptions may occur. ## Can I get a Data Processing Agreement? Contact us via the email on the [legal page](/impressum) and we'll work through your requirements. ## Where is LowRouter hosted? The platform runs on EU-sovereign infrastructure in France, operated by Carbonifer SAS (Nantes). Your keys, billing data, and usage metadata stay in the EU, outside CLOUD Act reach. When you send a request to a provider you chose, the prompt is transmitted to that provider in its region — which is why every response reports the routed region back to you. ## I found a bug — where do I report it? [github.com/carboniferio/lowrouter/issues](https://github.com/carboniferio/lowrouter/issues), ideally with a request ID from the `X-Request-ID` header on a representative request. --- # About LowRouter # About LowRouter LowRouter is made by [Carbonifer](https://carbonifer.io), a French company based in Nantes, founded by [Olivier Bierlaire](https://www.linkedin.com/in/olivierbierlaire/) and [Yorly Orellanos](https://www.linkedin.com/in/yorly-orellanos/?locale=en). We built it to answer a question we kept running into as engineers: what does this software actually cost the planet? Not in the abstract — for this request, right now. AI is quietly becoming one of the biggest energy sinks in software, and almost nobody puts a number on it. LowRouter sits between your application and the AI providers. Your requests go through us, we pass them to the right model, and we tell you things nobody else does along the way: - **Environment.** Every response reports the energy the request used and the CO₂ it likely produced. Estimates, clearly labelled as such — how we calculate them is public, so you can check our math rather than take our word. - **Sovereignty.** Most services sell you a brand name and hide who actually ran your request, and where. We name the provider and the region every time, and you can keep your traffic inside a jurisdiction you trust. We're European ourselves, and it matters: we sit outside the US CLOUD Act and inside GDPR and the AI Act — which, for many of our customers, is not a preference. It's a requirement they have to meet. - **Privacy.** We see every prompt and every answer. We don't mine that. It passes through, gets counted, and that's it. We apply the same standard to ourselves: LowRouter runs on open-source technologies and mostly\* European services, hosted at Scaleway, in France. \* Mostly, because a few pieces aren't European yet — Stripe handles our payments, for instance. They operate under European jurisdiction, and we're committed to replacing them with European alternatives as credible ones emerge. Baby steps, honestly taken. LowRouter is a paid service, plain and simple. You buy credits, you spend them on requests, and that's how we keep the lights on — not by doing something else with your data. The pricing is public, like everything else here. We're in invite-only beta. The docs, the dashboard, and the way we measure carbon are all public — deliberately. Look first, trust after.

Questions? [Get in touch](/help) --- # LowRouter API Reference **LowRouter API** (v1.0.0) OpenAI-compatible API gateway for sustainable AI inference. Routes LLM requests to the provider, model, and region you choose while remaining fully compatible with OpenAI client libraries. ## Servers - `https://api.lowrouter.ai/v1` — Production API (v1) - `https://api.lowrouter.ai/api/v1` — Production API (legacy /api/v1 alias) ## Endpoints ### GET /aliases List model aliases Lists the account's model aliases with quota usage. Aliases are account-wide: every API key sees the same set. **Operation ID**: `listAliases` · **Tags**: Aliases ### POST /aliases Create a model alias Creates a named pointer to a concrete route. The target is validated against the live catalogue with the same fail-closed semantics the router applies at request time; unroutable targets are rejected with `invalid_alias_target`, duplicate names with `alias_exists`, and creations beyond the account limit with `alias_limit_reached`. **Operation ID**: `createAlias` · **Tags**: Aliases ### DELETE /aliases/{name} Delete a model alias Hard delete. The name is immediately reusable; requests still sending `alias/` fail with `404 alias_not_found`. **Operation ID**: `deleteAlias` · **Tags**: Aliases ### GET /aliases/{name} Get a model alias **Operation ID**: `getAlias` · **Tags**: Aliases ### PATCH /aliases/{name} Rename and/or repoint a model alias Repointing takes the full target triple (provider, canonical_id, optional locode) and takes effect on the very next request that uses the alias. Rename and repoint may be combined; the update is atomic. **Operation ID**: `updateAlias` · **Tags**: Aliases ### GET /aliases/{name}/history Get change history for a model alias Every create, rename, and repoint recorded for the alias, newest first, with old and new values. **Operation ID**: `getAliasHistory` · **Tags**: Aliases ### GET /aliases/{name}/stats Get usage stats for a model alias Usage totals and a per-(model, provider, region) breakdown over a time window. Stats are keyed to the alias itself: renaming keeps continuity; deleting and recreating a name starts fresh. Only traffic since alias attribution shipped is counted. **Operation ID**: `getAliasStats` · **Tags**: Aliases ### POST /chat/completions Create chat completion Creates a chat completion with automatic routing to the most carbon-efficient provider. Fully compatible with OpenAI's chat completions API. **Operation ID**: `createChatCompletion` · **Tags**: Completions ### POST /completions Create text completion Creates a text completion (legacy endpoint for compatibility). Routes to providers supporting text completion format. **Operation ID**: `createCompletion` · **Tags**: Completions ### POST /embeddings Create embeddings Creates an embedding vector representing the input text. Routes to providers supporting embeddings via Bifrost. Applies billing (input tokens only) and carbon tracking. **Operation ID**: `createEmbedding` · **Tags**: Embeddings ### GET /generation/{generation_id} Get generation statistics Retrieves detailed statistics for a specific generation including tokens, cost, carbon metrics, and latency. (NICE TO HAVE - may not be implemented in MVP) **Operation ID**: `getGeneration` · **Tags**: Generations ### GET /metrics/{generation_id} Get generation metrics Retrieves carbon and energy metrics for a specific generation. This endpoint provides historical access to energy consumption and carbon emissions data for completed requests. **Operation ID**: `getGenerationMetrics` · **Tags**: Metrics ### GET /models List available models Returns a list of all available models with their capabilities, pricing, and carbon intensity metrics. The catalogue includes non-chat models (embeddings and similar). Each entry carries a `modality` field saying which interaction mode it serves; filter with `?modality=chat` to list only models callable on `/v1/chat/completions`. **Operation ID**: `listModels` · **Tags**: Models ### GET /models/{model} Retrieve a model Returns details for a single model matching the OpenAI retrieve model format. The model parameter may contain slashes (e.g. nebius/NousResearch/Hermes-4-70B). **Operation ID**: `getModel` · **Tags**: Models ### GET /providers List available providers Returns a list of all configured providers with their status and regions. (NICE TO HAVE - may not be implemented in MVP) **Operation ID**: `listProviders` · **Tags**: Providers