# Errors (/api-reference/errors)

<!-- agent-signals: reading_time_min: 5 · est_tokens: 2453 · updated: 2026-07-30 -->
Related: [Introduction](/api-reference/v2-introduction.md), [Search](/api-reference/endpoint/search.md)

Every Firecrawl error response uses the same JSON shape. Look up the `error` value (or HTTP status) in the table below to find the cause, remedy, and whether the request is safe to retry.

<Note>
  This catalog covers the errors most agents and clients will encounter. It is non-exhaustive — if you receive an error not listed here, please 

  [open an issue](https://github.com/firecrawl/firecrawl/issues)

   so we can document it.
</Note>

## Error response shape [#error-response-shape]

All non-2xx responses return JSON with a top-level `success: false` and a string `error`. Some endpoints include additional fields (`details`, `code`) when more context is available.

```json
{
  "success": false,
  "error": "Unauthorized: Invalid token",
  "details": "Optional structured details (only present on some errors)"
}
```

| Field     | Type      | Description                                                       |
| --------- | --------- | ----------------------------------------------------------------- |
| `success` | `boolean` | Always `false` on errors.                                         |
| `error`   | `string`  | Human-readable error message. Use this to look up the row below.  |
| `details` | `any`     | Optional. Structured per-field validation errors when applicable. |

## Errors [#errors]

| HTTP | `error` (typical message)                        | Cause                                                                              | Remedy                                                                                                             | Retryable         |
| ---- | ------------------------------------------------ | ---------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------ | ----------------- |
| 400  | `Bad Request` / validation message               | Request body failed schema validation (missing or invalid fields).                 | Fix the request payload using the endpoint reference. Check `details` for fields.                                  | No                |
| 400  | `Invalid URL`                                    | The `url` field is missing, malformed, or uses an unsupported scheme.              | Pass an absolute `http(s)://` URL.                                                                                 | No                |
| 401  | `Unauthorized: Invalid token`                    | API key is missing, malformed, or revoked.                                         | Send `Authorization: Bearer fc-...` with a valid key from the [dashboard](https://www.firecrawl.dev/app/api-keys). | No                |
| 402  | `Payment Required: Insufficient credits`         | Plan credits are exhausted or billing is not configured.                           | Top up credits, enable auto-recharge, or upgrade your plan.                                                        | No                |
| 403  | `Forbidden`                                      | Key lacks permission for this endpoint or feature.                                 | Use a key with the required scope, or upgrade the plan that gates this feature.                                    | No                |
| 404  | `Not Found`                                      | The job ID, resource, or endpoint path does not exist.                             | Verify the resource ID and endpoint URL.                                                                           | No                |
| 408  | `Request Timeout`                                | The page took longer than the request `timeout` to load.                           | Increase `timeout`, simplify actions, or use `fastMode`.                                                           | Yes, with backoff |
| 409  | `Conflict`                                       | Resource is in a state that prevents the operation (e.g. already deleted).         | Re-fetch state and reconcile before retrying.                                                                      | No                |
| 413  | `Payload Too Large`                              | Request body exceeded the maximum allowed size.                                    | Reduce the payload (e.g. shorter schema, fewer URLs per batch).                                                    | No                |
| 422  | `Unprocessable Entity` / extraction schema error | Schema is invalid JSON Schema, or the model could not produce a conforming result. | Validate the schema; loosen required fields; try a different `model`.                                              | Sometimes         |
| 429  | `Rate limit exceeded`                            | Too many requests for your plan's per-minute limit.                                | Back off and retry after `Retry-After` seconds. See [Rate Limits](/rate-limits).                                   | Yes, with backoff |
| 429  | `Concurrency limit reached`                      | Concurrent browser limit for your plan reached.                                    | Wait for in-flight jobs to finish, lower concurrency, or upgrade your plan.                                        | Yes, with backoff |
| 500  | `Internal Server Error`                          | Unhandled server-side failure.                                                     | Retry with exponential backoff. If it persists, contact support with the request ID.                               | Yes, with backoff |
| 502  | `Bad Gateway`                                    | Upstream proxy or worker returned an invalid response.                             | Retry with backoff.                                                                                                | Yes, with backoff |
| 503  | `Service Unavailable`                            | Service temporarily unable to handle the request.                                  | Retry with backoff.                                                                                                | Yes, with backoff |
| 504  | `Gateway Timeout`                                | Request exceeded the gateway's timeout (typically long crawls).                    | Use the async crawl/batch endpoints and poll status instead.                                                       | Yes, with backoff |

For 429 responses, Firecrawl includes a `Retry-After` header (in seconds) when available — wait at least that long before retrying.

## Retry guidance [#retry-guidance]

Treat the **Retryable** column as authoritative; do not infer from the HTTP status alone. The pattern below uses exponential backoff with jitter and respects `Retry-After` on 429.

<CodeGroup>
  <CodeBlockTabs defaultValue="Python" groupId="curl+node+python">
    <CodeBlockTabsList>
      <CodeBlockTabsTrigger value="Python">
        Python
      </CodeBlockTabsTrigger>

      <CodeBlockTabsTrigger value="Node">
        Node
      </CodeBlockTabsTrigger>

      <CodeBlockTabsTrigger value="cURL">
        cURL
      </CodeBlockTabsTrigger>
    </CodeBlockTabsList>

    <CodeBlockTab value="Python">
      ```python  
      import time
      import random
      import requests

      RETRYABLE_STATUSES = {408, 429, 500, 502, 503, 504}

      def request_with_retry(method, url, headers=None, json=None, max_attempts=5):
          for attempt in range(max_attempts):
              resp = requests.request(method, url, headers=headers, json=json)
              if resp.status_code < 400 or resp.status_code not in RETRYABLE_STATUSES:
                  return resp
              # Honor Retry-After when present, otherwise exponential backoff with jitter.
              retry_after = resp.headers.get("Retry-After")
              delay = float(retry_after) if retry_after else min(2 ** attempt, 30) + random.random()
              time.sleep(delay)
          return resp
      ```
    </CodeBlockTab>

    <CodeBlockTab value="Node">
      ```js  
      const RETRYABLE_STATUSES = new Set([408, 429, 500, 502, 503, 504]);

      async function requestWithRetry(url, init = {}, maxAttempts = 5) {
        for (let attempt = 0; attempt < maxAttempts; attempt++) {
          const resp = await fetch(url, init);
          if (resp.ok || !RETRYABLE_STATUSES.has(resp.status)) return resp;
          // Honor Retry-After when present, otherwise exponential backoff with jitter.
          const retryAfter = resp.headers.get('Retry-After');
          const delayMs = retryAfter
            ? Number(retryAfter) * 1000
            : Math.min(2 ** attempt, 30) * 1000 + Math.random() * 1000;
          await new Promise((r) => setTimeout(r, delayMs));
        }
      }
      ```
    </CodeBlockTab>

    <CodeBlockTab value="cURL">
      ```bash  
      # Simple shell loop with exponential backoff for retryable statuses.
      attempt=0
      max=5
      until response=$(curl -sS -w "\n%{http_code}" -X POST "https://api.firecrawl.dev/v2/scrape" \
          -H "Authorization: Bearer $FIRECRAWL_API_KEY" \
          -H "Content-Type: application/json" \
          -d '{"url":"https://example.com"}'); do
        status=$(printf '%s' "$response" | tail -n1)
        case "$status" in
          408|429|500|502|503|504)
            attempt=$((attempt+1))
            [ "$attempt" -ge "$max" ] && break
            sleep $((2 ** attempt))
            ;;
          *) break ;;
        esac
      done
      echo "$response"
      ```
    </CodeBlockTab>
  </CodeBlockTabs>
</CodeGroup>

## 429 responses [#429-responses]

429 responses are the most common retryable error. Per-plan rate limits and concurrency limits are documented in [Rate Limits](/rate-limits). Always honor the `Retry-After` header when present rather than retrying immediately.
