{"record":{"id":"426298b62b10ed9f","repo":"janhq/jan","slug":"failed-to-fetch-models-from-provider-provider","errorCode":null,"errorMessage":"Failed to fetch models from ${provider.provider}: ${result.status} ${result.statusText}","messagePattern":"Failed to fetch models from (.+?): (.+?) (.+?)","errorType":"http","errorClass":null,"httpStatus":null,"severity":"error","filePath":"web-app/src/lib/remoteModelCatalog.ts","lineNumber":223,"sourceCode":"    throw new Error('Provider must have base_url configured')\n  }\n\n  const keyChain = providerRemoteApiKeyChain(provider)\n  const attempts: (string | undefined)[] = keyChain.length > 0 ? keyChain : [undefined]\n\n  let lastStatus = 0\n  let lastStatusText = ''\n  for (let i = 0; i < attempts.length; i++) {\n    const result = await getJson(\n      fetchImpl,\n      `${provider.base_url}/models`,\n      buildHeaders(provider, attempts[i])\n    )\n    lastStatus = result.status\n    lastStatusText = result.statusText\n    if (!result.ok) {\n      if ([401, 403, 429].includes(result.status) && i < attempts.length - 1) continue\n      throw new Error(`Failed to fetch models from ${provider.provider}: ${result.status} ${result.statusText}`)\n    }\n\n    const body = result.body as { data?: unknown }\n    const rows = Array.isArray(body?.data) ? (body.data as unknown[]) : []\n    return normalizeCatalog(kind, rows)\n  }\n\n  throw new Error(`Failed to fetch models from ${provider.provider}: ${lastStatus} ${lastStatusText}`)\n}\n\nfunction normalizeCatalog(kind: CatalogKind, rows: unknown[]): RemoteCatalogModel[] {\n  const inferCaps =\n    kind === 'openai'\n      ? inferOpenAICapabilities\n      : kind === 'gemini'\n        ? inferGeminiCapabilities\n        : inferAnthropicCapabilities\n","sourceCodeStart":205,"sourceCodeEnd":241,"githubUrl":"https://github.com/janhq/jan/blob/7205d770c1e097c3daf35a911176410e93bc5564/web-app/src/lib/remoteModelCatalog.ts#L205-L241","documentation":"After exhausting key rotation attempts, fetchTopRemoteModels throws with the provider name and the last HTTP status/statusText when the models-list response was not ok. Statuses 401/403/429 trigger a retry with the next key; any other non-ok status (or being on the last attempt) throws immediately. It wraps the upstream HTTP failure with provider context.","triggerScenarios":"Provider returns 404 (wrong base_url path, missing /v1), 500/502 upstream outage, 400 bad request, or 401/403/429 on the final key attempt; endpoint not actually OpenAI-compatible (no /models route).","commonSituations":"base_url pointing at a server without a models endpoint; provider outage or gateway error; all keys rate-limited simultaneously; wrong path like https://api.openai.com (missing /v1).","solutions":["Check the status code in the message: 404 means fix base_url to include the correct API path (usually /v1).","For 401/403/429, replace or wait out the API keys and retry.","For 5xx, verify provider status/retry with backoff; confirm the endpoint is OpenAI-compatible and serves GET /models."],"exampleFix":"// before\nprovider.base_url = 'https://api.openai.com' // 404: no /models here\n// after\nprovider.base_url = 'https://api.openai.com/v1'","handlingStrategy":"retry","validationCode":"const probe = await fetch(`${provider.base_url.replace(/\\/$/, '')}/models`, { headers: buildHeaders(provider) })\nif (!probe.ok) console.warn('catalog endpoint returned', probe.status)","typeGuard":"const isRetryableStatus = (s) => [401, 403, 429, 500, 502, 503, 504].includes(s)","tryCatchPattern":"try { models = await fetchTopRemoteModels(provider, fetch) } catch (e) { if (/Failed to fetch models from/.test(e.message)) { const status = parseInt(e.message.match(/(\\d{3})/)?.[1] ?? '0', 10); if (isRetryableStatus(status)) await backoffRetry(); else showErrorWithStatusHint(status) } }","preventionTips":["Verify base_url ends with the correct API root (/v1) for OpenAI-compatible servers.","Add backoff retry for 5xx statuses before surfacing errors.","Log statusText plus provider id to speed up triage of upstream outages."],"tags":["http","catalog","provider","api-error"],"backgroundTag":"http-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"}