janhq/jan · error · Error

Failed to fetch models from ${provider.provider}: ${response

Error message

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

What it means

Thrown by getModels when the provider's GET {base_url}/models returns a non-OK HTTP status that is not one of the specifically handled codes (401/403/404). It surfaces the raw status code and status text so the caller can diagnose provider-side problems (5xx outages, 429 rate limits, 400 bad requests, 405 method not allowed, etc.). It is the catch-all for any !response.ok branch the method does not recognize.

Source

Thrown at web-app/src/services/providers/tauri.ts:217

        }

        if (!response.ok) {
          if (response.status === 401) {
            throw new Error(
              `Authentication failed: API key is required or invalid for ${provider.provider}`
            )
          }
          if (response.status === 403) {
            throw new Error(
              `Access forbidden: Check your API key permissions for ${provider.provider}`
            )
          }
          if (response.status === 404) {
            throw new Error(
              `Models endpoint not found for ${provider.provider}. Check the base URL configuration.`
            )
          }
          throw new Error(
            `Failed to fetch models from ${provider.provider}: ${response.status} ${response.statusText}`
          )
        }

        const data = await response.json()

        if (data.data && Array.isArray(data.data)) {
          return data.data
            .map((model: { id: string }) => model.id)
            .filter(Boolean)
        }
        if (Array.isArray(data)) {
          return data
            .filter(Boolean)
            .map((model) =>
              typeof model === 'object' && 'id' in model ? model.id : model
            )
        }

View on GitHub (pinned to fad3f12a14)

Solutions

  1. Read the numeric status embedded in the message: 5xx -> provider is down, retry later / check the provider status page; 429 -> rate limit, reduce frequency or use a key with higher quota; 4xx other than auth -> verify base_url path and that the provider implements /models.
  2. Reproduce with curl: curl -i -H "x-api-key: $KEY" -H "Authorization: Bearer $KEY" ${base_url}/models to see the raw response.
  3. Verify provider configuration (base_url, custom_header entries) in provider settings.
  4. If 429 persists across all keys, back off, rotate keys, or switch providers.

Example fix

// before: every non-ok non-401/403/404 falls into one generic throw
if (!response.ok) {
  if (response.status === 404) { /* ... */ }
  throw new Error(`Failed to fetch models from ${provider.provider}: ${response.status} ${response.statusText}`)
}
// after: branch transient 5xx / 429 for caller retry guidance
if (!response.ok) {
  if (response.status >= 500 || response.status === 429) {
    throw new Error(`${provider.provider} is temporarily unavailable (${response.status}). Retry shortly.`)
  }
  throw new Error(`Failed to fetch models from ${provider.provider}: ${response.status} ${response.statusText}`)
}
Defensive patterns

Strategy: try-catch

Validate before calling

// Pre-flight the endpoint before listing models
async function isModelsEndpointOk(base_url: string, key?: string): Promise<boolean> {
  const headers: Record<string, string> = { 'Content-Type': 'application/json' }
  if (key) { headers['x-api-key'] = key; headers['Authorization'] = `Bearer ${key}` }
  try {
    const res = await fetch(`${base_url}/models`, { method: 'GET', headers })
    return res.ok
  } catch { return false }
}

Type guard

function isNonOkStatus(status: number): boolean {
  return !(status >= 200 && status < 300)
}

Try / catch

try {
  const models = await provider.getModels(providerConfig)
} catch (e) {
  const msg = e instanceof Error ? e.message : ''
  const m = msg.match(/:\s(\d{3})\s/)
  if (m) {
    const status = Number(m[1])
    if (status >= 500 || status === 429) scheduleRetry()
    else showUserError(msg)
  } else throw e
}

Prevention

When it happens

Trigger: The fetchTauri call to ${provider.base_url}/models resolved with response.ok === false AND response.status is not 401, 403, or 404. Concretely: 500/502/503 server errors, 429 on the final key attempt (the continue branch is skipped when ki === keyAttempts.length-1), 400 Bad Request, 405 Method Not Allowed.

Common situations: Provider service is up but erroring (5xx); rate-limited after exhausting the whole API-key chain (429); base_url points at a valid host but wrong path returning 400; provider does not implement an OpenAI-compatible GET /models; temporary provider outage.

Related errors


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