Skip to main content

Documentation Index

Fetch the complete documentation index at: https://docs.firecrawl.dev/llms.txt

Use this file to discover all available pages before exploring further.

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.
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 so we can document it.

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.
{
  "success": false,
  "error": "Unauthorized: Invalid token",
  "details": "Optional structured details (only present on some errors)"
}
FieldTypeDescription
successbooleanAlways false on errors.
errorstringHuman-readable error message. Use this to look up the row below.
detailsanyOptional. Structured per-field validation errors when applicable.

Errors

HTTPerror (typical message)CauseRemedyRetryable
400Bad Request / validation messageRequest body failed schema validation (missing or invalid fields).Fix the request payload using the endpoint reference. Check details for fields.No
400Invalid URLThe url field is missing, malformed, or uses an unsupported scheme.Pass an absolute http(s):// URL.No
401Unauthorized: Invalid tokenAPI key is missing, malformed, or revoked.Send Authorization: Bearer fc-... with a valid key from the dashboard.No
402Payment Required: Insufficient creditsPlan credits are exhausted or billing is not configured.Top up credits, enable auto-recharge, or upgrade your plan.No
403ForbiddenKey lacks permission for this endpoint or feature.Use a key with the required scope, or upgrade the plan that gates this feature.No
404Not FoundThe job ID, resource, or endpoint path does not exist.Verify the resource ID and endpoint URL.No
408Request TimeoutThe page took longer than the request timeout to load.Increase timeout, simplify actions, or use fastMode.Yes, with backoff
409ConflictResource is in a state that prevents the operation (e.g. already deleted).Re-fetch state and reconcile before retrying.No
413Payload Too LargeRequest body exceeded the maximum allowed size.Reduce the payload (e.g. shorter schema, fewer URLs per batch).No
422Unprocessable Entity / extraction schema errorSchema is invalid JSON Schema, or the model could not produce a conforming result.Validate the schema; loosen required fields; try a different model.Sometimes
429Rate limit exceededToo many requests for your plan’s per-minute limit.Back off and retry after Retry-After seconds. See Rate Limits.Yes, with backoff
429Concurrency limit reachedConcurrent browser limit for your plan reached.Wait for in-flight jobs to finish, lower concurrency, or upgrade your plan.Yes, with backoff
500Internal Server ErrorUnhandled server-side failure.Retry with exponential backoff. If it persists, contact support with the request ID.Yes, with backoff
502Bad GatewayUpstream proxy or worker returned an invalid response.Retry with backoff.Yes, with backoff
503Service UnavailableService temporarily unable to handle the request.Retry with backoff.Yes, with backoff
504Gateway TimeoutRequest 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

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

Rate limits

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