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

  1. Retry with backoff — models.dev outages are usually transient.
  2. Allow-list https://models.dev in your proxy/firewall and verify with curl.
  3. Pass an AbortSignal with a sane timeout and handle failure by falling back to a cached catalog.
  4. 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

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


AI-assisted analysis of n8n-io/n8n@5ac6606e81 (2026-08-12). Data as JSON: /api/errors/3ae4a604323a3d5d. Report an issue: GitHub.