{"record":{"id":"a0a370d63f5351c8","repo":"n8n-io/n8n","slug":"invalid-response-format-from-templates-api","errorCode":null,"errorMessage":"Invalid response format from templates API","messagePattern":"Invalid response format from templates API","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"packages/@n8n/ai-workflow-builder.ee/src/tools/web/templates.ts","lineNumber":86,"sourceCode":"}): Promise<TemplateSearchResponse> {\n\tconst queryString = buildSearchQueryString(query);\n\tconst url = `${N8N_API_BASE_URL}/templates/search${queryString ? `?${queryString}` : ''}`;\n\n\tconst response = await fetch(url, {\n\t\tmethod: 'GET',\n\t\theaders: {\n\t\t\t'Content-Type': 'application/json',\n\t\t\tAccept: 'application/json',\n\t\t},\n\t});\n\n\tif (!response.ok) {\n\t\tthrow new Error(`Failed to fetch templates: ${response.status} ${response.statusText}`);\n\t}\n\n\tconst data: unknown = await response.json();\n\tif (!isTemplateSearchResponse(data)) {\n\t\tthrow new Error('Invalid response format from templates API');\n\t}\n\treturn data;\n}\n\n/**\n * Fetch a specific workflow template by ID from n8n API\n */\nexport async function fetchTemplateByID(id: number): Promise<TemplateFetchResponse> {\n\tconst url = `${N8N_API_BASE_URL}/workflows/templates/${id}`;\n\n\tconst response = await fetch(url, {\n\t\tmethod: 'GET',\n\t\theaders: {\n\t\t\t'Content-Type': 'application/json',\n\t\t\tAccept: 'application/json',\n\t\t},\n\t});\n","sourceCodeStart":68,"sourceCodeEnd":104,"githubUrl":"https://github.com/n8n-io/n8n/blob/5ac6606e81f67bb9534255570cd4e86fd8101eee/packages/@n8n/ai-workflow-builder.ee/src/tools/web/templates.ts#L68-L104","documentation":"Thrown by fetchTemplateList after a successful HTTP 200 from https://api.n8n.io/api/templates/search when the JSON body fails the isTemplateSearchResponse type guard. The guard requires the body to be an object containing totalWorkflows:number and workflows:array. A 2xx response carrying an HTML error page, a maintenance banner, or a backwards-incompatible schema change will trip it.","triggerScenarios":"GET https://api.n8n.io/api/templates/search returns 200 with a body lacking totalWorkflows or workflows (e.g. CDN serves an HTML error page, the API ships a schema change, or a caching proxy returns a stale/mismatched payload). The response.ok check at line 80 has already passed, so only shape mismatches reach this throw.","commonSituations":"n8n.cloud or api.n8n.io deploys a templates API schema change; a corporate proxy intercepts and rewrites the JSON; an incident returns a JSON error object instead of the search envelope; local development with a mocked API that forgets the totalWorkflows field.","solutions":["Inspect the actual response body (log response.text() before parsing) to see what api.n8n.io returned.","Verify the endpoint and shape against the current n8n templates API documentation.","If behind a proxy, bypass it and retry to rule out response rewriting.","If you maintain a mock server, ensure it returns { totalWorkflows: number, workflows: [] }."],"exampleFix":"// before\nconst data: unknown = await response.json();\nif (!isTemplateSearchResponse(data)) {\n  throw new Error('Invalid response format from templates API');\n}\n\n// after - surface the body for diagnosis\nconst text = await response.text();\nlet data: unknown;\ntry { data = JSON.parse(text); } catch { throw new Error(`Templates API returned non-JSON body: ${text.slice(0, 200)}`); }\nif (!isTemplateSearchResponse(data)) {\n  throw new Error(`Unexpected templates API shape: ${JSON.stringify(Object.keys(data ?? {}))}`);\n}","handlingStrategy":"type-guard","validationCode":"// Validate before trusting the API shape — re-use the same predicate\nimport { isTemplateSearchResponse } from '@/tools/web/templates';\n\nfunction assertSearchResponse(raw: unknown): TemplateSearchResponse {\n  if (!isTemplateSearchResponse(raw)) {\n    throw new Error(`Unexpected templates API shape: ${JSON.stringify(Object.keys((raw as any) ?? {}))}`);\n  }\n  return raw;\n}","typeGuard":"function isTemplateSearchResponse(data: unknown): data is TemplateSearchResponse {\n  if (typeof data !== 'object' || data === null) return false;\n  const obj = data as Record<string, unknown>;\n  return typeof obj.totalWorkflows === 'number' && Array.isArray(obj.workflows);\n}","tryCatchPattern":"try {\n  const result = await fetchTemplateList(query);\n  // use result\n} catch (e) {\n  if (e instanceof Error && e.message.startsWith('Invalid response format from templates API')) {\n    // degrade gracefully — surface 'templates unavailable' to the caller\n  }\n  throw e;\n}","preventionTips":["Always type-guard upstream API responses before use, even on 200.","Log response.text() on validation failure so schema drift is diagnosable.","Pin templates API integration tests against recorded fixtures to catch contract changes early."],"tags":["api","templates","type-guard","network","ai-workflow-builder"],"backgroundTag":null,"analyzedSha":"5ac6606e81f67bb9534255570cd4e86fd8101eee","analyzedAt":"2026-08-12T05:26:35.080Z","schemaVersion":2},"datasetVersion":"2026-08-12T13:17:24.610Z"}