janhq/jan · error · Error

Failed to fetch models from ${provider.provider}: ${result.s

Error message

Failed to fetch models from ${provider.provider}: ${result.status} ${result.statusText}

What it means

Thrown inside the fetchTopRemoteModels key-rotation loop when a GET to `${provider.base_url}/models` returns non-OK and either the status is not in [401,403,429] or this is the final key attempt (remoteModelCatalog.ts:223). This is the in-loop terminal error carrying the exact failing status/statusText.

Source

Thrown at web-app/src/lib/remoteModelCatalog.ts:223

    throw new Error('Provider must have base_url configured')
  }

  const keyChain = providerRemoteApiKeyChain(provider)
  const attempts: (string | undefined)[] = keyChain.length > 0 ? keyChain : [undefined]

  let lastStatus = 0
  let lastStatusText = ''
  for (let i = 0; i < attempts.length; i++) {
    const result = await getJson(
      fetchImpl,
      `${provider.base_url}/models`,
      buildHeaders(provider, attempts[i])
    )
    lastStatus = result.status
    lastStatusText = result.statusText
    if (!result.ok) {
      if ([401, 403, 429].includes(result.status) && i < attempts.length - 1) continue
      throw new Error(`Failed to fetch models from ${provider.provider}: ${result.status} ${result.statusText}`)
    }

    const body = result.body as { data?: unknown }
    const rows = Array.isArray(body?.data) ? (body.data as unknown[]) : []
    return normalizeCatalog(kind, rows)
  }

  throw new Error(`Failed to fetch models from ${provider.provider}: ${lastStatus} ${lastStatusText}`)
}

function normalizeCatalog(kind: CatalogKind, rows: unknown[]): RemoteCatalogModel[] {
  const inferCaps =
    kind === 'openai'
      ? inferOpenAICapabilities
      : kind === 'gemini'
        ? inferGeminiCapabilities
        : inferAnthropicCapabilities

View on GitHub (pinned to fad3f12a14)

Solutions

  1. Read the status code in the message: 404 → fix base_url (usually should end at /v1, not include /models); 5xx → retry later or check provider status page; 401/403 on last key → refresh API key.
  2. Confirm base_url does not already end with /models — the code appends /models itself.
  3. For 429, reduce catalog refresh frequency or add backoff.
  4. Verify the provider's actual /models path against their API docs (some use /v1/models, some /openai/v1/models).

Example fix

// before
throw new Error(`Failed to fetch models from ${provider.provider}: ${result.status} ${result.statusText}`)
// after: include the response body hint when available
const hint = result.body?.error?.message ?? result.statusText
throw new Error(`Failed to fetch models from ${provider.provider}: ${result.status} ${hint}`)
Defensive patterns

Strategy: try-catch

Validate before calling

function normalizeCatalogBaseUrl(raw: string): string {
  // the code appends /models itself; strip a user-supplied /models suffix
  return raw.replace(/\/models\/?$/, '').replace(/\/$/, '')
}
// before the loop:
provider.base_url = normalizeCatalogBaseUrl(provider.base_url!)

Type guard

function isCatalogOk(r: { ok: boolean; status: number }): boolean {
  return r.ok || ![401, 403, 404, 429, 500, 502, 503].includes(r.status)
}

Try / catch

try {
  return await fetchTopRemoteModels(provider, fetchImpl)
} catch (e) {
  if (e instanceof Error && /Failed to fetch models/.test(e.message)) {
    toast.error(e.message)
    return []  // degrade catalog UI instead of crashing
  }
  throw e
}

Prevention

When it happens

Trigger: The /models endpoint returns 404 (wrong base URL path), 500/502/503 (provider outage), 400 (malformed request), or — on the last key of the chain — a 401/403/429 that exhausts rotation.

Common situations: User set base_url with a trailing path that already includes /models so the request hits /models/models (404); provider deprecated the v1 endpoint; transient 502 from a load balancer; the provider requires a different auth scheme so every key 401s.

Related errors


AI-assisted analysis of janhq/jan@fad3f12a14 (2026-08-12). Data as JSON: /api/errors/426298b62b10ed9f. Report an issue: GitHub.