{"record":{"id":"d88e497109b96e8c","repo":"n8n-io/n8n","slug":"n8n-api-method-path-failed-res-status","errorCode":null,"errorMessage":"n8n API ${method} ${path} failed (${res.status}): ${text}","messagePattern":"n8n API (.+?) (.+?) failed \\((.+?)\\): (.+?)","errorType":"exception","errorClass":"N8nApiError","httpStatus":null,"severity":"error","filePath":"packages/@n8n/instance-ai/evaluations/clients/n8n-client.ts","lineNumber":1035,"sourceCode":"\t\t// A bare `?? DEFAULT` would turn `timeoutMs: 0` into `AbortSignal.timeout(0)` —\n\t\t// an instant abort, where the old truthiness check meant \"unbounded\". No caller\n\t\t// passes one, and unbounded is what this path exists to remove, so a\n\t\t// non-positive value falls back to the default: bounded either way.\n\t\tconst timeoutMs =\n\t\t\toptions.timeoutMs !== undefined && options.timeoutMs > 0\n\t\t\t\t? options.timeoutMs\n\t\t\t\t: DEFAULT_REQUEST_TIMEOUT_MS;\n\n\t\tconst res = await fetch(`${this.baseUrl}${path}`, {\n\t\t\tmethod,\n\t\t\theaders,\n\t\t\tbody: options.body ? JSON.stringify(options.body) : undefined,\n\t\t\tsignal: AbortSignal.timeout(timeoutMs),\n\t\t});\n\n\t\tif (!res.ok) {\n\t\t\tconst text = await res.text();\n\t\t\tthrow new N8nApiError(\n\t\t\t\t`n8n API ${method} ${path} failed (${res.status}): ${text}`,\n\t\t\t\tres.status,\n\t\t\t);\n\t\t}\n\n\t\t// Capture auth cookie from login response\n\t\tconst setCookie = res.headers.get('set-cookie');\n\t\tif (setCookie) {\n\t\t\tconst match = setCookie.match(/n8n-auth=[^;]+/);\n\t\t\tif (match) {\n\t\t\t\tthis.sessionCookie = match[0];\n\t\t\t}\n\t\t}\n\n\t\treturn await res.json();\n\t}\n}\n","sourceCodeStart":1017,"sourceCodeEnd":1053,"githubUrl":"https://github.com/n8n-io/n8n/blob/5ac6606e81f67bb9534255570cd4e86fd8101eee/packages/@n8n/instance-ai/evaluations/clients/n8n-client.ts#L1017-L1053","documentation":"The internal fetch wrapper throws N8nApiError whenever res.ok is false, embedding method, path, status, and response body. This is the generic surface for any non-2xx from the n8n REST API and the parent class for distinguishable server failures.","triggerScenarios":"Any n8n REST call returning 4xx/5xx: missing/invalid credentials (401/403), not-found (404), validation (400), rate limit (429), or server errors (5xx).","commonSituations":"Expired session causing 401s; referencing a deleted resource (404); sending malformed bodies (400); n8n under load returning 5xx.","solutions":["Read the embedded status and body text to classify the failure (auth vs not-found vs server).","For 401/403, re-login and retry; for 404, verify the resource id; for 5xx, retry with backoff or surface the incident.","Catch N8nApiError specifically and branch on its `.status` field rather than parsing the message."],"exampleFix":"try {\n  await client.fetch('/rest/foo', { method: 'GET' });\n} catch (e) {\n  if (e instanceof N8nApiError && e.status === 401) { await client.login(); /* retry */ }\n  else throw e;\n}","handlingStrategy":"try-catch","validationCode":"// Pre-flight reachability:\nconst healthz = await fetch(`${base}/healthz`).catch(() => null);\nif (!healthz || !healthz.ok) throw new Error('n8n unreachable before call');","typeGuard":"const isN8nApiError = (e: unknown): e is { status: number } =>\n  e instanceof Error && typeof (e as any).status === 'number';","tryCatchPattern":"try { return await client.fetch(path, opts); }\ncatch (e) {\n  if (e instanceof N8nApiError && (e.status === 401 || e.status === 403)) { await client.login(); /* retry */ }\n  else if (e instanceof N8nApiError && e.status >= 500) { /* backoff retry */ }\n  else throw e;\n}","preventionTips":["Branch on N8nApiError.status, not on message substrings.","Re-login on 401/403 before retrying.","Wrap fetch in a retry-with-backoff for 5xx."],"tags":["network","n8n-api","http"],"backgroundTag":null,"analyzedSha":"5ac6606e81f67bb9534255570cd4e86fd8101eee","analyzedAt":"2026-08-12T05:26:35.080Z","schemaVersion":2},"datasetVersion":"2026-08-12T13:17:24.610Z"}