DEV Community

neuralbyte
neuralbyte

Posted on

Firecrawl Pricing: How to Calculate the Real Cost Per Usable Page

TL;DR

  • Do not calculate Firecrawl cost as plan price / included credits unless every credit produces one accepted page.
  • Use total spend / accepted unique pages as the production metric.
  • Log the endpoint, feature flags, credits, HTTP status, retries, duplicate status, and validation outcome for every URL.
  • Firecrawl’s billing documentation distinguishes platform failures from processed target errors: a processed 403 or 404 may still consume credits.
  • Run the calculator in this article against a representative batch before choosing Firecrawl or an alternative.

Firecrawl pricing looks simple when you read the base rate: credits go in, and crawled pages come out.

The model becomes less straightforward as soon as you build a production pipeline.

Some endpoints consume credits per page. Others charge by result block or browser minute. Scrape options can add credits. Monthly plan credits may expire unused. A request can complete successfully while returning content that your application rejects. A target website can return a billable 403 even though it did not produce a useful document.

If you are evaluating a crawler for RAG, monitoring, lead enrichment, or data extraction, you need an accounting model that connects API consumption to accepted output.

This guide builds that model with a small, dependency-free Python script. The Firecrawl billing rules cited here were checked on September 4, 2026. Always verify the current values in Firecrawl’s official billing documentation.

1. Define the Unit You Actually Buy

For most applications, a submitted URL is not the final unit of value.

A RAG pipeline needs a document with enough clean text to chunk and embed. A product feed needs a record that passes its schema. A monitoring system needs a canonical page containing the watched field. An AI agent needs fresh evidence rather than a challenge page.

Define these counters separately:

submitted_urls
provider_completed_pages
accepted_unique_pages
valid_records
Enter fullscreen mode Exit fullscreen mode

The first two are useful operational metrics. The last two are useful financial denominators.

Use one of these equations:

effective_cost_per_page = total_spend / accepted_unique_pages
effective_cost_per_record = total_spend / valid_records
Enter fullscreen mode Exit fullscreen mode

total_spend should include:

  • subscription fees;
  • purchased overflow credits;
  • feature-related charges;
  • separately billed network or proxy usage;
  • retry and reprocessing costs;
  • cleanup required because an output failed validation.

2. Model Firecrawl Credits by Operation

According to Firecrawl’s current documentation, Scrape and Crawl start with a base per-page credit.

Search is charged by result blocks. Interact is charged by browser minute. Agent uses dynamic pricing. Certain Scrape options add credits on top of the base page cost.

The exact values can change, but the model has the following structure:

page_credits = base_page_cost
             + pdf_pages * pdf_modifier
             + json_extraction_modifier
             + prompt_injection_check_modifier
             + zdr_modifier
Enter fullscreen mode Exit fullscreen mode

These modifiers can stack.

They can also affect Crawl and scraped Search results because those operations use page scraping internally. Check the live Firecrawl pricing table for current values.

This immediately exposes a common estimation bug:

100,000 credits != 100,000 usable pages
Enter fullscreen mode Exit fullscreen mode

That equality holds only when:

  1. Every page consumes exactly one credit.
  2. Every processed page passes your acceptance checks.
  3. No credits are used for other endpoints or options.
  4. The entire monthly allowance is used.

If JSON extraction is enabled or the acceptance rate falls below 100%, the number of usable pages changes.

3. Log Enough Data to Reconstruct the Bill

Your job-level events should make cost explainable after a run.

A compact event schema could look like this:

{
  "job_id": "crawl_2026_09_04_001",
  "url": "https://example.com/docs/install",
  "canonical_url": "https://example.com/docs/install",
  "endpoint": "crawl",
  "options": {
    "format": "markdown",
    "zdr": false,
    "pdf_parsing": false,
    "json_extraction": false
  },
  "provider_completed": true,
  "http_status": 200,
  "credits_consumed": 1,
  "retry_count": 0,
  "content_bytes": 18432,
  "quality_status": "accepted",
  "duplicate": false,
  "stored": true
}
Enter fullscreen mode Exit fullscreen mode

Do not collapse every bad outcome into success: false.

Use explicit statuses such as:

provider_failed
processed_http_error
empty_content
blocked_page
duplicate
schema_rejected
storage_failed
accepted
Enter fullscreen mode Exit fullscreen mode

This distinction matters because Firecrawl’s pricing FAQ says failed requests are not charged, while its detailed billing guide says credits are charged when the infrastructure processes a request—even when the target returns a 403 or 404.

The guide also documents a special billing case when prompt-injection checking has already run.

The engineering takeaway is simple:

Determine cost from recorded credit usage, not from a Boolean field named success.

4. Calculate the Real Cost per Accepted Page

