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
- Verify each API key in Settings > Model Providers is valid and test one directly against the provider.
- Wait out the rate-limit window or add keys with distinct rate-limit pools.
- 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
- Proactively test keys on save in settings.
- Alert when a key returns repeated 401/429 instead of silently rotating.
- Use distinct keys with separate rate-limit pools for rotation.
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
- Authentication failed: API key is required or invalid for
- Failed to fetch models from
- No API key configured for
- Access forbidden: Check your API key permissions for
- Failed to fetch models from
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)