coderouter: OpenAI and OpenRouter API keys as Responses providers - #12111
Conversation
…m picker The coderouter page showed Claude upstream accounts and hosted subrouter accounts (Codex) as two unrelated sections, each with its own add flow, plus a per-page team switcher and provider logos. Now one Accounts section lists both sources in one table with provider, label, status, and actions, and one add panel offers Anthropic API key, Claude Code OAuth token, Amazon Bedrock, Codex, and OpenCode. The Codex and OpenCode entries are text-only CLI commands. The team scope moves to the bottom-left sidebar as a dashboard-wide switcher that persists the same cookie the server already reads, so the coderouter page no longer renders its own team links. Usage first, accounts second, machines last. Removes the unused aiAccounts and claudeUpstream catalogs and adds coderouterAccounts and teamSwitcher (en, ja). Claude-Session: https://claude.ai/code/session_01LMP2oJjwAkLbLxLaWmLaLS
The team scope was a separate row above the account row. It is now a Team entry inside the bottom-left account menu, next to Settings and Billing, that opens a submenu listing every permitted team with the current one checked. The trigger shows the current team under the user name. The scope hook moves to dashboard-team-scope.ts and keeps the cookie-based switch. The sidebar shell is back to its main layout. The "not configured" notice now says Codex accounts are hidden because the hosted account service is not configured on this deployment. Claude-Session: https://claude.ai/code/session_01LMP2oJjwAkLbLxLaWmLaLS
The sidebar footer had a standalone sun/moon button next to the account menu. The switch is now a menu item after Team, named after the theme it switches to, and the footer holds only the account control. The toggle logic is a shared useThemeToggle hook so the site header keeps its button. Claude-Session: https://claude.ai/code/session_01LMP2oJjwAkLbLxLaWmLaLS
The accounts table now includes the coderouter-native accounts that `cr add` stores (Codex and OpenCode Go sign-ins), with their session count, state, and a remove action against /api/coderouter/accounts. Before, the page only listed hosted-subrouter accounts and Claude upstream accounts, so the accounts coderouter actually routes with were invisible. "Pricing coverage" is gone as a metric card. The API-equivalent footnote now says what the value is, and only when some tokens had no list price does it add the share that was left out. Claude-Session: https://claude.ai/code/session_01LMP2oJjwAkLbLxLaWmLaLS
Teams can add an OpenAI API key or an OpenRouter API key as a coderouter account next to their Codex sign-ins. The Responses surface (/v1/responses, /v1/models) now selects from a provider pool: Codex sign-ins go to the ChatGPT backend as before, an OpenAI key goes to api.openai.com, and an OpenRouter key goes to openrouter.ai with bare OpenAI model ids rewritten to openai/<model>. Session stickiness, placement spread, rate-limit cooldown and failover are unchanged; a key the provider rejects is marked broken with last failure api_key_rejected and the request moves on. Keys are stored like every other credential (KMS envelope, AAD bound to team/account/provider). The provider account id is a SHA-256 fingerprint of the key, so re-adding a key updates the same row and the row never carries the secret. API keys have no expiry and skip the refresher. Migration 20260907150000 widens the provider CHECK on the three coderouter tables. Run it on staging and production before merging. The dashboard add panel gains OpenAI API key and OpenRouter API key tabs posting to /api/coderouter/accounts. Claude-Session: https://claude.ai/code/session_01LMP2oJjwAkLbLxLaWmLaLS
|
Warning Review limit reachedNext included review available in 3 minutes. View limit detailsLimit details: You’ve used all 10 included reviews currently available. You've used all free OSS reviews for now. Wait for the free limit to reset to keep reviewing this public repository. Review configuration: ⚙️ Run configurationConfiguration used: Path: .coderabbit.yaml Review profile: ASSERTIVE Plan: Advanced Run ID: 📒 Files selected for processing (19)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
Base UI Tabs replace the hand-rolled tab buttons in the add panel, so arrow keys and roving focus work. Team items in the account menu are a Menu.RadioGroup, which owns the checked state. The accounts section is keyed by team so a team switch never keeps a half-filled form. A team switch drops only the team query parameter. A 503 from a Claude account route shows the Claude save or remove error, not the shared-account copy. The hosted-subrouter notice is shown to account managers only and now says which accounts it is about. Test fixtures reset per test. Claude-Session: https://claude.ai/code/session_01LMP2oJjwAkLbLxLaWmLaLS
…ter-api-key-providers Claude-Session: https://claude.ai/code/session_01LMP2oJjwAkLbLxLaWmLaLS
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes using default effort and found 1 potential issue.
❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.
Reviewed by Cursor Bugbot for commit 4ba728b. Configure here.
…itcher' into feat-coderouter-api-key-providers
…itcher' into feat-coderouter-api-key-providers
…itcher' into feat-coderouter-api-key-providers
…ey-providers # Conflicts: # web/app/[locale]/dashboard/components/coderouter-accounts.tsx # web/messages/en.json # web/messages/ja.json # web/tests/coderouter-accounts.test.tsx
|
All contributors have signed the CLA ✍️ ✅ |
…sion binding per surface, models 401 failover The envelope identity check still listed only codex and opencode-go, so an OpenAI or OpenRouter key could not be encrypted or decrypted. It now reads the shared provider list. Session bindings are stored under the surface's first provider, so a session that moves between a Codex sign-in and a key replaces its row instead of adding a second one. Model discovery on a 401 forces a refresh, which marks a rejected key broken, and tries the next account. Claude-Session: https://claude.ai/code/session_01LMP2oJjwAkLbLxLaWmLaLS
|
Addressed the Bugbot findings: the envelope identity check now uses the shared provider list (the high-severity one, a real miss), session bindings are stored under the surface's first provider so a move between a sign-in and a key replaces the row, and /v1/models fails over on 401 by forcing a refresh that marks a rejected key broken. Tests added for all three. |
92f8da5 Fix sidebar Sign In button and account avatar not rendering on Intel Macs (manaflow-ai#12126) 5a16fbb cloud: guest `cmux self` and `cmux vm ls` via GET /api/vm/self (manaflow-ai#12116) 9cbdb7c web: remove the public /dashboard/admin page (manaflow-ai#12110) 5fda406 coderouter: OpenAI and OpenRouter API keys as Responses providers (manaflow-ai#12111)
…naflow-ai#12111) * web(dashboard): one accounts list, sidebar team switcher, no page team picker The coderouter page showed Claude upstream accounts and hosted subrouter accounts (Codex) as two unrelated sections, each with its own add flow, plus a per-page team switcher and provider logos. Now one Accounts section lists both sources in one table with provider, label, status, and actions, and one add panel offers Anthropic API key, Claude Code OAuth token, Amazon Bedrock, Codex, and OpenCode. The Codex and OpenCode entries are text-only CLI commands. The team scope moves to the bottom-left sidebar as a dashboard-wide switcher that persists the same cookie the server already reads, so the coderouter page no longer renders its own team links. Usage first, accounts second, machines last. Removes the unused aiAccounts and claudeUpstream catalogs and adds coderouterAccounts and teamSwitcher (en, ja). Claude-Session: https://claude.ai/code/session_01LMP2oJjwAkLbLxLaWmLaLS * web(dashboard): put the team picker inside the account menu The team scope was a separate row above the account row. It is now a Team entry inside the bottom-left account menu, next to Settings and Billing, that opens a submenu listing every permitted team with the current one checked. The trigger shows the current team under the user name. The scope hook moves to dashboard-team-scope.ts and keeps the cookie-based switch. The sidebar shell is back to its main layout. The "not configured" notice now says Codex accounts are hidden because the hosted account service is not configured on this deployment. Claude-Session: https://claude.ai/code/session_01LMP2oJjwAkLbLxLaWmLaLS * web(dashboard): move the theme switch into the account menu The sidebar footer had a standalone sun/moon button next to the account menu. The switch is now a menu item after Team, named after the theme it switches to, and the footer holds only the account control. The toggle logic is a shared useThemeToggle hook so the site header keeps its button. Claude-Session: https://claude.ai/code/session_01LMP2oJjwAkLbLxLaWmLaLS * web(dashboard): list cr-added accounts, drop the pricing coverage card The accounts table now includes the coderouter-native accounts that `cr add` stores (Codex and OpenCode Go sign-ins), with their session count, state, and a remove action against /api/coderouter/accounts. Before, the page only listed hosted-subrouter accounts and Claude upstream accounts, so the accounts coderouter actually routes with were invisible. "Pricing coverage" is gone as a metric card. The API-equivalent footnote now says what the value is, and only when some tokens had no list price does it add the share that was left out. Claude-Session: https://claude.ai/code/session_01LMP2oJjwAkLbLxLaWmLaLS * coderouter: route Responses calls through OpenAI and OpenRouter API keys Teams can add an OpenAI API key or an OpenRouter API key as a coderouter account next to their Codex sign-ins. The Responses surface (/v1/responses, /v1/models) now selects from a provider pool: Codex sign-ins go to the ChatGPT backend as before, an OpenAI key goes to api.openai.com, and an OpenRouter key goes to openrouter.ai with bare OpenAI model ids rewritten to openai/<model>. Session stickiness, placement spread, rate-limit cooldown and failover are unchanged; a key the provider rejects is marked broken with last failure api_key_rejected and the request moves on. Keys are stored like every other credential (KMS envelope, AAD bound to team/account/provider). The provider account id is a SHA-256 fingerprint of the key, so re-adding a key updates the same row and the row never carries the secret. API keys have no expiry and skip the refresher. Migration 20260907150000 widens the provider CHECK on the three coderouter tables. Run it on staging and production before merging. The dashboard add panel gains OpenAI API key and OpenRouter API key tabs posting to /api/coderouter/accounts. Claude-Session: https://claude.ai/code/session_01LMP2oJjwAkLbLxLaWmLaLS * web(dashboard): account menu order is settings, theme, billing, team Claude-Session: https://claude.ai/code/session_01LMP2oJjwAkLbLxLaWmLaLS * web(dashboard): pin the theme in the menu order test Claude-Session: https://claude.ai/code/session_01LMP2oJjwAkLbLxLaWmLaLS * web(dashboard): address review findings on the accounts page and menu Base UI Tabs replace the hand-rolled tab buttons in the add panel, so arrow keys and roving focus work. Team items in the account menu are a Menu.RadioGroup, which owns the checked state. The accounts section is keyed by team so a team switch never keeps a half-filled form. A team switch drops only the team query parameter. A 503 from a Claude account route shows the Claude save or remove error, not the shared-account copy. The hosted-subrouter notice is shown to account managers only and now says which accounts it is about. Test fixtures reset per test. Claude-Session: https://claude.ai/code/session_01LMP2oJjwAkLbLxLaWmLaLS * web(dashboard): style the active add tab with Base UI's data-active Claude-Session: https://claude.ai/code/session_01LMP2oJjwAkLbLxLaWmLaLS * web(dashboard): assert the active tab without assuming attribute order Claude-Session: https://claude.ai/code/session_01LMP2oJjwAkLbLxLaWmLaLS * web(dashboard): show account identifiers to managers only, label refreshing state Claude-Session: https://claude.ai/code/session_01LMP2oJjwAkLbLxLaWmLaLS * coderouter: admit API-key providers in the envelope identity, one session binding per surface, models 401 failover The envelope identity check still listed only codex and opencode-go, so an OpenAI or OpenRouter key could not be encrypted or decrypted. It now reads the shared provider list. Session bindings are stored under the surface's first provider, so a session that moves between a Codex sign-in and a key replaces its row instead of adding a second one. Model discovery on a 401 forces a refresh, which marks a rejected key broken, and tries the next account. Claude-Session: https://claude.ai/code/session_01LMP2oJjwAkLbLxLaWmLaLS

Stacked on #12103 (retarget to
mainafter it merges).Teams can add an OpenAI API key or an OpenRouter API key as a coderouter account next to their Codex sign-ins.
/v1/responsesand/v1/modelsselect from a provider pool (codex,openai-apikey,openrouter-apikey) with the same session stickiness, placement spread, 429 cooldown and failover. Codex sign-ins still go to the ChatGPT backend; an OpenAI key goes toapi.openai.com; an OpenRouter key goes toopenrouter.ai, with bare OpenAI model ids rewritten toopenai/<model>and OpenRouter attribution headers set.last failure api_key_rejectedand the request moves to the next account. The dashboard shows the state; the fix is to remove and re-add the key.{provider, apiKey, label}to/api/coderouter/accounts. ThecrCLI is a separate repo and does not gaincr add openaihere.Operator step before merge: migration
web/db/migrations/20260907150000_coderouter_api_key_providerswidens the provider CHECK oncoderouter_accounts,coderouter_credentials,coderouter_session_accounts. Runbun run cloud-vm:migrate -- stagingthen-- productionfirst, perservices/coderouter/README.md. Verified on a scratch Postgres.Tests:
tests/coderouter-api-key-providers.test.ts(parsing, fingerprint, masking, refresher, upstream routing for both keys, model rewrite, 401 failover, models catalog), existing proxy, refresh, selector and accounts tests updated for the pooled provider and the new fixture field. Typecheck and complexity gate pass.Not verified live: no OpenAI or OpenRouter key was available in this session to send a real request through.
https://claude.ai/code/session_01LMP2oJjwAkLbLxLaWmLaLS
Need help on this PR? Tag
@codesmith-botwith what you need. Autofix is disabled.Note
High Risk
Adds handling and encrypted storage of third-party API keys and changes core Codex proxy routing, session pooling, and credential lifecycle; requires the provider CHECK migration before deploy.
Overview
Teams can add OpenAI and OpenRouter API keys as coderouter accounts alongside Codex sign-ins. The dashboard Add account panel gets two new tabs with forms that POST
{ provider, apiKey, label? }to/api/coderouter/accounts.Routing:
/v1/responsesand/v1/modelsnow select from a shared pool (codex,openai-apikey,openrouter-apikey) with the same session stickiness, placement spread, 429 cooldown, and failover. Traffic goes to the ChatGPT Codex backend,api.openai.com, or OpenRouter as appropriate; OpenRouter rewrites bare model ids toopenai/<model>and sets attribution headers.Credentials: Keys are validated and stored in the existing KMS envelope;
providerAccountIdis a SHA-256 fingerprint so re-adding the same key updates one row. API keys do not expire and skip OAuth refresh—a 401 triggers a forced “refresh” that marks the account broken (api_key_rejected) and fails over to the next account.Data: Schema and migration
20260907150000_coderouter_api_key_providerswiden provider CHECKs oncoderouter_accounts,coderouter_credentials, andcoderouter_session_accounts. Session bindings for the Responses surface are keyed under the pool’s first provider (codex) so failover between pool members does not leave duplicate bindings.Reviewed by Cursor Bugbot for commit 8b4b167. Bugbot is set up for automated code reviews on this repo. Configure here.
Summary by cubic
Adds OpenAI and OpenRouter API keys as coderouter providers, so teams can route
/v1/responsesand/v1/modelsthrough these keys alongside Codex sign-ins. Codex sign-ins still go to the ChatGPT backend; OpenAI keys go toapi.openai.com; OpenRouter keys go toopenrouter.aiwith bare OpenAI model IDs rewritten toopenai/<model>.Behavior
api_key_rejected) and the request moves to the next account.{provider, apiKey, label}to/api/coderouter/accounts.Migration
20260907150000_coderouter_api_key_providerson staging and production before merging; it widens the provider CHECK on three coderouter tables.Written for commit 8b4b167. Summary will update on new commits.