firecrawl/firecrawl · warning · FirecrawlError

Unexpected error occurred while trying to ${action}. Status

Error message

Unexpected error occurred while trying to ${action}. Status code: ${response.status}

What it means

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.

Source

Thrown at apps/js-sdk/firecrawl/src/index.backup.ts:1595

  handleError(response: AxiosResponse, action: string): void {
    if (!response) {
      throw new FirecrawlError(
        `No response received while trying to ${action}. This may be a network error or the server is unreachable.`,
        0
      );
    }

    if ([400, 402, 403, 408, 409, 500].includes(response.status)) {
      const errorMessage: string =
        response.data.error || "Unknown error occurred";
      const details = response.data.details ? ` - ${JSON.stringify(response.data.details)}` : '';
      throw new FirecrawlError(
        `Failed to ${action}. Status code: ${response.status}. Error: ${errorMessage}${details}`,
        response.status,
        response?.data?.details
      );
    } else {
      throw new FirecrawlError(
        `Unexpected error occurred while trying to ${action}. Status code: ${response.status}`,
        response.status
      );
    }
  }

  /**
   * Initiates a deep research operation on a given query and polls until completion.
   * @param query - The query to research.
   * @param params - Parameters for the deep research operation.
   * @param onActivity - Optional callback to receive activity updates in real-time.
   * @param onSource - Optional callback to receive source updates in real-time.
   * @returns The final research results.
   */
  async deepResearch(
    query: string, 
    params: DeepResearchParams<zt.ZodSchema>,
    onActivity?: (activity: {

View on GitHub (pinned to 656bffcc28)

Solutions

  1. Treat 429 specifically: implement exponential backoff with jitter and respect Retry-After if present — this status is the most common resident of this branch.
  2. For 502/503/504, retry a bounded number of times then surface a transient-failure error to the user.
  3. 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.
  4. If self-hosted and you see 404/405, confirm the deep-research or llms-txt route is enabled on your build.
  5. Upgrade the SDK: newer versions special-case more statuses and surface server error text for these cases.

Example fix

// before
try { await app.deepResearch(q); }
catch (e) { throw new Error(e.message); } // 'Unexpected error ... 429' is opaque

// after
try { await app.deepResearch(q); }
catch (e) {
  if (e instanceof FirecrawlError && e.statusCode === 429) {
    await sleep(retryAfterMs(e) ?? backoffMs(attempt));
    return app.deepResearch(q);
  }
  throw e;
}
Defensive patterns

Strategy: retry

Validate before calling

function parseRetryAfter(e): number | null {
  // axios errors do not surface Retry-After by default; reserve this for 429/503 you observe
  if (e?.statusCode === 429 || e?.statusCode === 503) return 2000; // default backoff
  return null;
}

Type guard

function isTransientUnhandledStatus(e: unknown): boolean {
  return e instanceof Error && [401, 404, 405, 429, 502, 503, 504].includes((e as any).statusCode ?? -1);
}
function isRateLimited(e: unknown): boolean {
  return e instanceof Error && (e as any).statusCode === 429;
}

Try / catch

for (let attempt = 0; attempt < 4; attempt++) {
  try { return await app.deepResearch(query, params); }
  catch (e) {
    if (!(e instanceof Error) || !isTransientUnhandledStatus(e) || attempt === 3) throw e;
    await new Promise(r => setTimeout(r, (parseRetryAfter(e) ?? 500) * 2 ** attempt));
  }
}

Prevention

When it happens

Trigger: 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`.

Common situations: 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.

Related errors


AI-assisted analysis of firecrawl/firecrawl@656bffcc28 (2026-08-12). Data as JSON: /api/errors/fdedefc38ac5ced7. Report an issue: GitHub.