# Errors

> Every API error code, what causes it, how to remedy it, and whether to retry.

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

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

| 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.           | Turn on pay-as-you-go, 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               |
| 403  | `SCRAPE_PROMPT_INJECTION_DETECTED`                 | JSON mode with `checkPromptInjection: true` detected a prompt injection attempt in the scraped page content, so extraction was aborted. | Inspect the page content manually. If it is a false positive, retry without `checkPromptInjection`. See [Prompt injection detection](/features/llm-extract#prompt-injection-detection). | 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.

## Agent

Errors specific to [`/agent`](/features/agent) and its status, trace, snapshot, and cancel endpoints. The trace and snapshot endpoints relay their upstream error body unchanged, so those two can answer with a body that omits the `success` field described above; match on the HTTP status and the `error` string.

| HTTP | `error` (typical message)                          | Cause                                                              | Remedy                                                                              | Retryable        |
|------|----------------------------------------------------|--------------------------------------------------------------------|-------------------------------------------------------------------------------------|------------------|
| 400  | `Invalid job ID format. Job ID must be a valid UUID.` | The `jobId` path segment is not a UUID.                            | Pass the `id` returned by `POST /v2/agent`.                                         | No               |
| 400  | `Invalid snapshot ID`                              | The `snapshotId` path segment is malformed.                        | Use a `snapshotId` taken from an `artifact.updated` [trace event](/api-reference/endpoint/agent-trace). | No     |
| 400  | `Trace is only available for Spark 2 extracts`     | The job predates Spark 2, which is what records traces.            | Nothing to do for that job. Every new run executes on `spark-2` and has a trace.    | No               |
| 400  | `Snapshots are only available for Spark 2 extracts` | The job predates Spark 2, which is what records snapshots.         | Nothing to do for that job. Every new run executes on `spark-2` and has snapshots.  | No               |
| 400  | `Your team has zero data retention enabled. This is not supported on extract.` | Agent runs cannot be served for a team with zero data retention forced on. | Contact [support@firecrawl.com](mailto:support@firecrawl.com) to unblock the feature for your team. | No |
| 404  | `Agent job not found`                              | The job ID does not exist, or belongs to another team.             | Check the job ID, and use a key from the team that started the run.                 | No               |
| 404  | `Snapshot not found`                               | No snapshot with that ID belongs to this job.                      | Re-fetch the trace and use a current `snapshotId` from an `artifact.updated` [trace event](/api-reference/endpoint/agent-trace). | No |
| 409  | `Agent already finished`                           | Cancel was called on a run that had already reached a terminal state. | Poll `GET /v2/agent/{jobId}` for the outcome instead.                               | No               |
| 409  | `Agent is already cancelled`                       | Cancel was called on a run that was already cancelling.            | Poll `GET /v2/agent/{jobId}`. A cancelled run reports `failed` with a cancellation message.  | No       |
| 500  | `Failed to passthrough agent request.`             | The agent service rejected the job at submission.                  | Retry with backoff. If it persists, contact support with the request ID.            | Yes, with backoff |

A run that hits its `maxCredits` limit does not return an HTTP error. It finishes as a failed job. Poll the status endpoint and you get `status: "failed"` with a credit-limit error message, no `data`, and `creditsUsed: 0`, since failed runs are not billed. In the trace, the same outcome appears as a `run.finished` event with `outcome: "credit_limit_reached"`.

### Trace error codes

Terminal and `error.occurred` trace events carry a structured `error` object whose `code` is one of five values. Each also carries a `retryable` boolean, which you should treat the same way as the **Retryable** column above.

| `code` | Meaning and remedy |
|--------|--------------------|
| `cancelled` | You cancelled the run. Start a new one when you want the work redone. |
| `credit_limit_reached` | The run hit its `maxCredits` ceiling. Raise `maxCredits`, or narrow the prompt so the run needs less work. |
| `parent_finished` | A subagent stopped because the agent that spawned it finished first. Read the parent agent's own terminal event for the real cause. |
| `refused` | The agent declined the task. Rephrase the prompt, or scope it to URLs you're authorized to collect from. |
| `internal` | An unexpected failure inside the run. Retry the run; if it persists, contact support with the job ID. |

## 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>

```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
```

```js Node
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));
  }
}
```

```bash cURL
# 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"
```

</CodeGroup>

## 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.
