n8n-io/n8n · error · Error
Failed to fetch provider catalog: ${response.statusText}
Error message
Failed to fetch provider catalog: ${response.statusText} What it means
fetchProviderCatalog GETs https://models.dev/api.json and throws when the response is not ok, embedding response.statusText. Used by Agent/model resolution to look up provider metadata (cost, modalities, reasoning flag) from models.dev.
Source
Thrown at packages/@n8n/agents/src/sdk/catalog.ts:165
*
* Returns a map of provider ID → ProviderInfo with all available models.
* The catalog is fetched once and can be cached by the caller.
*
* @example
* ```typescript
* import { fetchProviderCatalog } from '@n8n/agents';
*
* const catalog = await fetchProviderCatalog();
* console.log(Object.keys(catalog)); // ['anthropic', 'openai', ...]
* console.log(catalog.anthropic.models['claude-sonnet-4-5'].reasoning); // true
* ```
*/
export async function fetchProviderCatalog(options?: {
signal?: AbortSignal;
}): Promise<ProviderCatalog> {
const response = await fetch(MODELS_DEV_URL, { signal: options?.signal });
if (!response.ok) {
throw new Error(`Failed to fetch provider catalog: ${response.statusText}`);
}
const data = modelsDevCatalogSchema.parse(await response.json());
const catalog: ProviderCatalog = {};
for (const [key, rawProvider] of Object.entries(data)) {
const providerResult = modelsDevProviderSchema.safeParse(rawProvider);
if (!providerResult.success) continue;
const provider = providerResult.data;
if (!provider.models || Object.keys(provider.models).length === 0) continue;
const models: Record<string, ModelInfo> = {};
for (const [modelId, rawModel] of Object.entries(provider.models)) {
const modelResult = modelsDevModelSchema.safeParse(rawModel);
if (!modelResult.success) continue;
const model = modelResult.data;
// Deprecated models still 404 at call time when the provider retires
// them, so never offer them.View on GitHub (pinned to 5ac6606e81)
Solutions
- Retry with backoff — models.dev outages are usually transient.
- Allow-list https://models.dev in your proxy/firewall and verify with curl.
- Pass an AbortSignal with a sane timeout and handle failure by falling back to a cached catalog.
- Provide your own fetch implementation via options.fetch that points to a mirrored catalog.
Example fix
// before
const catalog = await fetchProviderCatalog();
// after
let catalog;
try {
catalog = await fetchProviderCatalog({ signal: AbortSignal.timeout(8000) });
} catch (e) {
catalog = await loadCachedCatalog(); // graceful fallback
} Defensive patterns
Strategy: retry
Validate before calling
async function fetchCatalogSafe() {
const response = await fetch('https://models.dev/api.json', { signal: AbortSignal.timeout(8000) });
if (!response.ok) throw new Error(`preflight status ${response.status}`);
return response;
} Try / catch
let catalog;
try {
catalog = await fetchProviderCatalog({ signal: AbortSignal.timeout(8000) });
} catch (e) {
catalog = loadCachedCatalog();
if (!catalog) throw e;
} Prevention
- Cache the catalog locally after first successful fetch.
- Allow-list models.dev in corporate proxies; verify reachability during onboarding.
- Provide options.fetch with retry/timeout for production telemetry paths.
When it happens
Trigger: Any non-2xx response from models.dev: 4xx/5xx, proxy auth required (407), rate limited (429), or service outage (503). Also triggered when a transparent proxy returns an error status for the catalog host.
Common situations: Corporate egress proxy blocks or rewrites models.dev; air-gapped environments; models.dev temporarily down; captive portal intercepting HTTPS; custom fetch override returning an error status.
Related errors
- Supabase upsert failed: ${error.message}
- Supabase query failed: ${error.message}
- Supabase delete failed: ${error.message}
- Failed to list ${provider} models (status ${response.status}
- Webhook request failed: ${response.status} ${response.status
AI-assisted analysis of n8n-io/n8n@5ac6606e81 (2026-08-12).
Data as JSON: /api/errors/3ae4a604323a3d5d.
Report an issue: GitHub.