<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0"><channel><title><![CDATA[Sid Mishra]]></title><description><![CDATA[Sid Mishra]]></description><link>https://sidmishra.hashnode.dev</link><generator>RSS for Node</generator><lastBuildDate>Sat, 31 Oct 2026 23:14:54 GMT</lastBuildDate><atom:link href="https://sidmishra.hashnode.dev/rss.xml" rel="self" type="application/rss+xml"/><language><![CDATA[en]]></language><ttl>60</ttl><item><title><![CDATA[How to keep a RAG knowledge base fresh without re-embedding everything]]></title><description><![CDATA[Hash, compare, delete deliberately: re-embed only the pages that changed, and never let a blocked crawl wipe your index.]]></description><link>https://sidmishra.hashnode.dev/keep-rag-knowledge-base-fresh</link><guid isPermaLink="true">https://sidmishra.hashnode.dev/keep-rag-knowledge-base-fresh</guid><category><![CDATA[RAG ]]></category><category><![CDATA[AI]]></category><category><![CDATA[Python]]></category><category><![CDATA[webscraping ]]></category><dc:creator><![CDATA[Sid Mishra]]></dc:creator><pubDate>Wed, 28 Oct 2026 05:56:35 GMT</pubDate><enclosure url="https://cdn.hashnode.com/uploads/covers/6abe24add74e5af86a8014ee/b0c9ce4a-ff71-4d60-9522-183f754aab89.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Most RAG pipelines I look at are refreshed by brute force: re-crawl the docs site every night, re-chunk everything, re-embed everything, overwrite the index. It works, eventually, but it's wasteful and it hides three bugs a nightly rebuild never fixes.</p>
<p>Below is what actually goes stale, what a full rebuild really costs (less than you'd think in embeddings, more elsewhere), a do-it-yourself content-hash pattern you can drop into any scraper, and how we handle it in MeshArc, the crawler I'm building. The first half needs no sign-up for anything.</p>
<h2>What goes wrong when a knowledge base ages</h2>
<p>A stale chunk does not announce itself. It still sits close to the query in vector space, so retrieval still ranks it highly and the model still quotes it with confidence. Three failure modes are worth separating:</p>
<ol>
<li><strong>Stale chunks.</strong> The page changed; the index holds the old wording. The assistant recommends a deprecated flag or an old price.</li>
<li><strong>Zombie chunks.</strong> The page was deleted, but its vectors were not. Nightly <em>upserts</em> never remove anything, so deleted pages live on indefinitely unless something deletes them on purpose.</li>
<li><strong>Flicker re-embeds.</strong> A cookie banner, a "last updated 3 minutes ago" line or a rotating testimonial makes every page look modified on every run, so a "changed pages only" pipeline quietly turns back into a full rebuild.</li>
</ol>
<p>There is a fourth, nastier one: <strong>the blocked crawl that deletes your index.</strong> If the site rate-limits you halfway through a run and your pipeline treats "not seen this run" as "removed", you wipe half the knowledge base. Any deletion logic needs a guard against partial runs.</p>
<h2>What a full nightly rebuild costs</h2>
<p>Here is the arithmetic for a mid-sized documentation site. The assumptions are ours and stated so you can swap in your own:</p>
<ul>
<li>2,000 pages, averaging 1,200 words each.</li>
<li>Chunks of about 350 words with a 40-word overlap, which gives roughly 4 chunks per page and 8,000 chunks in all.</li>
<li>About 1.3 tokens per English word.</li>
<li>OpenAI <code>text-embedding-3-small</code> at <strong>$0.02 per million input tokens</strong> (OpenAI's model page, checked on 23 October 2026).</li>
</ul>
<p>That is roughly 2.8 million embedded words, or 3.7 million tokens, per full rebuild: <strong>about $0.07</strong>. Thirty nights is about <strong>$2.20 a month</strong>.</p>
<p>So, to be fair to the brute-force approach: with a small embedding model, the embedding bill is not what hurts. A larger model, a bigger corpus or a hosted embedding service with per-request pricing changes that, but the real costs of rebuilding everything are usually elsewhere:</p>
<ul>
<li><strong>Fetching.</strong> 2,000 pages × 30 nights is 60,000 page reads a month, every one of which can be blocked, rate-limited or billed.</li>
<li><strong>Index writes.</strong> 8,000 chunk upserts a night, most of them writing identical vectors over identical vectors. Managed vector databases tend to price writes, and self-hosted ones still pay for them in compaction and I/O.</li>
<li><strong>Correctness.</strong> A rebuild that overwrites by ID still leaves zombies behind, and a rebuild that drops and recreates the index has a window in which the assistant answers from nothing.</li>
</ul>
<p>The fix for all of these is the same: find out what changed, and touch only that.</p>
<h2>The do-it-yourself version: hash, compare, delete deliberately</h2>
<p>You do not need any particular vendor for this. The pattern is:</p>
<ol>
<li>Normalise each page's text (strip boilerplate, collapse whitespace) and hash it.</li>
<li>Compare against the hash you stored last time.</li>
<li>Re-chunk and re-embed only pages whose hash moved; give every chunk an ID that starts with the page URL.</li>
<li>Before adding a changed page's new chunks, delete <em>all</em> of its old ones. The chunk count can shrink, so overwriting by ID is not enough.</li>
<li>Delete pages that have disappeared, but only if the run reached most of the site.</li>
</ol>
<pre><code class="language-python">import hashlib, json, re
from pathlib import Path

STATE = Path("hashes.json")

def normalise(text: str) -&gt; str:
    text = re.sub(r"Last updated[^\n]*", "", text)   # add your own volatile lines
    return re.sub(r"\s+", " ", text).strip()

def plan_refresh(pages: dict[str, str], expected_urls: int):
    """pages: {url: markdown} from this run. Returns what to embed and delete."""
    old = json.loads(STATE.read_text()) if STATE.exists() else {}
    new = {u: hashlib.sha256(normalise(t).encode()).hexdigest() for u, t in pages.items()}

    changed = [u for u, h in new.items() if old.get(u) != h]
    removed = [u for u in old if u not in new]

    # Guard: a run that saw under 90% of the site must not delete anything.
    if len(pages) &lt; 0.9 * expected_urls:
        removed = []
        new = {**old, **new}          # keep unseen pages' hashes for next time

    STATE.write_text(json.dumps(new))
    return changed, removed

# changed -&gt; delete chunks with IDs starting f"{url}#", then embed and add new chunks
# removed -&gt; delete chunks with IDs starting f"{url}#"
</code></pre>
<p>Two honest caveats. First, this still fetches every page every night to compute the hashes; it saves embedding and index writes, not crawling. Second, the <code>normalise</code> function is where the work lives. Every site has its own flicker, and you will be adding regexes for months.</p>
<p>If you are already on LangChain, its indexing API (a record manager plus <code>index()</code> with incremental cleanup) does the hash-and-skip part for documents you hand it. It does not fetch pages or know whether a crawl was partial, so the first and last steps above remain yours.</p>
<h2>Skipping the fetch as well: sitemap <code>lastmod</code></h2>
<p>Many sites publish a <code>lastmod</code> date per URL in their sitemap. If it is trustworthy, you can skip fetching pages that have not changed at all. "If" is doing real work there: plenty of sitemaps stamp every URL with today's date, and some never update it. A sensible incremental crawler checks how many entries carry <code>lastmod</code>, falls back to a full crawl when coverage is poor, and runs a full pass periodically anyway to catch pages whose dates lie.</p>
<h2>How MeshArc does it</h2>
<p>This is the part where we describe our own product, so weigh it accordingly. Everything below is from <a href="https://mesharc.dev/docs/configuration">our docs</a> and <a href="https://mesharc.dev/pricing">pricing page</a>.</p>
<p>A <strong>project</strong> is a site plus its settings, run on a schedule (<code>hourly</code>, <code>daily</code>, <code>weekly</code> or <code>manual</code>). Every run is diffed against the previous one, page by page: added, modified, removed, same, withheld. Boilerplate such as cookie banners, newsletter prompts and share bars is stripped by default, and a modified page with fewer than <code>notify_min_words</code> changed words (default 5) does not count as changed.</p>
<p>With <code>sitemap_incremental</code> on, a scheduled run fetches only pages the sitemap shows as new or with a newer <code>lastmod</code>, and carries the rest forward from the last full run. A full run is forced every <code>sitemap_full_every</code> runs (default 10), whenever fewer than 90% of entries carry <code>lastmod</code>, or when a carried page is over 30 days old. <strong>Carried-forward pages cost 0 credits.</strong> So do refused pages, 404s and 410s.</p>
<p>Removals are only reported when a run reached at least 90% of the site. Below that, missing pages are <em>withheld</em>, not removed, so a blocked crawl cannot empty your index.</p>
<pre><code class="language-bash">pip install mesharc langchain-mesharc
</code></pre>
<pre><code class="language-python">from mesharc import MeshArc

arc = MeshArc()  # reads MESHARC_API_KEY

project = arc.projects.create(
    "https://docs.example.com",
    name="Docs KB",
    schedule="daily",
    config={"include_paths": ["/docs/*"], "sitemap_incremental": True},
)

run = arc.runs.start(project["id"], wait=True)   # first run is the baseline
record = arc.changes(project["id"])
print(record["change"]["counts"])
</code></pre>
<p>Then, after each scheduled run (from the <code>run.finished</code> webhook or a cron of your own), load only what changed and apply it to any LangChain vector store:</p>
<pre><code class="language-python">from langchain_mesharc import MeshArcLoader

def refresh(store, project_id: str) -&gt; None:
    docs = MeshArcLoader(mode="changes", project_id=project_id,
                         include_removed=True).load()
    gone = [d.metadata["url"] for d in docs if d.metadata["change"] == "removed"]
    fresh = [d for d in docs if d.metadata["change"] != "removed"]
    if gone:
        store.delete(ids=gone)
    if fresh:
        store.add_documents(fresh, ids=[d.metadata["url"] for d in fresh])
</code></pre>
<p>That keys one document per URL. If you split pages into chunks before embedding, use chunk IDs that start with the URL and delete a page's old chunks first, exactly as in the DIY version. The <a href="https://mesharc.dev/docs/langchain">LangChain integration docs</a> have the details.</p>
<p>If you would rather not run that code at all, MeshArc can write straight into <strong>Pinecone, Qdrant, Weaviate or pgvector</strong> as a <a href="https://mesharc.dev/docs/connectors-destinations">destination</a>. It splits Markdown into paragraph-aligned chunks of about 350 words with a 40-word overlap, embeds them with your own OpenAI-compatible endpoint and key (we add no markup on embeddings), and by default writes only pages classed as added, modified or removed. Destinations are included on every plan.</p>
<h2>The same docs site, priced</h2>
<p>Back to the 2,000-page site, read daily on our Growth plan ($99 for 500,000 credits, so about $0.20 per 1,000 plain fetches). Assume the pages fetch plainly at 1 credit each, and that around 2% change on a typical day — your site's real churn will differ, which is the point of measuring it.</p>
<table>
<thead>
<tr>
<th>Approach</th>
<th>Page reads a month</th>
<th>Credits</th>
<th>Cost on Growth</th>
</tr>
</thead>
<tbody><tr>
<td>Full re-crawl every night</td>
<td>60,000</td>
<td>60,000</td>
<td>~$11.88</td>
</tr>
<tr>
<td><code>sitemap_incremental</code>, ~40 changed pages a night, plus 3 forced full runs</td>
<td>~1,080 + 6,000</td>
<td>~7,080 (plus sitemap reads at 1 credit per file)</td>
<td>~$1.40</td>
</tr>
</tbody></table>
<p>Embeddings follow the same shape: only the ~40 changed pages a day are re-embedded, instead of all 2,000. Pages that need a real browser cost 4 credits rather than 1, so multiply accordingly; our <a href="https://mesharc.dev/blog/firecrawl-pricing-explained">Firecrawl pricing breakdown</a> works through the per-rung maths in more detail.</p>
<h2>Where this does not fit</h2>
<ul>
<li><strong>One-off corpora.</strong> If you index a site once for a demo, none of this matters. Any scraper will do.</li>
<li><strong>Sites without a usable sitemap.</strong> Incremental fetching needs <code>lastmod</code>. Without it, MeshArc still diffs and still writes only changed pages to your store, but it has to fetch every page to know.</li>
<li><strong>Logged-in content.</strong> Pages behind your own SSO are better pulled from the source system (Confluence, Notion, a CMS export) than scraped.</li>
<li><strong>Self-hosting.</strong> MeshArc is not open source. If you need the crawler on your own hardware, the DIY approach above with an open-source crawler is the honest answer; our <a href="https://mesharc.dev/compare">comparison page</a> lists the trade-offs by vendor.</li>
</ul>
<h2>Try it on your own docs</h2>
<p>The quickest test is to point a project at a site you already index, let it run twice, and look at the change counts: that number, not the page count, is what your pipeline should be embedding each night. The <a href="https://mesharc.dev/use-cases/rag-pipeline">RAG pipeline use case</a> has the overview; if your assistant reads whole sites at once, the <a href="https://mesharc.dev/tools/llms-txt-generator">llms.txt generator</a> is a free alternative to embeddings altogether.</p>
<p><a href="https://mesharc.dev/playground"><strong>Try a page in the playground</strong></a> · <a href="https://mesharc.dev/signup"><strong>Start free — 1,000 credits, no card</strong></a></p>
<hr />
<p><em>Originally published on</em> <a href="https://mesharc.dev/blog/keep-rag-knowledge-base-fresh"><em>mesharc.dev</em></a><em>.</em></p>
]]></content:encoded></item><item><title><![CDATA[Firecrawl Pricing Explained: What 1,000 Pages Really Cost]]></title><description><![CDATA[Firecrawl pricing in dollars per 1,000 pages: plans, the JSON surcharge, 404 billing and credit expiry, worked through with real numbers.]]></description><link>https://sidmishra.hashnode.dev/firecrawl-pricing-explained</link><guid isPermaLink="true">https://sidmishra.hashnode.dev/firecrawl-pricing-explained</guid><category><![CDATA[web scraping]]></category><category><![CDATA[APIs]]></category><category><![CDATA[Python]]></category><category><![CDATA[Web Development]]></category><dc:creator><![CDATA[Sid Mishra]]></dc:creator><pubDate>Tue, 27 Oct 2026 08:41:59 GMT</pubDate><enclosure url="https://cdn.hashnode.com/uploads/covers/6abe24add74e5af86a8014ee/bfcd5102-f79e-4082-a66f-b1c1f0d40662.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>I keep getting asked what Firecrawl actually costs per 1,000 pages, and the honest answer is "it depends" — on your plan, your billing cycle, the output formats you request and how many of your URLs turn out to be 404s. So I went through Firecrawl's pricing page and billing docs line by line and worked it all out in dollars, with a small Python estimator at the end you can point at your own workload.</p>
<p>Every Firecrawl figure below comes from <a href="https://www.firecrawl.dev/pricing">firecrawl.dev/pricing</a> and the <a href="https://docs.firecrawl.dev/billing">Firecrawl billing docs</a>, <strong>checked on 21 October 2026</strong>. Prices change; if you are reading this much later, check them again.</p>
<p>Disclosure: I build MeshArc, which competes with Firecrawl. I have tried to be fair, and I say plainly where Firecrawl is the cheaper choice.</p>
<h2>The plans, converted to dollars per 1,000 credits</h2>
<p>Firecrawl sells monthly credit allowances. Converting each plan to a cost per 1,000 credits is the first step, because a credit is a page only in the simplest case.</p>
<table>
<thead>
<tr>
<th>Plan</th>
<th>Monthly billing</th>
<th>Annual billing (per month)</th>
<th>Credits/month</th>
<th>$ per 1,000 credits (monthly / annual)</th>
</tr>
</thead>
<tbody><tr>
<td>Free</td>
<td>$0</td>
<td>—</td>
<td>1,000</td>
<td>—</td>
</tr>
<tr>
<td>Hobby</td>
<td>$19</td>
<td>$16</td>
<td>5,000</td>
<td>$3.80 / $3.20</td>
</tr>
<tr>
<td>Standard</td>
<td>$99</td>
<td>$83</td>
<td>100,000</td>
<td>$0.99 / $0.83</td>
</tr>
<tr>
<td>Growth</td>
<td>$399</td>
<td>$333</td>
<td>500,000</td>
<td>$0.80 / $0.67</td>
</tr>
<tr>
<td>Scale</td>
<td>$749</td>
<td>$599</td>
<td>1,000,000</td>
<td>$0.75 / $0.60</td>
</tr>
</tbody></table>
<p>Two things stand out. First, Hobby is nearly four times dearer per credit than Standard, so anyone doing real volume ends up on Standard or above. Second, the annual discount is worth about 16% and is how most comparisons (including ours on <a href="https://mesharc.dev/compare">/compare</a>) arrive at "$0.83 per 1,000 pages".</p>
<p>Firecrawl's concurrency also scales with plan: 2 on Free, 5 on Hobby, 25 on Standard, 50 on Growth, 100 on Scale. If you need throughput rather than volume, that can push you up a tier before credits do.</p>
<h2>What a credit buys</h2>
<p>From the billing docs, the per-page costs that matter for most scraping jobs:</p>
<table>
<thead>
<tr>
<th>Operation</th>
<th>Credits</th>
</tr>
</thead>
<tbody><tr>
<td>Scrape or crawl, Markdown/HTML</td>
<td>1 per page</td>
</tr>
<tr>
<td>Enhanced proxy (including <code>auto</code> escalation)</td>
<td>+0 — billed at the base 1 credit</td>
</tr>
<tr>
<td>JSON format (LLM extraction)</td>
<td>+4 per page</td>
</tr>
<tr>
<td>PDF parsing</td>
<td>+1 per PDF page</td>
</tr>
<tr>
<td>Zero Data Retention</td>
<td>+1 per page</td>
</tr>
<tr>
<td>Map</td>
<td>1 per call</td>
</tr>
<tr>
<td>Search</td>
<td>2 per 10 results, plus scrape costs for each scraped result</td>
</tr>
<tr>
<td>Monitor</td>
<td>1 per page per check</td>
</tr>
</tbody></table>
<p>The enhanced-proxy line deserves credit. Firecrawl does not charge extra when a page needs a better proxy, and escalated retries are not billed separately. For a site that needs residential-grade routing on every page, that flat 1 credit is very hard to beat — more on that below.</p>
<p>The JSON line is the one that changes bills. A page with <code>formats: ["json"]</code> costs 5 credits, not 1. Modifiers stack, so JSON plus Zero Data Retention is 6.</p>
<h2>The 404 question</h2>
<p>This is the line most people miss. From Firecrawl's billing docs: if a document is returned — including a 403 or a 404 — the page costs 1 credit plus any option costs. Only when no document comes back at all (the site never responded) is it free.</p>
<p>The docs suggest checking <code>metadata.statusCode</code> to spot blocked URLs. That is good advice, but by then you have paid. On a crawl driven by an old sitemap, or a list of product URLs scraped six months ago, 404s are not rare. And if you asked for JSON, each 404 costs 5 credits.</p>
<h2>Worked examples in dollars</h2>
<p>All Firecrawl prices below use Standard at $0.99 per 1,000 credits (monthly) with the annual $0.83 in brackets.</p>
<p><strong>1. 1,000 plain pages, Markdown.</strong> 1,000 credits → <strong>$0.99</strong> ($0.83).</p>
<p><strong>2. 1,000 product pages, extracted to JSON.</strong> 1,000 × 5 = 5,000 credits → <strong>$4.95</strong> ($4.15). The model cost is included, which is convenient.</p>
<p><strong>3. The same 1,000 JSON pages, where 80 URLs are now 404s.</strong> Still 5,000 credits → <strong>$4.95</strong> ($4.15). The 80 dead URLs cost 400 credits between them and returned nothing useful.</p>
<p><strong>4. A 50-page PDF manual.</strong> 50 credits for parsing on top of the scrape → about <strong>$0.05</strong>. Cheap; PDFs are not where Firecrawl bills hurt.</p>
<p><strong>5. Re-checking 2,000 pages weekly with Monitor.</strong> 2,000 × 4 checks = 8,000 credits a month → <strong>$7.92</strong> ($6.64), whether or not anything changed.</p>
<h2>Expiry, rollover and top-ups</h2>
<ul>
<li>Plan credits reset monthly and <strong>do not roll over</strong> on Free, Hobby, Standard or Growth. Annual Scale rolls unused credits over one month; annual Enterprise, two.</li>
<li>Pay-as-you-go top-ups come in $5 increments: 1,000 credits on Hobby, 2,000 on Standard, 2,500 on Growth, 5,000 on Scale. On Standard that is <strong>$2.50 per 1,000 credits</strong> — 2.5× the plan rate. Top-ups stay on the account until used, but expire if you cancel the subscription.</li>
<li>Pay-as-you-go needs a paid plan; on Free, requests return HTTP 402 when credits run out.</li>
</ul>
<p>The practical effect: size your plan to your typical month, not your peak, and treat top-ups as expensive insurance.</p>
<h2>The same jobs on MeshArc</h2>
<p>MeshArc bills by the rung a page actually needed, and does not bill pages that did not deliver. From <a href="https://mesharc.dev/pricing">mesharc.dev/pricing</a>, on Growth ($99 for 500,000 credits, so $0.198 per 1,000 credits):</p>
<table>
<thead>
<tr>
<th>Rung</th>
<th>Credits/page</th>
<th>$ per 1,000 pages</th>
</tr>
</thead>
<tbody><tr>
<td>Fetch</td>
<td>1</td>
<td>$0.20</td>
</tr>
<tr>
<td>Fetch with session (walled sites, after one render)</td>
<td>2</td>
<td>$0.40</td>
</tr>
<tr>
<td>Render (real Chrome)</td>
<td>4</td>
<td>$0.79</td>
</tr>
<tr>
<td>Render, residential (incl. 1 MB)</td>
<td>16</td>
<td>$3.17</td>
</tr>
</tbody></table>
<p>Refused pages, 404s and 410s, pages served from cache, URLs blocked by robots.txt and pages carried forward unchanged on an incremental run cost nothing. Structured extraction is 1 credit per 1,000 tokens, with the model bill paid to your own provider.</p>
<p>Running the examples again:</p>
<ul>
<li><strong>1,000 plain pages:</strong> $0.20 if they fetch plainly, $0.79 if every one needs a browser.</li>
<li><strong>1,000 JSON pages, 80 of them 404s:</strong> the 80 cost 0. The other 920, at about 3,000 tokens each (the example on our pricing page), are 3 credits each for extraction plus the read — but you also pay your model provider. If that model bill matters to you, Firecrawl's all-in 5 credits may well be simpler.</li>
<li><strong>A 100-page Cloudflare-walled site:</strong> one render earns a session, then 99 pages read with it: 202 credits, about <strong>$0.04</strong>.</li>
<li><strong>2,000 pages, 4 runs a month, 20% needing a browser, 2% residential:</strong> our pricing page puts that at about 15,200 credits, or roughly <strong>$3.01</strong>. Firecrawl Monitor on the same pages is $7.92 monthly — though Monitor gives you diffs, and so does MeshArc, so compare like with like.</li>
</ul>
<h2>Where Firecrawl is cheaper</h2>
<p>Be honest about the residential case. A page that needs a residential IP costs MeshArc 16 credits ($3.17 per 1,000 on Growth). Firecrawl charges 1 credit with enhanced proxy, so $0.83–$0.99 per 1,000. If your whole job is hard sites that need residential routing on every page and cannot be read with a session, Firecrawl is three to four times cheaper per page. Our <a href="https://mesharc.dev/firecrawl-alternative">Firecrawl alternative page</a> also says Firecrawl is the better pick if you want to self-host an open-source crawler; MeshArc is not open source.</p>
<p>Firecrawl's JSON format also includes the model. If you do not want a model key of your own, that is a real convenience.</p>
<p>Where the maths goes the other way is plain pages (about 4× cheaper on MeshArc), walled sites that a session can read (about 2×), anything with a meaningful 404 rate, and repeat crawls where most pages have not changed.</p>
<h2>Estimate your own bill</h2>
<p>Here is a small, dependency-free calculator for Firecrawl's side, using the rates above. Change the inputs to your workload.</p>
<pre><code class="language-python"># Firecrawl monthly cost estimate (rates checked 21 Oct 2026)
PLAN_USD_PER_1K = {"standard_monthly": 0.99, "standard_annual": 0.83,
                   "growth_monthly": 0.80, "growth_annual": 0.67}

def firecrawl_cost(pages, json_share=0.0, pdf_pages=0, zdr=False,
                   plan="standard_monthly"):
    per_page = 1 + (1 if zdr else 0)
    credits = pages * per_page + pages * json_share * 4 + pdf_pages
    return credits, round(credits / 1000 * PLAN_USD_PER_1K[plan], 2)

# 10,000 pages, 30% as JSON, 200 PDF pages
print(firecrawl_cost(10_000, json_share=0.3, pdf_pages=200))
# (22200.0, 21.98)
</code></pre>
<p>Note that 404s are not discounted in that formula — because Firecrawl does not discount them.</p>
<p>To see what the same URLs cost on MeshArc, run them through the SDK and add up the per-page <code>credits</code> field. Every row tells you the HTTP status, the verdict and what it cost.</p>
<pre><code class="language-bash">pip install mesharc        # or: npm install mesharc
</code></pre>
<pre><code class="language-python">from mesharc import MeshArc

arc = MeshArc()  # reads MESHARC_API_KEY

urls = [line.strip() for line in open("urls.txt") if line.strip()]
batch = arc.scrape(urls, config={"concurrency": 4})

total = 0
for row in batch["pages"]:
    total += row["credits"]
    print(row["url"], row["httpStatus"], row["verdict"], row["credits"])

print(f"{len(urls)} URLs, {total} credits, ~${total / 1000 * 0.198:.2f} on Growth")
</code></pre>
<p>A 404 or a refused page shows up in that list with <code>credits</code> of 0.</p>
<h2>The short version</h2>
<ul>
<li>Firecrawl Standard is <strong>$0.99 per 1,000 pages</strong> billed monthly, <strong>$0.83</strong> billed annually.</li>
<li>JSON extraction makes it <strong>$4.95 / $4.15</strong> per 1,000, model included.</li>
<li>403s and 404s are billed. Plan credits do not roll over below Scale. Top-ups cost 2.5× the plan rate on Standard.</li>
<li>Enhanced proxy is free on top, which makes Firecrawl the cheaper choice for hard, residential-only sites.</li>
</ul>
<p>If you want to compare on your own URLs rather than mine, paste a few into the <a href="https://mesharc.dev/playground">MeshArc playground</a> and look at the credits column, or see the full vendor table on <a href="https://mesharc.dev/compare">/compare</a>. Re-crawling the same sites every week? The <a href="https://mesharc.dev/use-cases/website-change-monitoring">website change monitoring use case</a> shows how unchanged pages drop to zero.</p>
<p><a href="https://mesharc.dev/playground"><strong>Try it in the playground</strong></a> · <a href="https://mesharc.dev/signup"><strong>Start free — 1,000 credits, no card</strong></a></p>
<hr />
<p><em>Originally published on</em> <a href="https://mesharc.dev/blog/firecrawl-pricing-explained"><em>mesharc.dev</em></a><em>.</em></p>
]]></content:encoded></item></channel></rss>