{"record":{"id":"a6d8c0d4c6e02fd6","repo":"janhq/jan","slug":"failed-to-fetch-models-from-provider-provider-a6d8c0","errorCode":null,"errorMessage":"Failed to fetch models from ${provider.provider}: ${lastStatus} ${lastStatusText}","messagePattern":"Failed to fetch models from (.+?): (.+?) (.+?)","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"web-app/src/services/providers/tauri.ts","lineNumber":247,"sourceCode":"        if (Array.isArray(data)) {\n          return data\n            .filter(Boolean)\n            .map((model) =>\n              typeof model === 'object' && 'id' in model ? model.id : model\n            )\n        }\n        if (data.models && Array.isArray(data.models)) {\n          return data.models\n            .map((model: string | { id: string }) =>\n              typeof model === 'string' ? model : model.id\n            )\n            .filter(Boolean)\n        }\n        console.warn('Unexpected response format from provider API:', data)\n        return []\n      }\n\n      throw new Error(\n        `Failed to fetch models from ${provider.provider}: ${lastStatus} ${lastStatusText}`\n      )\n    } catch (error) {\n      console.error('Error fetching models from provider:', error)\n\n      // Preserve structured error messages thrown above\n      const structuredErrorPrefixes = [\n        'Authentication failed',\n        'Access forbidden',\n        'Models endpoint not found',\n        'Failed to fetch models from',\n      ]\n\n      if (\n        error instanceof Error &&\n        structuredErrorPrefixes.some((prefix) =>\n          (error as Error).message.startsWith(prefix)\n        )","sourceCodeStart":229,"sourceCodeEnd":265,"githubUrl":"https://github.com/janhq/jan/blob/7205d770c1e097c3daf35a911176410e93bc5564/web-app/src/services/providers/tauri.ts#L229-L265","documentation":"Thrown after the key-attempt loop is exhausted: every alternative key returned a retryable status (401/403/429) so the function reports the last observed status/statusText. It signals that no configured credential could successfully list models on this provider.","triggerScenarios":"Multiple key attempts configured (e.g. old and new keys) and every attempt failed with 401/403/429; the loop ends and throws with lastStatus/lastStatusText from the final attempt.","commonSituations":"Both the previous and rotated API keys are expired/revoked; all keys hit the provider's rate limit; keys belong to accounts without access to this endpoint.","solutions":["Replace the stored keys with a freshly generated, valid API key for the provider.","Check the last status in the message: 429 means rate limited — wait or raise quota; 401/403 means re-authenticate.","Remove stale keys from settings so only one valid key is tried.","Confirm account billing/plan status on the provider dashboard."],"exampleFix":"// before: keys = [expiredKey, alsoExpiredKey]\n// after: rotate to a working key in provider settings\nprovider.api_key = 'sk-<newly-generated-key>'","handlingStrategy":"try-catch","validationCode":"// Ensure at least one plausibly-valid key is configured before attempting\nif (!provider.api_key?.trim()) throw new Error('No API key configured for ' + provider.provider)","typeGuard":null,"tryCatchPattern":"try {\n  await fetchModelsFromProvider(provider)\n} catch (e) {\n  if (e instanceof Error && /Failed to fetch models from .* \\d{3}/.test(e.message)) {\n    openKeyRotationFlow(provider.provider) // all key attempts exhausted\n  }\n}","preventionTips":["Keep only current, valid keys configured; delete stale ones.","Rotate keys before expiry and verify each new key with curl.","Track rate limits so all keys are not consumed simultaneously."],"tags":["api-key","http-error","retry-exhausted","authentication"],"backgroundTag":"api-error-response","analyzedSha":"7205d770c1e097c3daf35a911176410e93bc5564","analyzedAt":"2026-09-17T14:27:30.100Z","contentChangedAt":"2026-09-17T14:27:30.100Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}