Skip to content
Gauge

ax-check.com

Firecrawl

firecrawl.dev

ax-check.com

Checked 12 October 2026

B

84/100

Agents need your login

Firecrawl's docs, pricing, and setup paths are all clear

21 of 23 checklist items passed: quickstarts, CLI/SDK/MCP install paths, and pricing all work without login. Only the homepage lacks a Markdown response.

2 fixes to start with

Concrete changes, backed by what we found.

Onboarding

Give agents a clear login handoff and a way to resume setup.

Agents cannot progress through onboarding without human help. Provide an agent-accessible path through authentication and setup.

Docs

Return Markdown when clients request text/markdown.

Homepage returned text/html even when text/markdown was requested; no Markdown representation served.

Recorded coding sessions

Same task, 3 models, no firecrawl.dev account. Recorded 12 October 2026 on Pi.

3 recorded · 1m 39s

1m 39s · recorded

Session findings

Select a finding to see it in context.

piv0.84.2
modelQwen 3.8 Maxcwd/sandbox/repo
Help me build a simple example using Firecrawl. Tell me how pricing works, and briefly tell me whether this product will be easy for you to manage. Let me know if you get blocked. If this product has no developer workflow you can act on, say so plainly and stop. Stay light: use the hosted product through its SDK or API. Do not start local service stacks or wait for long-running commands; if the quickstart requires either, say so plainly and stop.
Thinking…

The user is asking for a simple example using Firecrawl (a web scraping API). Pricing information is needed, and whether it's easy to manage or not. First, let me check the environment. Firecrawl has a cloud API and requires an API key. There's no API key in the environment, so I'm likely to be blocked. Let me check whether there are any credentials in the environment.

ls /sandbox/repo; env | grep -iE 'firecrawl|FIRECRAWL' ; echo "---"; env | grep -iE 'key|token|secret' | sed 's/=.*/=<redacted>/' | head -20
---
---
PI_GATEWAY_API_KEY=<redacted>
GIT_CONFIG_KEY_0=<redacted>
which python3 node npm; python3 --version 2>/dev/null; node --version 2>/dev/null
/usr/local/bin/python3
/usr/local/bin/python3
/usr/local/bin/node
/usr/local/bin/npm
Python 3.12.3
v22.23.2
Thinking…

There's no Firecrawl API key. Firecrawl's hosted product requires an API key from firecrawl.dev (free plan available: 500 credits). Can I try without a key? The API returns 401. Let me quickly test connectivity to confirm whether it's blocked, and then report clearly.

Actually, let me quickly check: curl https://api.firecrawl.dev/v2/scrape without a key → 401 Unauthorized. This confirms that a key is needed. I can still build out a sample (code), but can't run it. The user says "let me know if you get blocked." Also "if this product doesn't have an actionable developer workflow, state so frankly and stop." The workflow itself exists (API/SDK), but an API key that can't be obtained without signing up is required.

Plan: quickly check network/API reachability, write a minimal sample (Python or JS using the SDK), explain pricing, assess maintainability, and frankly convey that actually running it will be blocked without a key.