The following script reads an exported JSON Lines file. It does not call Firecrawl and requires only the Python standard library.

#!/usr/bin/env python3
"""Calculate crawler cost per accepted page from JSONL job events."""

from __future__ import annotations

import argparse
import json
from collections import Counter
from pathlib import Path


def load_events(path: Path) -> list[dict]:
    events = []

    with path.open(encoding="utf-8") as source:
        for line_number, line in enumerate(source, start=1):
            if not line.strip():
                continue

            try:
                events.append(json.loads(line))
            except json.JSONDecodeError as exc:
                raise ValueError(
                    f"Invalid JSON on line {line_number}: {exc}"
                ) from exc

    return events


def money(value: float) -> str:
    return f"${value:,.6f}"


def main() -> None:
    parser = argparse.ArgumentParser()
    parser.add_argument("events", type=Path)
    parser.add_argument("--subscription", type=float, default=0.0)
    parser.add_argument("--overages", type=float, default=0.0)
    parser.add_argument("--network", type=float, default=0.0)
    parser.add_argument("--other", type=float, default=0.0)
    args = parser.parse_args()

    events = load_events(args.events)

    outcomes = Counter(
        event.get("quality_status", "unknown")
        for event in events
    )

    accepted_urls = {
        event.get("canonical_url") or event["url"]
        for event in events
        if event.get("quality_status") == "accepted"
        and event.get("stored") is True
        and not event.get("duplicate", False)
    }

    total_credits = sum(
        float(event.get("credits_consumed", 0))
        for event in events
    )

    total_spend = (
        args.subscription
        + args.overages
        + args.network
        + args.other
    )

    accepted_count = len(accepted_urls)

    print(f"Submitted events:       {len(events):,}")
    print(f"Credits consumed:       {total_credits:,.2f}")
    print(f"Accepted unique pages:  {accepted_count:,}")
    print(f"Total spend:            {money(total_spend)}")

    print("Outcome breakdown:")
    for status, count in outcomes.most_common():
        print(f"  {status:22} {count:>8,}")

    if accepted_count:
        print(
            f"Cost / accepted page:   "
            f"{money(total_spend / accepted_count)}"
        )
        print(
            f"Credits / accepted page:"
            f"{total_credits / accepted_count:>12.4f}"
        )
    else:
        print(
            "Cost / accepted page:   "
            "undefined (no accepted pages)"
        )


if __name__ == "__main__":
    main()
Enter fullscreen mode Exit fullscreen mode

Run it like this:

python3 firecrawl_cost.py events.jsonl \
  --subscription YOUR_PLAN_SPEND \
  --overages YOUR_EXTRA_CREDIT_SPEND \
  --network YOUR_NETWORK_SPEND \
  --other YOUR_OTHER_PIPELINE_COST
Enter fullscreen mode Exit fullscreen mode

Avoid putting a static plan price in the script.

Feed it the actual invoice amount for the measured period. This keeps the calculation valid when prices, discounts, taxes, or plan tiers change.

Firecrawl also exposes current and historical usage endpoints. The Credit Usage API reference explains how to retrieve remaining team credits.

Store periodic account-level snapshots so you can reconcile them with job-level events.

5. Add Plan Utilization to the Model

Even perfect page-level logs can miss the cost of unused monthly credits.

Calculate:

plan_utilization = credits_consumed / credits_in_plan
Enter fullscreen mode Exit fullscreen mode

If you purchase a monthly allowance and use only 40%, the unused 60% still affects the cost of the accepted data produced that month.

Firecrawl states that plan credits generally reset each billing cycle. Limited rollover is available for certain higher-tier annual arrangements.

This is where workload shape matters:

Workload Subscription fit Main cost risk
Continuous documentation ingestion Usually strong Feature multipliers and quality rejection
Weekly monitoring at stable volume Often strong Duplicate pages and unnecessary checks
One-time migration Often weak Large unused allowance after the job
Seasonal product crawl Variable Idle months and sudden overages
Early-stage AI agent Difficult to predict Endpoint mix and volatile demand

For a steady pipeline, Firecrawl’s integrated features and developer experience may justify the subscription.

For a bursty pipeline, a pay-as-you-go alternative may provide clearer unit economics.

6. Prevent Expensive Retry Loops

Firecrawl’s billing guide says processed HTTP errors can consume credits and recommends checking metadata.statusCode.

Use that signal to classify retryable and terminal outcomes.

A basic policy might look like this:

RETRYABLE = {408, 425, 429, 500, 502, 503, 504}
TERMINAL = {400, 401, 403, 404, 410}


def should_retry(
    status_code: int | None,
    attempt: int,
    max_attempts: int = 3
) -> bool:
    if attempt >= max_attempts:
        return False

    if status_code is None:
        # Network or provider failure:
        # inspect the actual exception.
        return True

    if status_code in TERMINAL:
        return False

    return status_code in RETRYABLE
Enter fullscreen mode Exit fullscreen mode

This is a starting point, not a universal policy.

A 403 may become recoverable after a configuration change, but retrying it immediately ten times is rarely useful.

A production implementation should also include:

  • exponential backoff;
  • jitter;
  • a per-domain circuit breaker;
  • a strict retry budget;
  • a dead-letter queue for manual review.

You should also pass an explicit Crawl limit.

Firecrawl documents a pre-flight credit check against the requested limit. Omitting the parameter can require enough available credits for the default limit, even if the crawl would ultimately discover fewer pages.

7. Benchmark Firecrawl Against an Alternative

A fair benchmark requires more than using the same number of URLs.

Create a frozen sample containing several cohorts:

  • static documentation pages;
  • JavaScript-heavy application pages;
  • PDFs;
  • pages with known blocking or regional behavior;
  • duplicate and canonical URL variants;
  • pages that must produce valid structured fields.

Keep the following variables constant:

  • output format;
  • rendering and wait behavior;
  • retry ceiling;
  • crawl depth and scope;
  • concurrency target;
  • content acceptance tests;
  • canonicalization and deduplication;
  • measurement window.

Then compare:

  • cost per accepted page;
  • cost per valid record;
  • p50 and p95 latency;
  • throughput;
  • retry amplification;
  • engineering time.

If you want to compare a different billing model, Nstproxy Crawl pricing documents pay-as-you-go usage from an account balance as well as optional subscriptions.

Proxy traffic is billed separately, so include it in --network.

The articles Nstdata vs Firecrawl and Best Firecrawl Alternatives provide additional feature context. The Firecrawl Scrape endpoint guide covers implementation details.

Do not assume that an alternative wins because its nominal URL rate is lower.

Run both providers through the same acceptance function. A cheaper successful request has no economic advantage if it produces more rejected data.

8. Build a Production Cost Dashboard

At minimum, chart the following metrics by date, domain, endpoint, and option set:

submitted URLs
processed pages
credits consumed
accepted unique pages
credits per accepted page
cost per accepted page
HTTP error rate
quality rejection rate
duplicate rate
retry amplification
p95 completion time
Enter fullscreen mode Exit fullscreen mode

retry_amplification is especially useful:

retry_amplification = total_attempts / unique_submitted_urls
Enter fullscreen mode Exit fullscreen mode

When this number rises, cost and latency usually rise together.

Break it down by domain to identify targets that need a different strategy.

You should also alert on sudden changes in credits_per_accepted_page. This single metric can reveal several problems:

  • an expensive option was enabled accidentally;
  • a new source contains many PDFs;
  • extraction quality has declined;
  • a domain has started blocking requests;
  • a retry loop is consuming credits;
  • duplicate detection has stopped working.

Final Take

Firecrawl pricing is best understood as a programmable cost system, not a fixed price per URL.

The API provides valuable high-level capabilities, but the economic result depends on:

  • which capabilities you call;
  • how many credits they consume;
  • how completely you use the plan;
  • how many outputs your application accepts.

Instrument those variables, and Firecrawl cost becomes measurable.

Ignore them, and a neat pricing-table calculation can be wrong by several multiples.

The implementation is straightforward:

  1. Log each operation.
  2. Reconcile actual credits.
  3. Count accepted unique outputs.
  4. Measure retry amplification.
  5. Benchmark alternatives with the same quality contract.

FAQ

What is the correct formula for Firecrawl cost per page?

Use total crawler-related spend divided by accepted unique pages. Include the subscription, overflow credits, relevant add-ons, network costs, and retries in the numerator. Count only deduplicated outputs that passed your quality checks in the denominator.

Is one Firecrawl credit always one page?

No. Base Scrape and Crawl operations may start at one credit per page, but other endpoints and options follow different rules. JSON extraction, PDF parsing, zero-data retention, search, and browser interaction can change credit consumption.

Are Firecrawl 403 and 404 responses free?

Do not assume so. Firecrawl’s detailed billing guide says requests processed by its infrastructure can consume credits even when the target returns 403 or 404. Record metadata.statusCode and avoid blind retries.

How can I track Firecrawl credits programmatically?

Use Firecrawl’s Credit Usage and Historical Credit Usage endpoints for account-level data. Reconcile those values with your own job-level event logs.

When should I consider a pay-as-you-go alternative?

Consider a pay-as-you-go alternative when crawl volume is irregular, plan utilization is low, or you are still discovering the workload.

A subscription may still be better for steady volume or when bundled capabilities reduce meaningful engineering costs. Measure both models using the same dataset and acceptance rules.

Top comments (0)