{"record":{"id":"de90d05331bba5b7","repo":"paperclipai/paperclip","slug":"openrouter-returned-an-invalid-model-catalog","errorCode":null,"errorMessage":"OpenRouter returned an invalid model catalog.","messagePattern":"OpenRouter returned an invalid model catalog\\.","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"warning","filePath":"server/src/services/openrouter-models.ts","lineNumber":14,"sourceCode":"import type { AdapterModel } from \"@paperclipai/adapter-utils\";\n\nlet cached: { until: number; models: AdapterModel[] } | undefined;\nlet pending: Promise<AdapterModel[]> | undefined;\n\n/** OpenRouter's public catalog does not require access to anyone's credentials. */\nexport async function listOpenRouterModels(refresh = false): Promise<AdapterModel[]> {\n  if (!refresh && cached && cached.until > Date.now()) return cached.models;\n  if (pending) return pending;\n  pending = (async () => {\n    const response = await fetch(\"https://openrouter.ai/api/v1/models\", { signal: AbortSignal.timeout(10_000) });\n    if (!response.ok) throw new Error(\"Could not load OpenRouter models. Retry or enter a model ID manually.\");\n    const body = await response.json() as { data?: Array<{ id?: unknown; name?: unknown }> };\n    if (!Array.isArray(body.data)) throw new Error(\"OpenRouter returned an invalid model catalog.\");\n    const models = body.data.flatMap(model => typeof model.id === \"string\" && model.id.includes(\"/\")\n      ? [{ id: `openrouter/${model.id}`, label: typeof model.name === \"string\" ? model.name : model.id }]\n      : []).sort((a, b) => a.label.localeCompare(b.label));\n    cached = { until: Date.now() + 60_000, models };\n    return models;\n  })();\n  try { return await pending; } finally { pending = undefined; }\n}\n","sourceCodeStart":1,"sourceCodeEnd":23,"githubUrl":"https://github.com/paperclipai/paperclip/blob/3f1d897a7c018d76563a21c6e39c3c9b03933622/server/src/services/openrouter-models.ts#L1-L23","documentation":"listOpenRouterModels validates the JSON body of the OpenRouter catalog response. If body.data is not an array, it throws 'OpenRouter returned an invalid model catalog.' This means the HTTP call succeeded but the payload shape did not match the expected { data: [...] } envelope.","triggerScenarios":"OpenRouter returns 200 with an unexpected body: an HTML error/captcha page, a proxy or captive-portal response, an API schema change removing/renaming the 'data' field, or a truncated JSON payload.","commonSituations":"Responses from an intercepting proxy (auth portals, TLS inspection blocks); OpenRouter API version change altering the response shape; middleware returning JSON without a 'data' array.","solutions":["Log the raw response body when this occurs to see what was actually returned.","Check https://openrouter.ai/api/v1/models directly for a changed response shape and update parsing if the schema changed.","Bypass any intercepting proxy or fix captive-portal/TLS issues so the genuine OpenRouter response arrives.","Enter a model ID manually while the catalog is unavailable."],"exampleFix":"// before\nif (!Array.isArray(body.data)) throw new Error(\"OpenRouter returned an invalid model catalog.\");\n// after\nif (!Array.isArray(body.data)) {\n  console.error(\"openrouter catalog body:\", JSON.stringify(body).slice(0, 500));\n  throw new Error(\"OpenRouter returned an invalid model catalog.\");\n}","handlingStrategy":"fallback","validationCode":"const body = await res.json();\nif (body && Array.isArray(body.data)) { /* safe to parse */ }","typeGuard":"const isCatalog = (b: unknown): b is { data: Array<{ id?: unknown; name?: unknown }> } =>\n  typeof b === \"object\" && b !== null && Array.isArray((b as { data?: unknown }).data);","tryCatchPattern":"try {\n  models = await listOpenRouterModels();\n} catch (e) {\n  if (e.message.includes(\"invalid model catalog\")) { models = lastKnownGoodModels; }\n  else throw e;\n}","preventionTips":["Persist the last successful catalog so shape failures degrade gracefully.","Log unexpected response bodies to detect proxies or API schema changes.","Pin/monitor the OpenRouter API version in use."],"tags":["network","http","response-validation"],"backgroundTag":"unexpected-response-shape","analyzedSha":"3f1d897a7c018d76563a21c6e39c3c9b03933622","analyzedAt":"2026-09-18T08:03:59.046Z","contentChangedAt":"2026-09-18T08:03:59.046Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}