Skip to content

fix(billing): show OpenRouter's routing reason when an EU key cannot serve the model - #1404

Merged
Makisuo merged 6 commits into
mainfrom
fix/openrouter-byok-eu-routing-errors
Oct 13, 2026
Merged

Makisuo merged 6 commits into
mainfrom
fix/openrouter-byok-eu-routing-errors

Conversation

@Makisuo

@Makisuo Makisuo commented Oct 13, 2026 •

Copy link
Copy Markdown
Collaborator

Follow-up to #1401, which added the OpenRouter client and the EU key check.

Problem

A customer's EU key now passes the plan check, but every chat turn fails with a 404 from eu.openrouter.ai:

No endpoints found for openai/gpt-6-luna. Every candidate endpoint was removed during routing: Filter by Data Region removed openai/flex, openai, azure, azure/us, amazon-bedrock/us-east-1, openai/fast; Drop Out-of-Region Endpoints After BYOK removed azure/eu.

azure/eu is the only EU provider for the EU default model. It was removed by a setting in the customer's own OpenRouter account: OpenRouter's wording points to a provider key attached to that account whose Azure resource is outside the EU. The EU key check sees this 404 as an untyped NotFound and tells the admin "Could not check this key with OpenRouter (NotFound). Try again." That message is wrong, because retrying fails the same way every time, and it drops the one line that explains what to change.

Change

  • openrouter-api.ts: createChatCompletion now has typed BadRequest (400) and NotFound (404) errors, and both keep OpenRouter's message.
  • OrgOpenRouterKeyService: toKeyError refuses a save that fails with either as a key problem (OpenRouterKeyValidationError). The admin sees OpenRouter's reason, followed by a pointer to the provider key and data region settings in their OpenRouter account.

Only the EU check makes a chat call, so US saves are unchanged. A key that is already saved isn't re-checked: the affected org has to fix its OpenRouter settings, or re-save its key to see the reason.

Tests

A new case gives the EU check a 404 with OpenRouter's { error: { code, message } } body. It asserts that the save is refused, that OpenRouter's reason is in the message, and that nothing is stored.

🤖 Generated with Claude Code


View with [code]smith Autofix with [code]smith
Need help on this PR? Tag @codesmith-bot with what you need.


Devin Review

Summary by CodeRabbit

  • Bug Fixes
    • Improved validation for EU OpenRouter keys. When the selected model has no available endpoints, the key is rejected instead of being saved, and the error explains the issue and points to account settings.
    • Improved feedback for other OpenRouter request errors, including invalid requests and unavailable resources, while keeping existing handling for authentication and payment errors unchanged.

…endpoint

The EU instance sends every model call to eu.openrouter.ai, which only serves
chat to Business or Enterprise plans. The save-time check called /key, which
answers 200 for any live key, so a key on a lower plan saved fine and then
every agent turn failed with a 403.

On the EU instance, saving now also asks the in-region chat endpoint for one
token: a 403 is refused with a message naming the plan requirement, a 402 as
out of credits.
…ient

The key check now goes through two typed operations (getKey,
createChatCompletion) built on @distilled.cloud/core: a credentials service
resolved per request, a REST protocol reading OpenRouter's
{ error: { code, message } } body, and per-status error classes
(Unauthorized, PaymentRequired, Forbidden). The service maps those tags to
what the settings page shows instead of branching on raw status codes.
…/5xx

- EU_DEFAULT_OPENROUTER_MODEL moves to @maple/domain/organization-regions;
  the agents and the EU key check both read it, so a model change cannot
  leave EU key saves failing on a model the catalogue dropped
- Cover the EU check's 402 refusal and its retryable upstream failure
…serve the model

