Skip to content

Add externally managed API key safety controls#20

Open
elkimek wants to merge 1 commit into
Routstr:mainfrom
elkimek:agent/add-external-api-key-safety
Open

Add externally managed API key safety controls#20
elkimek wants to merge 1 commit into
Routstr:mainfrom
elkimek:agent/add-external-api-key-safety

Conversation

@elkimek

@elkimek elkimek commented Jul 12, 2026

Copy link
Copy Markdown

Summary

  • add an opt-in apiKeyManagement: "external" policy for applications that already own funded Routstr sessions
  • allow an existing API key to be used without requiring a local Cashu balance or configured mint
  • prevent automatic key creation, top-up, refund, removal, and replacement in the request pipeline when external management is enabled
  • add autoProviderFailover: false for applications that also own provider selection
  • expose both options through RoutstrClient, resolveRequestContext, fetchAIResponse, and routeRequests

Why

Some clients persist and synchronize funded Routstr API-key sessions separately from their device-local Cashu wallets. In that setup, the current apikeys request path has two risky incompatibilities:

  1. it requires a positive local Cashu balance before using an already-funded API key; and
  2. provider errors can trigger SDK-managed key refunds, removal, top-ups, or provider failover.

That can break a valid funded session on a device with an empty local wallet, or move a shared provider balance into one device's wallet unexpectedly.

This PR keeps all existing defaults unchanged. The safe external policy is explicit:

new RoutstrClient(wallet, storage, discovery, "min", "apikeys", {
  apiKeyManagement: "external",
  autoProviderFailover: false,
});

Balance metadata may still be refreshed after a successful request, but the request pipeline never moves funds or changes the externally owned key. Explicit BalanceManager calls remain available to callers.

Validation

  • pnpm exec vitest run tests/unit/externalApiKeyManagement.test.ts — 4 passed
  • pnpm exec vitest run tests/unit --exclude tests/unit/providerManagerPricing.test.ts — 54 passed
  • pnpm run build — ESM, CJS, and declarations built successfully

The complete unit command currently reports four pre-existing failures in providerManagerPricing.test.ts: production uses a 420,000 ms cooldown while those tests expect 42,000 ms. This patch does not touch that code.

@sh1ftred

Copy link
Copy Markdown
Contributor

Thanks for this PR! We appreciate the work, but after reviewing it we're going to take a different approach.

The real issue

The core problem this PR solves — rejecting a funded API key when the local Cashu wallet has zero balance — is a real bug, but it lives in a single function: _checkBalance(). It runs before _spendToken() and throws InsufficientBalanceError if the local wallet is empty, regardless of whether a funded key already exists in storage.

_spendToken() itself is already correct — it checks storageAdapter.getApiKey(baseUrl) and uses an existing key if one is present. The request just never gets there.

The fix we're going with

Skip the wallet balance check in _checkBalance() when an existing API key is already in storage:

private async _checkBalance(baseUrl: string): Promise<void> {
  if (this.mode === "apikeys" && this.storageAdapter.getApiKey(baseUrl)) {
    return; // funded key exists, balance lives on the provider
  }
  // ...existing check
}

PR #24 implements this.

Importing existing keys

Users who already have a funded API key just import it into the SDK storage before making requests — no new config flags or modes needed:

storage.setApiKey("https://provider.example/", "sk-existing-key");
const response = await client.routeRequest({ ... });

The SDK then manages the key normally (refunds, topups, failover all still work), which gives users better recovery behavior than the "external" mode in this PR, since "external" disables all of that.

Why not the external mode

The apiKeyManagement: "external" mode disables all SDK wallet mutations — refunds, topups, failover. That means when a provider is down, the user is on their own. If the user is already embedding the SDK with a wallet, they want the SDK to handle recovery, not an escape hatch.

Closing this in favor of #24. Thanks again for the work!

@sh1ftred

sh1ftred commented Jul 13, 2026

Copy link
Copy Markdown
Contributor

I see that you already have a cashu wallet in there, you should be able to easily plug it into routstr-sdk.
Here's a simple script that shows how you can plug a wallet into routstr-sdk https://github.com/Routstr/routstr-sdk/blob/main/scripts/routstr-cheapest.ts

it plugs a cli wallet to the sdk, but you can plug any wallet very easily. Pls let me know if you face further issues here

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.

2 participants