{"record":{"id":"fdedefc38ac5ced7","repo":"firecrawl/firecrawl","slug":"unexpected-error-occurred-while-trying-to-action","errorCode":null,"errorMessage":"Unexpected error occurred while trying to ${action}. Status code: ${response.status}","messagePattern":"Unexpected error occurred while trying to (.+?)\\. Status code: (.+?)","errorType":"exception","errorClass":"FirecrawlError","httpStatus":null,"severity":"warning","filePath":"apps/js-sdk/firecrawl/src/index.backup.ts","lineNumber":1595,"sourceCode":"  handleError(response: AxiosResponse, action: string): void {\n    if (!response) {\n      throw new FirecrawlError(\n        `No response received while trying to ${action}. This may be a network error or the server is unreachable.`,\n        0\n      );\n    }\n\n    if ([400, 402, 403, 408, 409, 500].includes(response.status)) {\n      const errorMessage: string =\n        response.data.error || \"Unknown error occurred\";\n      const details = response.data.details ? ` - ${JSON.stringify(response.data.details)}` : '';\n      throw new FirecrawlError(\n        `Failed to ${action}. Status code: ${response.status}. Error: ${errorMessage}${details}`,\n        response.status,\n        response?.data?.details\n      );\n    } else {\n      throw new FirecrawlError(\n        `Unexpected error occurred while trying to ${action}. Status code: ${response.status}`,\n        response.status\n      );\n    }\n  }\n\n  /**\n   * Initiates a deep research operation on a given query and polls until completion.\n   * @param query - The query to research.\n   * @param params - Parameters for the deep research operation.\n   * @param onActivity - Optional callback to receive activity updates in real-time.\n   * @param onSource - Optional callback to receive source updates in real-time.\n   * @returns The final research results.\n   */\n  async deepResearch(\n    query: string, \n    params: DeepResearchParams<zt.ZodSchema>,\n    onActivity?: (activity: {","sourceCodeStart":1577,"sourceCodeEnd":1613,"githubUrl":"https://github.com/firecrawl/firecrawl/blob/656bffcc2883f1af5befe38766b1ff5f0469993a/apps/js-sdk/firecrawl/src/index.backup.ts#L1577-L1613","documentation":"Fallback branch of handleError() for HTTP statuses NOT in the explicit [400,402,403,408,409,500] list. It only reports the raw status code and discards the server's `error`/`details` fields, making it markedly less actionable than error 180. Notably, 401 (unauthorized) and 429 (rate limit) land here, so an invalid key that the server reports as 401 produces a vague 'Unexpected error ... 401' message.","triggerScenarios":"Response status is anything outside [400,402,403,408,409,500] and not 200: 401 unauthorized, 404 not-found (when not special-cased), 405 method not allowed, 429 too many requests, 502/503/504 gateway errors, or a non-standard 2xx (201/202) reaching the else branch of a method that only checks `status === 200`.","commonSituations":"Hitting Firecrawl rate limits (429) during a bulk crawl; the API gateway being briefly down (502/503); an expired or revoked API key the server reports as 401; pointing the SDK at a wrong apiUrl path that returns 404/405.","solutions":["Treat 429 specifically: implement exponential backoff with jitter and respect Retry-After if present — this status is the most common resident of this branch.","For 502/503/504, retry a bounded number of times then surface a transient-failure error to the user.","If you see 'status code: 401' here, your API key is invalid even though the message doesn't say so — rotate/re-set FIRECRAWL_API_KEY.","If self-hosted and you see 404/405, confirm the deep-research or llms-txt route is enabled on your build.","Upgrade the SDK: newer versions special-case more statuses and surface server error text for these cases."],"exampleFix":"// before\ntry { await app.deepResearch(q); }\ncatch (e) { throw new Error(e.message); } // 'Unexpected error ... 429' is opaque\n\n// after\ntry { await app.deepResearch(q); }\ncatch (e) {\n  if (e instanceof FirecrawlError && e.statusCode === 429) {\n    await sleep(retryAfterMs(e) ?? backoffMs(attempt));\n    return app.deepResearch(q);\n  }\n  throw e;\n}","handlingStrategy":"retry","validationCode":"function parseRetryAfter(e): number | null {\n  // axios errors do not surface Retry-After by default; reserve this for 429/503 you observe\n  if (e?.statusCode === 429 || e?.statusCode === 503) return 2000; // default backoff\n  return null;\n}","typeGuard":"function isTransientUnhandledStatus(e: unknown): boolean {\n  return e instanceof Error && [401, 404, 405, 429, 502, 503, 504].includes((e as any).statusCode ?? -1);\n}\nfunction isRateLimited(e: unknown): boolean {\n  return e instanceof Error && (e as any).statusCode === 429;\n}","tryCatchPattern":"for (let attempt = 0; attempt < 4; attempt++) {\n  try { return await app.deepResearch(query, params); }\n  catch (e) {\n    if (!(e instanceof Error) || !isTransientUnhandledStatus(e) || attempt === 3) throw e;\n    await new Promise(r => setTimeout(r, (parseRetryAfter(e) ?? 500) * 2 ** attempt));\n  }\n}","preventionTips":["Wrap every SDK call in a bounded retry-with-backoff that targets 429/502/503/504 specifically.","Treat any 'Unexpected error ... 401' as an invalid API key even though the message does not say so.","Do not branch on server error text for these statuses — it is absent; branch on statusCode only.","Upgrade the SDK regularly; newer versions special-case more statuses and preserve server text."],"tags":["http","rate-limit","gateway","handleerror","fallback"],"analyzedSha":"656bffcc2883f1af5befe38766b1ff5f0469993a","analyzedAt":"2026-08-12T01:18:00.488Z","schemaVersion":2},"datasetVersion":"2026-08-12T06:17:24.410Z"}