An EU key on the right plan can still fail every turn when its own OpenRouter
account settings remove the only in-region provider (e.g. an attached Azure
provider key outside the EU: "Drop Out-of-Region Endpoints After BYOK removed
azure/eu", a 404). The EU key check fell through to the generic NotFound and
told the admin to try again, dropping OpenRouter's explanation.

The OpenRouter client now types 400 and 404 on the chat completion, and the
key check refuses those with OpenRouter's message and a pointer to the
account's provider key and data region settings.
…u-routing-errors

# Conflicts:
#	packages/backend/src/services/org/OrgOpenRouterKeyService.test.ts
#	packages/backend/src/services/org/OrgOpenRouterKeyService.ts
#	packages/backend/src/services/org/openrouter-api.ts
@maple-review-bot

maple-review-bot Bot commented Oct 13, 2026 •

Copy link
Copy Markdown

Maple review

🟢 Confidence 9/10 · safe to merge
The new mapping is reached only from the EU chat probe, which the added 404 test pins end to end; no state is written before validation.
quality 100/100 · no findings · tests covered · risk medium

Types OpenRouter's 400/404 on createChatCompletion and maps them in toKeyError, so an EU key that cannot route Maple's model is refused with OpenRouter's own routing reason instead of "try again". Contained and safe to merge. It does not address the open BillingNotConfiguredError issue at callAutumn: the diff leaves the entitlement path untouched.

  • createChatCompletion declares typed BadRequest (400) and NotFound (404), both keeping OpenRouter's message
  • toKeyError turns either into OpenRouterKeyValidationError, refusing the save and showing the routing reason

Production impact

Open errors in the changed files
Issue Service Occurrences File
@maple/http/errors/BillingNotConfiguredError maple-chat 13 packages/backend/src/services/org/OrgOpenRouterKeyService.ts

After this merges, Maple checks whether they stop.

What was checked
  • Only the EU path calls createChatCompletion (OrgOpenRouterKeyService.ts:169), so US saves are unchanged
  • 5xx still becomes UnknownOpenRouterError and then a retryable OpenRouterKeyPersistenceError (OrgOpenRouterKeyService.ts:61)
  • set validates before encrypting or inserting (OrgOpenRouterKeyService.ts:223), so a refused key stores nothing

f229ad0 · Updated on every push. Reply "won't fix" to dismiss a finding, or mention @maple-review-bot to ask about one.

@coderabbitai

coderabbitai Bot commented Oct 13, 2026 •

Copy link
Copy Markdown

Review in Change Stack →

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration
  • Configuration used: defaults
  • Review profile: CHILL
  • Plan: Advanced
  • Run ID: 5c7d0dd0-aa80-4038-af00-6cf376828f0e
📥 Commits

Reviewing files that changed from the base of the PR and between b0a1628 and f229ad0.

📒 Files selected for processing (3)
  • packages/backend/src/services/org/OrgOpenRouterKeyService.test.ts
  • packages/backend/src/services/org/OrgOpenRouterKeyService.ts
  • packages/backend/src/services/org/openrouter-api.ts

Included review availability: This review used your included allowance. Your plan provides up to 4 included reviews per hour; 1 remain after this review.

📝 Walkthrough

The OpenRouter API client now types HTTP 400 and 404 responses. The organization key service maps these errors to EU key validation failures. A test verifies that a “No endpoints found” response prevents the key from being configured.

Changes: EU OpenRouter key validation

Layer / File(s) Summary
Typed OpenRouter errors
packages/backend/src/services/org/openrouter-api.ts
Adds BadRequest and NotFound errors for HTTP 400 and 404 responses. createChatCompletion declares both errors.
Key service validation and test
packages/backend/src/services/org/OrgOpenRouterKeyService.ts, packages/backend/src/services/org/OrgOpenRouterKeyService.test.ts
Maps BadRequest and NotFound to an EU model validation error that includes the upstream message and account-settings guidance. The test verifies that a 404 “No endpoints found” response rejects set and leaves the key unconfigured.

Priority: ➖ Normal

Estimated code review effort: 2 (Simple) | ~10 minutes

Change: Bug fix

Merge Risk: ⚪ Minimal · up to f229a

The EU key-save check is reported to show OpenRouter’s reason and reject the key; no actionable merge risk is identified from the supplied evidence.

Pre-merge checks · Passed 5
Passed Passed checks (5 passed)
Check name Status Explanation
Description Check Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check Passed The title clearly describes the main change: showing OpenRouter’s routing reason when an EU key cannot serve the model.
Docstring Coverage Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check. Docstring coverage is scoped to functions touched by this diff. Analyzed 0 functions across 3…
Linked Issues check Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
📝 Generate docstrings
  • Commit to this branch
  • Create a new PR
🧪 Generate unit tests (beta)
  • Commit to this branch
  • Create a new PR
  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@devin-ai-integration devin-ai-integration Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

✅ Devin Review: No Issues Found

Devin Review analyzed this PR and found no bugs or issues to report.

Devin Review

@Makisuo
Makisuo merged commit 1d9a91b into main Oct 13, 2026
37 checks passed
@Makisuo
Makisuo deleted the fix/openrouter-byok-eu-routing-errors branch October 13, 2026 16:05
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant