---
title: OpenRouter (one key, hundreds of models, no Claude account)
---

# OpenRouter Provider (the "Anthropic Skin")

## What it is

Omniscio supports spawning Claude-Code-style sessions backed by **OpenRouter** — a gateway to hundreds of models — via its Anthropic-compatible endpoint (the **"Anthropic Skin"**, hosted at `https://openrouter.ai/api`). It is the **seventh** member of the `anthropic-compat` family, alongside `deepseek`, `kimi`, `glm`, `minimax`, `meta`, and `qwen`.

**What makes OpenRouter special:** it is the **"connect with one key, no Claude account"** run path. A new user can pick OpenRouter in first-run setup, paste a single `sk-or-…` key, and run real agents — no Anthropic account, no sign-in, no managed pool. That **same one key also powers the built-in AI helpers** (session titles, suggestions, reply drafts, the daily digest), because it reuses the shared `userOpenrouterApiKey` slot.

> **Two OpenRouters — don't confuse them.** OpenRouter ALSO ships as a flag-gated `customOpenai` **preset** (proxy-backed through the local CLIProxyAPI, behind the off-by-default `custom-providers` feature, keyed separately in `customProviderKeys`). That preset is a power-user "add any OpenAI-compatible endpoint" path. THIS provider (`openrouter`) is the no-proxy, onboarding-visible, one-key path. They coexist; most users only ever see this one.

## Where to find it

First-run setup's **Connect** step puts OpenRouter in the provider pick list; afterwards it lives in **Settings → Accounts** with the rest of the providers.

## How it behaves

### What the user sees

An OpenRouter session looks identical in the sidebar and main pane to a Claude session — same status dots, streaming bubbles, Ctrl+Enter to send, Plan / Auto-Accept / Bypass Permissions modes, and tool-call approval UI. Tool-call behavior is **identical to Claude** because the same `claude` CLI binary is doing the talking — only the model and the upstream endpoint differ. There is no yolo / auto-approve mode; the standard Claude approval flow applies.

### How to enable

Two ways lead here:

1. **First-run setup (the headline path).** On the Setup v2 "Connect" step, **OpenRouter** appears in the provider pick list ("Run any model with one key — no account needed"). Pick it, paste your key, and setup finishes with a working, account-free run path. Saving the key auto-enables `allowOpenrouterSessionSpawn` AND powers the built-in helpers (one key, both jobs). A note under the key field says exactly that.
2. **Settings → Accounts.** Like the other alternative providers, OpenRouter's management card lives under **Settings → Accounts → Show alternative AI providers** (off by default). Its two gates: the **Allow OpenRouter sessions** toggle and the **OpenRouter key** field (which is the SAME "Your OpenRouter key" field under Settings → API Keys — one shared slot).

> **Add your own key — or run on Omniscio credits.** The wiring ships complete but keyless — with no key, an OpenRouter spawn is refused up front with a clear "add a key" message (it never crashes or silently falls back to Claude). Two ways to pay, both rows of OpenRouter's **supply list** in **Settings → Accounts → Who pays & who serves**: your own `sk-or-…` key (below), **or** Omniscio's company key drawing down your prepaid balance — see the **Company OpenRouter credits** section below. A session connects through the first row that can serve.

Once ready you can launch an OpenRouter session three ways: the per-launch **ChangeProviderButton** on a fresh session, a **per-project default** (Edit Project → Default provider), or **programmatically** (`provider: 'openrouter'` via recipes / the CLI control API).

### Company OpenRouter credits (run on Omniscio's key, draw down your credits)

Instead of pasting your own OpenRouter key, **any signed-in user** can run OpenRouter sessions on **Omniscio's company OpenRouter key** — usage draws down that user's **prepaid credit balance** (the same balance shown in Settings → **My API Keys**) and is cut off when it hits zero. It is the **Omniscio credits** row of OpenRouter's supply list in **Settings → Accounts → Who pays & who serves** — a new user's list holds it right after their own key, and moving it up makes credits pay first. See [model-vendors.md](model-vendors.md).

Two things set it apart from the GLM company-credits path:

- **Ungated — no plan tier.** GLM's company credits are Pro-only; OpenRouter's are open to **any signed-in account** with a positive prepaid balance (`minTier: 'free'` in the routing map). The gate is sign-in + credits, nothing more.
- **Every model, priced live.** It covers **every model OpenRouter serves** (~400 today), not a fixed list. The gateway meters whatever `vendor/model` slug the session runs against OpenRouter's own price list (fetched + cached hourly), so a curated-picker model and a recipe/CLI-supplied slug both bill at their real rate. A model whose price the cache hasn't loaded yet is billed at a conservative blended floor (never under-billed) and self-corrects within the hour.

Rules (same fail-closed stance as GLM):

- **Signed-in only.** The row authenticates to Omniscio's gateway with your sign-in token; signed out, it is skipped and the next row serves, and with no other row an OpenRouter spawn fails closed with a "sign in (or add your own key)" message — never a broken session.
- **The list's order decides.** Whichever of your own key and the credits row comes first in OpenRouter's list pays first; the other is used when the first cannot serve.
- **Out of credits → the next row, or a clean stop.** When your balance reaches zero the gateway refuses; the session moves to the next row of the list that can serve, and only when none is left does it stop with an "out of credits — top up" message, not a raw error.

How it works: the OpenRouter session runs through the **JLS gateway** (the same Cloud Run service behind "My API Keys") at `<gateway>/v1/openrouter`, with your sign-in token as the bearer. The **company OpenRouter key lives only in the gateway** (`OPENROUTER_API_KEY` in the litellm env — already present for the DeepSeek failover chain) and never reaches your machine. The gateway prices the turn's real token usage from OpenRouter's live rate list, applies the standard **10% markup**, and debits your prepaid credits. The routing is data-driven (`FUNDABLE_PROVIDERS` in [fundable-providers.ts](../../src/shared/providers/fundable-providers.ts), a per-provider `minTier` table shared with GLM), so the two providers differ only by one row.

**Operator note (going live):** the credits row is **inert** until the deploy carries the live-pricing forwarder + a credit grant. Because the litellm `openrouter/*` route + `OPENROUTER_API_KEY` are already wired (for the DeepSeek failover), going live is: deploy the gateway (`gateway/deploy.sh jls` + `litellm`), confirm a real `/v1/openrouter` turn returns a non-zero real-rate cost (the live drain receipt), and grant users credits (`gateway/scripts/grant-credits.mjs`). Full invariants + steps: [company-openrouter-gateway-contract.md](../../.claude/memory/contracts/company-openrouter-gateway-contract.md).

### Choosing the model

OpenRouter is a gateway, so its model ids are `vendor/model` **OpenRouter slugs**. Omniscio curates a short list (registry-driven, `MODELS_BY_PROVIDER.openrouter` in [provider-models.ts](../../src/shared/providers/provider-models.ts)):

- **Qwen3 Coder** (`qwen/qwen3-coder`, the default) — an open coding specialist (480B MoE); the low-cost open default, and the cheapest model here that could be proven to answer.
- **Qwen3.8 Flash** (`qwen/qwen3.8-flash`) — a fast, cost-efficient multimodal MoE. Offering it here is what makes this id **nameable**: a `reviewers` entry is placed by looking the name up against the engines' pickers, so an id no picker carries resolves to nothing and silently falls back to the author's own engine. **No AI Code Review entry currently names it** — both review steps run `glm` and `deepseek`. Verified on **this** lane, not the OpenAI-compat one — a live `POST https://openrouter.ai/api/v1/messages` answered HTTP 200 with a well-formed **Anthropic Message** (the shape that `deepseek/deepseek-chat` failed to return), so the engine can read it. It had been on `OPENROUTER_REFUSED_MODEL_IDS` (`src/shared/providers/custom-providers.ts`) since 2026-09-25 and came off on 2026-09-27 on that measurement.
- **DeepSeek V3** (`deepseek/deepseek-chat`) — cheap and capable, but **it could not answer as of 2026-09-25** (see the note below); it stays selectable rather than being removed, and it is no longer the default.
- **Kimi K3** (`moonshotai/kimi-k3`) and **Kimi K2.7 Code** (`moonshotai/kimi-k2.7-code`) — the same upstream models the `kimi` provider serves, offered here too because the two lanes bill different accounts.
- **Claude Sonnet 4.6** (`anthropic/claude-sonnet-4.6`) — Claude via OpenRouter, one click away (premium, billed at OpenRouter's Claude rates).

> [!WARNING]
> **`deepseek/deepseek-chat` could not answer as of 2026-09-25, which is why it is no longer the default.** It replies HTTP **200** with a body that is JSON but **not** an Anthropic Message, so the engine cannot read it and the turn ends with zero tokens and no output. Two other slugs (`anthropic/claude-sonnet-4.6`, `moonshotai/kimi-k3`) answered normally on the same key and lane in the same minute, so it is the model, not the key or the account. Nothing in OpenRouter's model list marks it — it is still listed, with a real timestamp, a real context window and non-zero pricing — so **a live request is the only way to know**. `qwen/qwen3-coder` was chosen as the replacement on an explicit live test; **do not revert this default without running one.**

**Use default** spawns **`qwen/qwen3-coder`** rather than omitting the model flag — OpenRouter's endpoint serves the model named in the slug, and the shared `claude` binary's built-in default is a Claude id OpenRouter can't route, so the registry pins a real slug via `getProviderDefaultModel`. An OpenRouter session never inherits your Claude default model.

### When a turn produces no output

A vendor can reject a request in a way the `claude` binary cannot parse — the case above. The engine then emits a *synthetic placeholder*: a turn with zero tokens and no visible reply. Omniscio keeps the evidence for it rather than guessing:

- the **HTTP status** the engine reported for the turn,
- the **synthetic frame's own `error`** value when it carries one,
- a **redacted, bounded snippet** of what the engine said.

The raw snippet goes to the **log** (`[placeholder-evidence]`), never into the session transcript. What you see in the session is a **humanized** sentence that names the engine, the model, and — when the running model is the provider's default — that Omniscio chose it. When nothing at all was reported, the message says so instead of inventing a cause. A recognized vendor rejection (out of balance, an expired key, an over-long conversation, a rate limit) is still named specifically, as before.

### Error states and fixes

Two gap codes (no binary to check — same `claude` binary as Claude):

| Gap           | What it means                                  | Click-to-fix lands you at…                            |
| ------------- | ---------------------------------------------- | ----------------------------------------------------- |
| `toggle-off`  | "Allow OpenRouter sessions" is OFF in Settings | Settings → Account → Allow OpenRouter sessions toggle |
| `key-missing` | OpenRouter API key not configured              | Settings → API Keys → Your OpenRouter key field       |

A retired/invalid model id, out-of-balance, context-overflow, bad key, or rate-limit is surfaced with the specific cause + fix via the shared `classifyAnthropicCompatPlaceholderError` / `isRetiredAnthropicCompatModel` guards the DeepSeek/Kimi/GLM/MiniMax/Meta/Qwen siblings use. See [provider-spawn-model-availability-contract.md](../../.claude/memory/contracts/provider-spawn-model-availability-contract.md).

## For agents

### How it works under the hood

**Spawn-env redirect, no alternate manager, no proxy.** OpenRouter publishes an Anthropic-compatible endpoint — same request/response shape as Anthropic's Messages API, at a different base URL. Omniscio reuses the existing `claude` CLI binary and redirects every HTTP call at spawn time by setting two environment variables on the child process:

- `ANTHROPIC_BASE_URL=https://openrouter.ai/api` — sends every request to OpenRouter's Anthropic Skin instead of `api.anthropic.com`. The `claude` CLI appends `/v1/messages` (so NO `/v1` suffix in the base). Env-overridable via `AMC_OPENROUTER_BASE_URL`. Verified against OpenRouter's official Claude Code integration docs.
- `ANTHROPIC_AUTH_TOKEN=<your OpenRouter key>` — supersedes the Claude OAuth/API-key for this child only.

No alternate session manager exists — readiness gates, the `spawn_non_claude_session` event fires, then it falls through to the same `createSessionWithPrompt()` path Claude uses. [spawn-cluster-manager.ts](../../src/main/process/spawn-cluster-manager.ts) reads the session row's `provider` column and injects the overlay on every CLI invocation (spawn, resume, aside-runner). Because OpenRouter **runs the `claude` binary**, it is eligible for the same crash/restart/suspended/re-arm recovery as native Claude — distinct from Anthropic _account_ machinery (rate-limit recovery, load-balancing), which it stays out of since it authenticates with your OpenRouter key.

> **Data locality (a routing note).** Selecting OpenRouter repoints the ENTIRE session — every prompt, file the agent reads, tool result, and the full `--resume` history — to OpenRouter, which itself routes to many upstream model hosts (per the chosen slug's provider). See the cross-border subprocessor disclosure in [SUBPROCESSORS.md](../SUBPROCESSORS.md).

### Cost tracking

Spend hits the standard `api_cost_log` table with `source: 'openrouter'`, so the Stats → Spend dashboard breaks it out separately. **Cost is re-priced, not taken verbatim:** the `claude` binary has no OpenRouter price table, so OpenRouter is `costReporting: 'estimated'` — Omniscio recomputes each turn from token counts × the slug's per-M rate via `repriceAnthropicCompatTurnCost`, the same path the sibling compat vendors use. Rates ([model-pricing.ts](../../src/shared/model-pricing.ts) `MODEL_PRICING`): `deepseek/deepseek-chat` $0.26/$1.03, `qwen/qwen3-coder` $0.22/$1.80, `anthropic/claude-sonnet-4.6` $3/$15 per 1M in/out (OpenRouter list; a ~6% routing fee applies on top). Because it is `estimated` + a metered own-key vendor, OpenRouter also joins the per-provider daily spend cap + anomaly monitor. See [central-ai-spend-contract.md](../../.claude/memory/contracts/central-ai-spend-contract.md). **The company-credits path meters separately:** when you run on Omniscio credits the gateway prices each turn server-side from OpenRouter's live rate list for credit debiting — independent of this client-side `estimated` reprice.

### Files

- [src/shared/providers/registry.ts](../../src/shared/providers/registry.ts) — the `openrouter` descriptor (`anthropic-compat`, `costReporting: 'estimated'`, `pickerOrder` 23)
- [src/main/services/providers/main-registry.ts](../../src/main/services/providers/main-registry.ts) — the readiness wiring (toggle + API-key gates, no binary gate)
- [src/shared/providers/provider-setup-catalog.ts](../../src/shared/providers/provider-setup-catalog.ts) — the `https://openrouter.ai/api` endpoint, models-list URL, `AMC_OPENROUTER_BASE_URL` override, and the shared `userOpenrouterApiKey` credentialKey
- [src/shared/providers/provider-models.ts](../../src/shared/providers/provider-models.ts) — `OPENROUTER_MODELS` (curated slugs) + `qwen/qwen3-coder` default
- [src/main/services/anthropic-compat-provider.ts](../../src/main/services/anthropic-compat-provider.ts) — the base URL + credential accessor (`ANTHROPIC_COMPAT_PROVIDERS` membership)
- [src/main/services/providers/openrouter-credential-store.ts](../../src/main/services/providers/openrouter-credential-store.ts) — the derived store over the shared `userOpenrouterApiKey` slot
- [src/main/db/migrations/20260827152412-allow-openrouter-provider.ts](../../src/main/db/migrations/20260827152412-allow-openrouter-provider.ts) — extends the `sessions.provider` allow-list trigger to include `openrouter`
- [src/renderer/src/features/onboarding/setup-v2/onboarding-providers.ts](../../src/renderer/src/features/onboarding/setup-v2/onboarding-providers.ts) — the onboarding pick + `powersHelpers` flag
- [src/renderer/src/features/onboarding/setup-v2/frames/connect-parts.tsx](../../src/renderer/src/features/onboarding/setup-v2/frames/connect-parts.tsx) — the Connect key field + the collapsed one-key-at-a-time picker
- [src/shared/types/settings/accounts-providers-settings.ts](../../src/shared/types/settings/accounts-providers-settings.ts) — `allowOpenrouterSessionSpawn`
- [src/shared/types/settings/ai-features-settings.ts](../../src/shared/types/settings/ai-features-settings.ts) — the reused `userOpenrouterApiKey`
- [src/shared/types/settings/accounts-providers-settings.ts](../../src/shared/types/settings/accounts-providers-settings.ts) — `supplyLists.openrouter`, OpenRouter's supply list (the older `providerFunding.openrouter` choice is read once, to build it)
- [src/shared/providers/fundable-providers.ts](../../src/shared/providers/fundable-providers.ts) — the per-lane `minTier` map (`openrouter: 'free'`) the Omniscio-credits row checks
- [src/main/services/providers/supply-connect.ts](../../src/main/services/providers/supply-connect.ts) — connects an OpenRouter session on the first row of its supply list that can serve: the user's key, or the Omniscio-credits row
- [src/main/services/providers/main-registry.ts](../../src/main/services/providers/main-registry.ts) — the openrouter readiness gate, which asks whether any row of that list can serve
- [src/renderer/src/features/settings/ApiKeysSettings.tsx](../../src/renderer/src/features/settings/sections/api-keys/ApiKeysSettings.tsx) — your OpenRouter key field, with one line pointing at Who pays & who serves (the credits toggle that lived here is gone)
- [gateway/gateway/openrouter-pricing.ts](../../gateway/gateway/openrouter-pricing.ts) — live-fetch + hourly cache of OpenRouter's `/api/v1/models` price list; `priceOpenRouterUsageUsd`
- [gateway/gateway/litellm-forwarder.ts](../../gateway/gateway/litellm-forwarder.ts) — `LIVE_FETCH_PRICED_PROVIDERS` (openrouter) → prices streamed + non-streamed turns from the live table

## Related

### Related

- [qwen-provider.md](qwen-provider.md) — the sixth `anthropic-compat` sibling
- [deepseek-provider.md](deepseek-provider.md) · [kimi-provider.md](kimi-provider.md) · [glm-provider.md](glm-provider.md) — the other `anthropic-compat` siblings
- [switching-providers.md](switching-providers.md) — the unified session-provider guide
- [provider-registry-contract.md](../../.claude/memory/contracts/provider-registry-contract.md) — the DeepSeek + Kimi + GLM + MiniMax + Meta + Qwen + OpenRouter (`anthropic-compat`) invariants
- [company-openrouter-gateway-contract.md](../../.claude/memory/contracts/company-openrouter-gateway-contract.md) — the invariants of OpenRouter's Omniscio credits row (ungated, gateway-routed, live-priced company credit)
- [model-vendors.md](model-vendors.md) — who pays for an OpenRouter session (the supply list)
- [company-glm-gateway-contract.md](../../.claude/memory/contracts/company-glm-gateway-contract.md) — the GLM sibling of the company-credit path (Pro-gated, static-priced)
- [api-keys.md](api-keys.md) — the "My API Keys" gateway + prepaid-credit balance the company-credit path draws down

