janhq/jan · error

API key rotation exhausted

Error message

API key rotation exhausted

What it means

Thrown by the multi-key rotation fetch loop after every configured API key has been tried and each attempt returned 401/403/429 (the loop only 'continue's to the next key for those statuses; any other status returns immediately). It means no key in the chain produced an acceptable response and there are no more keys to rotate to.

Solutions

  1. Verify each API key in Settings > Model Providers is valid and test one directly against the provider.
  2. Wait out the rate-limit window or add keys with distinct rate-limit pools.
  3. Remove duplicate/revoked keys from the key chain and re-add a fresh key from the provider dashboard.

Example fix

// before
apiKeys = [oldRevokedKey, oldRevokedKey]
// after
apiKeys = [freshKeyFromDashboard]
Defensive patterns

Strategy: retry

Validate before calling

if (!apiKeys.length) throw new Error('Configure at least one API key before calling the provider')
await Promise.all(apiKeys.map(k => probeKey(k))) // optional preflight

Type guard

const hasUsableKeys = (keys) => Array.isArray(keys) && keys.length > 0 && keys.every(k => typeof k === 'string' && k.length > 0)

Try / catch

try { return await rotatedFetch(url, init) } catch (e) { if (e.message === 'API key rotation exhausted') { notifyUserToRefreshKeys(); } else throw e }

Prevention

When it happens

Trigger: All configured API keys are invalid (401) or revoked (403); all keys hit the provider's rate limit (429) so rotation cannot help; apiKeys array entries resolve to the same broken key.

Common situations: Expired or rotated-out keys left in settings; org-level rate limits shared by every key; free-tier keys throttled simultaneously during a burst; key chain accidentally containing duplicates.

Understand the failure class

Background: "API key is required" / "API key not found" / "No API key was set": the missing-api-key error family across 16 libraries — this error's family across 16 libraries.

Related errors


AI-assisted analysis of janhq/jan@7205d770c1 (2026-09-17). Data as JSON: /api/errors/fcbe7637d042aa4e. Report an issue: GitHub.

Appendix: source

Thrown at web-app/src/lib/model-factory.ts:870

  ): Promise<Response> => {
    for (let i = 0; i < apiKeys.length; i++) {
      const key = apiKeys[i]!
      const nextHeaders = new Headers(init?.headers as HeadersInit | undefined)
      if (headerMode === 'authorization-bearer') {
        nextHeaders.set('Authorization', `Bearer ${key}`)
      } else if (headerMode === 'x-goog-api-key') {
        nextHeaders.set('x-goog-api-key', key)
      } else {
        nextHeaders.set('x-api-key', key)
      }
      const res = await inner(input, { ...init, headers: nextHeaders })
      if ([401, 403, 429].includes(res.status) && i < apiKeys.length - 1) {
        res.body?.cancel().catch(() => {})
        continue
      }
      return res
    }
    throw new Error('API key rotation exhausted')
  }
}

// An empty apiKey still puts an empty auth header on the wire, which upstreams
// answer with misleading 401s (e.g. Anthropic's "x-api-key header is
// required"). Fail here with an actionable message instead.
function requireRemoteApiKey(
  provider: ProviderObject,
  keyChain: string[]
): string {
  const key = keyChain[0] ?? provider.api_key?.trim()
  if (!key) {
    throw new Error(
      `No API key configured for ${provider.provider}. Add one in Settings > Model Providers.`
    )
  }
  return key
}

View on GitHub (pinned to 7205d770c1)