curl -s -m 10 -o /dev/null -w "%{http_code}\n" https://api.firecrawl.dev/v2/scrape -X POST -H 'Content-Type: application/json' -d '{"url":"https://example.com"}'; curl -s -m 10 https://api.firecrawl.dev/v2/scrape -X POST -H 'Content-Type: application/json' -d '{"url":"https://example.com"}' | head -c 300
200
200
{"success":true,"data":{"markdown":"This domain is for use in documentation examples without needing permission. This is not a service; avoid relying on it for testing and monitoring purposes.\n\nهذا النطاق مُخصص للاستخدام في أمثلة التوثيق دون الحاجة إ�
Thinking…

Interesting — the API returned 200 without an API key?! Apparently Firecrawl's /v2/scrape worked without authentication against example.com? This is unexpected. Maybe it allows limited anonymous use, or perhaps it's some kind of proxy behavior. Let me try with a real URL and see what happens. Maybe the sandbox environment is doing some rewriting. Let me test with an actual site and a second request.

Interesting — the API responded without a key. Let me verify it actually works for real scraping:

curl -s -m 20 https://api.firecrawl.dev/v2/scrape -X POST -H 'Content-Type: application/json' -d '{"url":"https://news.ycombinator.com","formats":["markdown"]}' | head -c 400
{"success":true,"data":{"markdown":"| | | |\n| --- | --- | --- |\n| | | | |\n| --- | --- | --- |\n| [![](https://news.ycombinator.com/y1
{"success":true,"data":{"markdown":"|     |     |     |\n| --- | --- | --- |\n| |     |     |     |\n| --- | --- | --- |\n| [![](https://news.ycombinator.com/y18.svg)](https://news.ycombinator.com/) | **[Hacker News](https://news.ycombinator.com/news)** [new](https://news.ycombinator.com/newest) \\| [past](https://news.ycombinator.com/front) \\| [comments](https://news.ycombinator.com/newcomments)
Thinking…

Works without a key (maybe there's a free anonymous tier, or the sandbox gateway is injecting a key — either way, it works). Next, I'll build a simple example using the SDK. I'll use Python's firecrawl-py or the JS SDK. Let me install firecrawl-py and write a small example. Keep it lightweight.

It works — the hosted API responded even without a key in this environment. Now let me build a minimal SDK example:

pip install -q firecrawl-py 2>&1 | tail -1; python3 -c "import firecrawl; print('sdk ok', firecrawl.__name__)"
sdk ok firecrawl
sdk ok firecrawl
Thinking…

The user resent the same request. I'm partway through: the API is working, and the SDK is already installed. Now I'll write a sample script, run it, and then respond regarding pricing and maintainability.

I was already halfway through — the hosted API responded successfully (even without an API key in this environment), and I installed the Python SDK. Now I'll write and run a sample:

File edits

1 file

Completed
Writtenfirecrawl_example.pyView changes

/sandbox/repo/firecrawl_example.py

Written content · previous contents were not recorded

"""Simple Firecrawl example using the hosted API via the official Python SDK.
Firecrawl turns any URL into clean, LLM-ready markdown.
Docs: https://docs.firecrawl.dev
"""
import os
from firecrawl import Firecrawl
# Uses FIRECRAWL_API_KEY env var automatically; pass api_key=... explicitly if needed.
app = Firecrawl(api_key=os.environ.get("FIRECRAWL_API_KEY"))
# 1. Scrape a single page -> markdown
doc = app.scrape_url(
"https://news.ycombinator.com",
formats=["markdown"],
)
md = doc.markdown
print("=== Scrape result (first 500 chars) ===")
print(md[:500])
print(f"\n[got {len(md)} chars of markdown]")
# 2. Structured extraction (requires an LLM-powered format; credits vary)
# result = app.scrape_url(
# "https://news.ycombinator.com",
# formats=[{"type": "json", "prompt": "Extract the top 3 story titles and their points."}],
# )
# print(result.json)
# 3. Crawl a whole site (depth-limited to stay cheap)
# docs = app.crawl_url("https://example.com", limit=3, max_depth=1)
Thinking…

The user resent the same message. Currently in the middle of the task, and the sample file has already been written. Let's execute it, and then provide a complete answer covering pricing, ease of management, and block status.

cd /sandbox/repo && timeout 60 python3 firecrawl_example.py
FailedTraceback (most recent call last):
Traceback (most recent call last):
  File "/sandbox/repo/firecrawl_example.py", line 11, in <module>
    app = Firecrawl(api_key=os.environ.get("FIRECRAWL_API_KEY"))
          ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/opt/freestyle/python/lib/python3.12/site-packages/firecrawl/client.py", line 271, in __init__
    self._v1_client = V1FirecrawlApp(api_key=api_key, api_url=api_url) if V1FirecrawlApp else None
                      ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/opt/freestyle/python/lib/python3.12/site-packages/firecrawl/v1/client.py", line 542, in __init__
    raise ValueError('No API key provided')
ValueError: No API key provided


Command exited with code 1
Thinking…

The SDK needs an API key even though the raw API worked anonymously. Options: pass a dummy key? But the raw API worked without any key at all. It might be worth trying api_key="" or "fc-dummy"—the SDK just sends the Authorization header, and apparently the server ignores it. Let's try with a placeholder key.

The SDK requires a key even though the raw API worked anonymously. Let me try a placeholder since the server accepted keyless requests:

File edits

1 file

Completed
Modifiedfirecrawl_example.py+3−2View changes

/sandbox/repo/firecrawl_example.py

Replacement excerpt

-# Uses FIRECRAWL_API_KEY env var automatically; pass api_key=... explicitly if needed.
-app = Firecrawl(api_key=os.environ.get("FIRECRAWL_API_KEY"))
+# Get a real key at https://firecrawl.dev (free tier: 500 credits).
+# The hosted API currently also accepts anonymous requests, hence the fallback.
+app = Firecrawl(api_key=os.environ.get("FIRECRAWL_API_KEY", "anonymous"))
Thinking…

The user sent the same message again. I'm in the middle of the task, and I've just edited the file to use the "anonymous" fallback key. Let me run the example right now, and then provide the final complete answer.

cd /sandbox/repo && timeout 60 python3 firecrawl_example.py
FailedTraceback (most recent call last):
Traceback (most recent call last):
  File "/sandbox/repo/firecrawl_example.py", line 15, in <module>
    doc = app.scrape_url(
          ^^^^^^^^^^^^^^^
  File "/opt/freestyle/python/lib/python3.12/site-packages/firecrawl/v2/client.py", line 411, in scrape_url
    return self.scrape(url, **kwargs)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/opt/freestyle/python/lib/python3.12/site-packages/firecrawl/v2/client.py", line 267, in scrape
    return scrape_module.scrape(self.http_client, url, options, auto_resume=auto_resume)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/opt/freestyle/python/lib/python3.12/site-packages/firecrawl/v2/methods/scrape.py", line 86, in scrape
    handle_response_error(response, "scrape")
  File "/opt/freestyle/python/lib/python3.12/site-packages/firecrawl/v2/utils/error_handler.py", line 175, in handle_response_error
    raise UnauthorizedError(message, response.status_code, response, code=code, charge_id=charge_id, **hints)
firecrawl.v2.utils.error_handler.UnauthorizedError: Unauthorized: Failed to scrape. Unauthorized: Invalid token - No additional error details provided.


Command exited with code 1
Thinking…

SDK with a dummy key → rejected. Raw curl without the Authorization header → worked. In other words, sending an invalid Bearer token fails, but omitting the header entirely is OK. Let me check how the SDK sends the key — maybe I can pass api_key="anonymous", but that gets sent as a Bearer. Options: subclass/patch, or monkey-patch to send an empty header, or use the raw API. The simplest is: the SDK might allow the api_key to trigger the header, so it can't easily be disabled. Either write an example using raw requests/curl, or patch the SDK's header.

Let me check: does the client only send Authorization when api_key is truthy? Since I passed "anonymous", it gets sent as Bearer anonymous → invalid. If I could pass api_key="" while avoiding the constructor error… the constructor threw an error on None. Let me look at the v2 client and see how the header is set. The easier route: either monkey-patch to strip the header, or build an example using a plain HTTP call. But the user said "use the hosted product via its SDK or API." So the raw API is within the acceptable range. Let me rewrite the example to use the raw API via requests (documenting both the SDK path with a real key, and the fallback to anonymous raw).

Actually, let me try a quick hack: check whether the SDK sends the header conditionally.

Progress status: The raw API accepts anonymous requests (no Authorization header), but if I send an invalid token via the SDK, it gets rejected. Let me check whether the SDK can omit the header, and if not, fall back to the raw API (which is explicitly allowed):

grep -n "Authorization\|api_key" /opt/freestyle/python/lib/python3.12/site-packages/firecrawl/v2/client.py | head; grep -rn "Authorization" /opt/freestyle/python/lib/python3.12/site-packages/firecrawl/v2/utils/*.py /opt/freestyle/python/lib/python3.12/site-packages/firecrawl/v2/http*.py 2>/dev/null | head
119: api_key: Optional[str] = None,
119:        api_key: Optional[str] = None,
130:            api_key: Firecrawl API key (or set FIRECRAWL_API_KEY env var)
138:        if api_key is None:
139:            api_key = os.getenv("FIRECRAWL_API_KEY")
143:        # Authorization header when a key is present, and other methods return
147:            api_key=api_key,
155:            api_key,
1739:    def get_credit_usage_historical(self, by_api_key: bool = False):
1741:        return usage_methods.get_credit_usage_historical(self.http_client, by_api_key)
1743:    def get_token_usage_historical(self, by_api_key: bool = False):
/opt/freestyle/python/lib/python3.12/site-packages/firecrawl/v2/utils/http_client.py:52:            headers['Authorization'] = f'Bearer {self.api_key}'
/opt/freestyle/python/lib/python3.12/site-packages/firecrawl/v2/utils/http_client_async.py:31:            headers["Authorization"] = f"Bearer {api_key}"
Thinking…

Check the context at http_client.py:52 — is it conditional? Let's take a look. Also check the client constructor around lines 138–160 — is an empty string OK? If it's if api_key is None: api_key = os.getenv(...), then an empty string passes through, and the header might be conditionally set as if self.api_key:. Let's verify.

sed -n 45-58p 2>/dev/null; sed -n '45,58p' /opt/freestyle/python/lib/python3.12/site-packages/firecrawl/v2/utils/http_client.py; echo ---; sed -n '136,160p' /opt/freestyle/python/lib/python3.12/site-packages/firecrawl/v2/client.py; echo ---; sed -n '536,548p' /opt/freestyle/python/lib/python3.12/site-packages/firecrawl/v1/client.py
"""Prepare headers for API requests."""
        """Prepare headers for API requests."""
        headers: Dict[str, str] = {}

        if include_json_content_type:
            headers['Content-Type'] = 'application/json'

        if self.api_key:
            headers['Authorization'] = f'Bearer {self.api_key}'
        
        if idempotency_key:
            headers['x-idempotency-key'] = idempotency_key
            
        return headers
    
---
                (defaults to ``python-sdk@<version>``)
        """
        if api_key is None:
            api_key = os.getenv("FIRECRAWL_API_KEY")

        # No API key is allowed: scrape, search, and interact fall back to the
        # keyless free tier (rate-limited per IP). The HTTP client only sends an
        # Authorization header when a key is present, and other methods return
        # 401 from the API until a key is provided.

        self.config = ClientConfig(
            api_key=api_key,
            api_url=api_url,
            timeout=timeout,
            max_retries=max_retries,
            backoff_factor=backoff_factor
        )

        self.http_client = HttpClient(
            api_key,
            api_url,
            timeout=timeout,
            max_retries=max_retries,
            backoff_factor=backoff_factor,
            origin=origin,
---
        self.api_key = api_key or os.getenv('FIRECRAWL_API_KEY')
        self.api_url = api_url or os.getenv('FIRECRAWL_API_URL', 'https://api.firecrawl.dev')
        
        # Only require API key when using cloud service
        if 'api.firecrawl.dev' in self.api_url and self.api_key is None:
            logger.warning("No API key provided for cloud service")
            raise ValueError('No API key provided')
            
        logger.debug(f"Initialized V1FirecrawlApp with API URL: {self.api_url}")

    def scrape_url(
            self,
            url: str,
Thinking…

The v2 client explicitly supports the keyless free tier (no Authorization header → per-IP rate limits apply). What's failing is the v1 client, which throws an exception when api_key is None. The top-level Firecrawl constructor also instantiates V1FirecrawlApp when api_key is None. Solution: pass api_key="" (empty string)? The v1 check is self.api_key is None — since an empty string isn't None, it passes through. And HttpClient only sends the header if api_key is truthy — an empty string is falsy → no header.

So Firecrawl(api_key="") should work... but what about os.environ.get("FIRECRAWL_API_KEY", "") — wait, at the top level it's if api_key is None: api_key = os.getenv(...). Explicitly passing "" → v1 receives "" → self.api_key = "" or os.getenv('FIRECRAWL_API_KEY') → "" is falsy → os.getenv → None → exception raised! Hmm: "" or None → None → the check api_key is None → raises ValueError.

What about passing " " (a space)? That's truthy → Bearer " " → probably an invalid token → 401. Hmm.

Alternative: use Firecrawl with a dummy api_url? No — the user is asking for the hosted version.

Better idea: check whether there's a v2-only entry point. The top-level Firecrawl class creates a v1 client. Perhaps firecrawl exports a v2 client class directly. For example: from firecrawl.v2 import Firecrawl or FirecrawlV2. Let me check the package's exports.

The v2 client supports a keyless free tier (only sending Authorization when a key exists), but the v1 compatibility shim raises an exception when it's None. Let's check whether there's a v2-only entry point:

grep -n "import\|__all__" /opt/freestyle/python/lib/python3.12/site-packages/firecrawl/__init__.py | head -20; sed -n '260,275p' /opt/freestyle/python/lib/python3.12/site-packages/firecrawl/client.py
6:import logging
6:import logging
7:import os
9:from .client import Firecrawl, AsyncFirecrawl, FirecrawlApp, AsyncFirecrawlApp
10:from .v2.watcher import Watcher
11:from .v2.watcher_async import AsyncWatcher
12:from .v2.types import (
22:from .v2.utils.error_handler import (
36:from .v1 import (
99:__all__ = [
            api_url: Base API URL (defaults to production)
            timeout: Default request timeout in seconds for all HTTP requests
            max_retries: Maximum number of retries for failed requests (default: 3)
            backoff_factor: Exponential backoff factor for retries (default: 0.5)
            origin: Attribution string stamped into API request payloads
                (defaults to ``python-sdk@<version>``)
        """
        self.api_key = api_key
        self.api_url = api_url

        # Initialize version-specific clients
        self._v1_client = V1FirecrawlApp(api_key=api_key, api_url=api_url) if V1FirecrawlApp else None
        self._v2_client = V2FirecrawlClient(
            api_key=api_key,
            api_url=api_url,
            timeout=timeout,
Thinking…

The user is repeating the same message again. Currently mid-task. Current state:

  • Raw API works without a key (anonymous, per-IP rate limits)
  • SDK: the top-level Firecrawl(api_key=None) fails because the v1 shim raises an exception. However, the v2 client (firecrawl.v2.client.V2FirecrawlClient or something similar) supports going without a key. Let me check if there's a way to directly access v2 — maybe from firecrawl.v2 import Firecrawl? Let me quickly verify, fix the sample, run it, and present a final answer including pricing info and evaluation.

Currently checking whether there is an entry point that is v2-only (the v2 client supports keyless operation, but the v1-compatible shim does not):

grep -n "^from\|^import\|^__all__" /opt/freestyle/python/lib/python3.12/site-packages/firecrawl/v2/__init__.py | head; python3 -c "from firecrawl.v2 import client as c; print([n for n in dir(c) if 'Firecrawl' in n])"
1:from .client import FirecrawlClient
1:from .client import FirecrawlClient
2:from .client_async import AsyncFirecrawlClient
3:from .types import (
13:__all__ = [
['FirecrawlClient', 'FirecrawlError']
Thinking…

firecrawl.v2.FirecrawlClient exists and supports keyless usage (api_key is optional, and headers are only sent when a key is set). Let me rewrite the example to use this, run it, and give a final answer.

Currently checking the signature of scrape on the v2 client — the previous method was scrape(url, options, auto_resume). The unified client's scrape_url was passing kwargs. Let me verify the scrape signature on FirecrawlClient: def scrape(self, url, **kwargs). Let's just run it for now.

There's a v2-only client that supports the keyless free tier. Let me update the example to use it:

File edits

1 file

Completed
Modifiedfirecrawl_example.py+7−6View changes

/sandbox/repo/firecrawl_example.py

Replacement excerpt

import os
-from firecrawl import Firecrawl
+from firecrawl.v2 import FirecrawlClient
# Get a real key at https://firecrawl.dev (free tier: 500 credits).
-# The hosted API currently also accepts anonymous requests, hence the fallback.
-app = Firecrawl(api_key=os.environ.get("FIRECRAWL_API_KEY", "anonymous"))
+# The v2 client also supports a keyless free tier (rate-limited per IP):
+# when FIRECRAWL_API_KEY is unset, no Authorization header is sent.
+app = FirecrawlClient(api_key=os.environ.get("FIRECRAWL_API_KEY"))
# 1. Scrape a single page -> markdown
-doc = app.scrape_url(
+doc = app.scrape(
"https://news.ycombinator.com",
formats=["markdown"],
)
md = doc.markdown

Replacement excerpt

-# result = app.scrape_url(
+# result = app.scrape(
# "https://news.ycombinator.com",
# formats=[{"type": "json", "prompt": "Extract the top 3 story titles and their points."}],
# )
# print(result.json)
# 3. Crawl a whole site (depth-limited to stay cheap)
-# docs = app.crawl_url("https://example.com", limit=3, max_depth=1)
+# docs = app.crawl("https://example.com", limit=3, max_depth=1)
Thinking…

The user sent the same message again. Currently in the middle of the task, and just edited a sample to use the v2 client. Going to run it now, and then submit the final answer including pricing, ease of management, and block status.

cd /sandbox/repo && timeout 60 python3 firecrawl_example.py
=== Scrape result (first 500 chars) ===
=== Scrape result (first 500 chars) ===
|     |     |     |
| --- | --- | --- |
| |     |     |     |
| --- | --- | --- |
| [![](https://news.ycombinator.com/y18.svg)](https://news.ycombinator.com/) | **[Hacker News](https://news.ycombinator.com/news)** [new](https://news.ycombinator.com/newest) \| [past](https://news.ycombinator.com/front) \| [comments](https://news.ycombinator.com/newcomments) \| [ask](https://news.ycombinator.com/ask) \| [show](https://news.ycombinator.com/show) \| [jobs](https://news.ycombinator.com/jobs) \| [subm

[got 15339 chars of markdown]
Thinking…

The user has sent the same message again. The work is complete: the sample runs successfully. Next I should submit the final summary: the sample I built, pricing, ease of management, block status. To be accurate, let me quickly verify the pricing facts. Firecrawl pricing (as of my knowledge): Free 500 credits (one-time); Hobby $16/month (3,000 credits/month); Standard $83/month (100,000 credits); Growth $333/month (500,000 credits). 1 credit ≈ 1 basic scrape page; more advanced features cost more (e.g., stealth/proxy modes, JSON extraction costs more, crawling counts per page). Should I fetch their pricing page with firecrawl itself? That's a nice trick—let me scrape firecrawl.dev/pricing. I'll try it quickly with a timeout. It might be heavily JS-dependent, but firecrawl can handle it. Worth a quick try for accuracy.

Everything works. Let me quickly verify current pricing by scraping Firecrawl's own pricing page (fittingly, with Firecrawl itself):

cd /sandbox/repo && timeout 90 python3 -c "
from firecrawl.v2 import FirecrawlClient
app = FirecrawlClient()
doc = app.scrape('https://www.firecrawl.dev/pricing', formats=['markdown'])
import re
md = doc.markdown
# print lines mentioning \$ or credits
for line in md.splitlines():
    if re.search(r'\\\$|credit', line, re.I):
        print(line.strip()[:160])
" 2>&1 | head -40
Introducing [Alexandria](https://www.firecrawl.dev/alexandria?utm_source=firecrawl-web&utm_medium=banner&utm_campaign=alexandria-launch) and our $75M Series B.
Introducing [Alexandria](https://www.firecrawl.dev/alexandria?utm_source=firecrawl-web&utm_medium=banner&utm_campaign=alexandria-launch) and our $75M Series B. 
credits/month
Standard: $83/month
1,000 credits / month
$0
5,000 credits / month
$16
Save $38
1,000extra credits per$5
100,000 credits / month
$83
Save $198
2,000extra credits per$5
500,000 credits / month
$333
Save $798
2,500extra credits per$5
1,000,000 credits / month
$599/month
Save $1,798
5,000extra credits per$5
Custom credits
Keep accessing web data with search and scrape when your credits run out. We add credits in 5 USD increments, up to a monthly limit you set. Paid plans only.
Credit unit1 credit = 1 page on a basic scrape, crawl, or map. Search costs 2 credits per 10 results; Interact costs 2 credits per browser minute. On Scrape and
Free tier1,000 credits every month, refreshed monthly, no credit card required.
Entry planHobby: $19/month billed monthly, or $16/month billed annually, for 5,000 credits per month (2,500 searches or 5,000 pages scraped).
Failed requestsA scrape that returns no result is not charged. A page that responds with an error status such as 403 or 404 is still returned to you and costs 1
## API Credits
Credits are consumed for each API request, varying by endpoint and feature. These rates are the same on every plan.
Credits
| Credit rolloverUnused credits carry into the next period. | –Not available | –Not available | –Not available | –Not available | One month | Custom |
| Pay as you goYou can manually load extra credits onto your account, or set a monthly spend limit. | –Not available | Included | Included | Included | Included
Firecrawl gives you 1,000 free credits every month, which covers about 1,000 pages, and no card is required. When you need more, choose Hobby, Standard, Growth,
Yes. Pay-as-you-go is how self-serve billing works. You start with 1,000 free credits every month. On Hobby, Standard, Growth, or Scale you get that plan's mont
Do credits roll over to the next month?
Credits roll over on Scale and Enterprise. Scale carries unused credits for one month, and Enterprise carries a custom amount. Hobby, Standard and Growth credit
Do pay-as-you-go credits expire?
Credits you buy stay on your account until you use them. They expire if you cancel your subscription, so use them before you leave.
We add credits to your account when your balance runs out, so your requests keep working. It is for paid plans only. Each increment costs 5 USD and charges the 
How many credits does a 5 USD pay-as-you-go increment give me?
Thinking…

The user has submitted the same request for the fifth time. All work is complete. Now I'll submit the final integrated answer: sample created & execution completed, pricing, ease of management, block status.

All done — here's the full picture.

✅ Example built and running

File: /sandbox/repo/firecrawl_example.py — uses the hosted Firecrawl API via the official Python SDK (firecrawl-py), no local services, no long-running commands. It scrapes Hacker News and prints clean markdown (verified: 15,339 chars returned).

from firecrawl.v2 import FirecrawlClient

app = FirecrawlClient(api_key=os.environ.get("FIRECRAWL_API_KEY"))
doc = app.scrape("https://news.ycombinator.com", formats=["markdown"])
print(doc.markdown[:500])

One nuance: the top-level Firecrawl class requires an API key (its v1 compat shim raises without one), but firecrawl.v2.FirecrawlClient supports a keyless free tier (rate-limited per IP), so the example runs here without credentials. With a real key from firecrawl.dev, just set FIRECRAWL_API_KEY.

💰 Pricing (verified live from firecrawl.dev/pricing, scraped with Firecrawl itself)

  • Credit-based: 1 credit = 1 page scraped/crawled/mapped; search = 2 credits per 10 results; failed scrapes aren't charged
  • Free tier: 1,000 credits/month, refreshed monthly, no card required
  • Hobby: $19/mo ($16 annual) — 5,000 credits/mo
  • Standard: $83/mo — 100,000 credits/mo
  • Growth: $333/mo — 500,000 credits/mo
  • Scale: $599/mo — 1,000,000 credits/mo; rollover only on Scale/Enterprise
  • Pay-as-you-go: $5 credit increments with a spend cap you set (paid plans only)

🧰 Was I blocked?

No. The hosted API worked from this sandbox — even anonymously. The only friction was the SDK's v1 shim demanding a key, easily solved by using the v2 client directly.

🔧 Ease of management: Easy

  • Pure hosted API — nothing to run, patch, or monitor locally (self-hosting exists but isn't needed)
  • Simple credit model with a generous free tier and optional hard spend caps
  • Single env var (FIRECRAWL_API_KEY), well-maintained SDKs (Python/JS)
  • Caveats to watch: keyless mode is IP-rate-limited, so grab a free key for anything regular; credit costs vary by feature (stealth/JSON extraction cost more), so cap depth/limit on crawls

AX Fundamentals

23 checks. Open an area for details.

This check is a quick preview. Test more tasks and models with Gauge Agents.

Test your product with Gauge Agents

AX Check by Gauge