# pip install firecrawl-pyfrom firecrawl import Firecrawlfirecrawl = Firecrawl( # No API key needed to get started — add one for higher rate limits: # api_key="fc-YOUR-API-KEY",)
// npm install firecrawlimport { Firecrawl } from 'firecrawl';const firecrawl = new Firecrawl({ // No API key needed to get started — add one for higher rate limits: // apiKey: "fc-YOUR-API-KEY",});
from firecrawl import Firecrawlfirecrawl = Firecrawl( # No API key needed to get started — add one for higher rate limits: # api_key="fc-YOUR-API-KEY",)# Scrape a website:doc = firecrawl.scrape("https://firecrawl.dev", formats=["markdown", "html"])print(doc)
import { Firecrawl } from 'firecrawl';const firecrawl = new Firecrawl({ // No API key needed to get started — add one for higher rate limits: // apiKey: "fc-YOUR-API-KEY",});// Scrape a website:const doc = await firecrawl.scrape('https://firecrawl.dev', { formats: ['markdown', 'html'] });console.log(doc);
# No API key needed to get started — add -H "Authorization: Bearer $FIRECRAWL_API_KEY" for higher rate limits:curl -s -X POST "https://api.firecrawl.dev/v2/scrape" \ -H "Content-Type: application/json" \ -d '{ "url": "https://firecrawl.dev", "formats": ["markdown", "html"] }'
# Scrape a URL and get markdownfirecrawl https://firecrawl.dev# With multiple formats (returns JSON)firecrawl https://firecrawl.dev --format markdown,html,links --pretty
For more details about the parameters, refer to the API Reference.
PDFs and documents:/scrape auto-detects PDFs, DOCX, and other document types from URLs. Pass a PDF URL the same way you would any webpage — Firecrawl parses it and returns clean markdown. For local files that are not accessible by URL, use /parse instead.
Each scrape consumes 1 credit. Additional credits apply for certain options: JSON mode costs 4 additional credits per page, question and highlights formats cost 4 additional credits per page per format, enhanced proxy costs 4 additional credits per page, PII redaction costs 4 additional credits per page, PDF parsing costs 1 credit per PDF page, and audio or video extraction costs 4 additional credits per page.
Used to extract structured data from scraped pages.
Extracting products? For product pages, the product format returns structured product fields (title, price, availability, variants) deterministically — no LLM call and no schema to define. Reach for json when you need custom fields or non-product pages.
from firecrawl import Firecrawlfrom pydantic import BaseModelapp = Firecrawl( # No API key needed to get started — add one for higher rate limits: # api_key="fc-YOUR-API-KEY",)class CompanyInfo(BaseModel): company_mission: str supports_sso: bool is_open_source: bool is_in_yc: boolresult = app.scrape( 'https://firecrawl.dev', formats=[{ "type": "json", "schema": CompanyInfo.model_json_schema() }], only_main_content=False, timeout=120000)print(result)
import { Firecrawl } from "firecrawl";import { z } from "zod";const app = new Firecrawl({ // No API key needed to get started — add one for higher rate limits: // apiKey: "fc-YOUR_API_KEY",});// Define schema to extract contents intoconst schema = z.object({ company_mission: z.string(), supports_sso: z.boolean(), is_open_source: z.boolean(), is_in_yc: z.boolean()});const result = await app.scrape("https://firecrawl.dev", { formats: [{ type: "json", schema: schema }],});console.log(result);
You can now extract without a schema by just passing a prompt to the endpoint. The llm chooses the structure of the data.
from firecrawl import Firecrawlapp = Firecrawl( # No API key needed to get started — add one for higher rate limits: # api_key="fc-YOUR-API-KEY",)result = app.scrape( 'https://firecrawl.dev', formats=[{ "type": "json", "prompt": "Extract the company mission from the page." }], only_main_content=False, timeout=120000)print(result)
import { Firecrawl } from "firecrawl";const app = new Firecrawl({ // No API key needed to get started — add one for higher rate limits: // apiKey: "fc-YOUR_API_KEY",});const result = await app.scrape("https://firecrawl.dev", { formats: [{ type: "json", prompt: "Extract the company mission from the page." }]});console.log(result);
# No API key needed to get started — add -H "Authorization: Bearer YOUR_API_KEY" for higher rate limits:curl -X POST https://api.firecrawl.dev/v2/scrape \ -H 'Content-Type: application/json' \ -d '{ "url": "https://firecrawl.dev", "formats": [{ "type": "json", "prompt": "Extract the company mission from the page." }] }'
Output:
JSON
{ "success": true, "data": { "json": { "company_mission": "AI-powered web scraping and data extraction", }, "metadata": { "title": "Firecrawl", "description": "AI-powered web scraping and data extraction", "robots": "follow, index", "ogTitle": "Firecrawl", "ogDescription": "AI-powered web scraping and data extraction", "ogUrl": "https://firecrawl.dev/", "ogImage": "https://firecrawl.dev/og.png", "ogLocaleAlternate": [], "ogSiteName": "Firecrawl", "sourceURL": "https://firecrawl.dev/" }, }}
The branding format extracts comprehensive brand identity information from a webpage, including colors, fonts, typography, spacing, UI components, and more. This is useful for design system analysis, brand monitoring, or building tools that need to understand a website’s visual identity.
from firecrawl import Firecrawlfirecrawl = Firecrawl( # No API key needed to get started — add one for higher rate limits: # api_key='fc-YOUR_API_KEY',)result = firecrawl.scrape( url='https://firecrawl.dev', formats=['branding'])print(result['branding'])
import { Firecrawl } from 'firecrawl';const firecrawl = new Firecrawl({ // No API key needed to get started — add one for higher rate limits: // apiKey: "fc-YOUR-API-KEY",});const result = await firecrawl.scrape('https://firecrawl.dev', { formats: ['branding']});console.log(result.branding);
# No API key needed to get started — add -H "Authorization: Bearer $FIRECRAWL_API_KEY" for higher rate limits:curl -s -X POST "https://api.firecrawl.dev/v2/scrape" \ -H "Content-Type: application/json" \ -d '{ "url": "https://firecrawl.dev", "formats": ["branding"] }'
You can combine the branding format with other formats to get comprehensive page data:
from firecrawl import Firecrawlfirecrawl = Firecrawl( # No API key needed to get started — add one for higher rate limits: # api_key='fc-YOUR_API_KEY',)result = firecrawl.scrape( url='https://firecrawl.dev', formats=['markdown', 'branding', 'screenshot'])print(result['markdown'])print(result['branding'])print(result['screenshot'])
import { Firecrawl } from 'firecrawl';const firecrawl = new Firecrawl({ // No API key needed to get started — add one for higher rate limits: // apiKey: "fc-YOUR-API-KEY",});const result = await firecrawl.scrape('https://firecrawl.dev', { formats: ['markdown', 'branding', 'screenshot']});console.log(result.markdown);console.log(result.branding);console.log(result.screenshot);
# No API key needed to get started — add -H "Authorization: Bearer $FIRECRAWL_API_KEY" for higher rate limits:curl -s -X POST "https://api.firecrawl.dev/v2/scrape" \ -H "Content-Type: application/json" \ -d '{ "url": "https://firecrawl.dev", "formats": ["markdown", "branding", "screenshot"] }'
The product format extracts a structured product deterministically — the same kind of structured output as the json format, but without an LLM call or a schema you define, purpose-built for product pages. If you’ve been pulling product fields with a json schema, use formats: ["product"] instead — it’s faster and cheaper, just limited to products.It returns a product object with title, brand, category, description, and variants — where each variant carries price, original price, availability, and images — useful for price monitoring, catalog ingestion, or comparison-shopping tools.
from firecrawl import Firecrawlfirecrawl = Firecrawl( # No API key needed to get started — add one for higher rate limits: # api_key='fc-YOUR_API_KEY',)result = firecrawl.scrape( url='https://example.com/products/wireless-headphones', formats=['product'])print(result['product'])
import { Firecrawl } from 'firecrawl';const firecrawl = new Firecrawl({ // No API key needed to get started — add one for higher rate limits: // apiKey: "fc-YOUR-API-KEY",});const result = await firecrawl.scrape('https://example.com/products/wireless-headphones', { formats: ['product']});console.log(result.product);
# No API key needed to get started — add -H "Authorization: Bearer $FIRECRAWL_API_KEY" for higher rate limits:curl -s -X POST "https://api.firecrawl.dev/v2/scrape" \ -H "Content-Type: application/json" \ -d '{ "url": "https://example.com/products/wireless-headphones", "formats": ["product"] }'
The product object contains the following properties:
title: The product name
brand: The product brand (optional)
category: The product category (optional)
url: The canonical URL of the product
description: The product description (optional)
variants: Array of product variants. Pricing, availability, and images live on each variant — a single-SKU product still returns exactly one variant carrying these. Each variant has:
id, sku, title: variant identifiers and label (all optional)
values: a map of option name to value, e.g. { "color": "Charcoal" } (optional)
price: the current price object (optional):
amount: The numeric price value
currency: The currency code, reported only when the page sources it (optional)
formatted: The price as displayed on the page (optional)
sale: present only when the variant is discounted (optional). Contains:
originalPrice: The original (pre-discount) price, same shape as price
availability: availability information, always present on a variant:
inStock: Whether the variant is in stock
text: The raw availability text from the page (optional)
images: array of variant images, each with a url and optional alt text (optional)
The product format extracts the product deterministically from on-page structured data — no LLM is involved. It merges multiple sources by priority: JSON-LD > schema.org microdata > RDFa > embedded state (__NEXT_DATA__/Nuxt/Apollo/Redux/Remix) > AliExpress runParams > GA4 dataLayer > OpenGraph/<meta>. The merge is identity-aware, so fields from different products are never combined. Currency is reported only when the page sources it.
Product extraction is fail-closed: ambiguous pages yield no product, and weaker sources such as OpenGraph only contribute when a price is present. On a page with no extractable product, the response omits the product object and adds a warning (e.g. “No product found…”).
Self-hosting: the product format is backed by a dedicated product-extraction service. On Firecrawl Cloud it works out of the box. If you self-host, set PRODUCT_EXTRACTION_SERVICE_URL to point at that service — when it is unset, requesting the product format returns a warning and no product (the same pattern the audio/video formats use for their service).
You can combine the product format with other formats to get comprehensive page data:
from firecrawl import Firecrawlfirecrawl = Firecrawl( # No API key needed to get started — add one for higher rate limits: # api_key='fc-YOUR_API_KEY',)result = firecrawl.scrape( url='https://example.com/products/wireless-headphones', formats=['markdown', 'product'])print(result['markdown'])print(result['product'])
import { Firecrawl } from 'firecrawl';const firecrawl = new Firecrawl({ // No API key needed to get started — add one for higher rate limits: // apiKey: "fc-YOUR-API-KEY",});const result = await firecrawl.scrape('https://example.com/products/wireless-headphones', { formats: ['markdown', 'product']});console.log(result.markdown);console.log(result.product);
# No API key needed to get started — add -H "Authorization: Bearer $FIRECRAWL_API_KEY" for higher rate limits:curl -s -X POST "https://api.firecrawl.dev/v2/scrape" \ -H "Content-Type: application/json" \ -d '{ "url": "https://example.com/products/wireless-headphones", "formats": ["markdown", "product"] }'
The audio format extracts audio from supported websites (e.g. YouTube) as MP3 files and returns a signed Google Cloud Storage URL. This is useful for building audio processing pipelines, transcription services, or podcast tools.
Audio extraction costs 5 credits per page (1 base + 4 additional).
from firecrawl import Firecrawlfirecrawl = Firecrawl( # No API key needed to get started — add one for higher rate limits: # api_key="fc-YOUR-API-KEY",)doc = firecrawl.scrape("https://www.youtube.com/watch?v=dQw4w9WgXcQ", formats=["audio"])print(doc.audio) # Signed GCS URL to the MP3 file
import { Firecrawl } from 'firecrawl';const firecrawl = new Firecrawl({ // No API key needed to get started — add one for higher rate limits: // apiKey: "fc-YOUR-API-KEY",});const doc = await firecrawl.scrape('https://www.youtube.com/watch?v=dQw4w9WgXcQ', { formats: ['audio']});console.log(doc.audio); // Signed GCS URL to the MP3 file
# No API key needed to get started — add -H "Authorization: Bearer $FIRECRAWL_API_KEY" for higher rate limits:curl -s -X POST "https://api.firecrawl.dev/v2/scrape" \ -H "Content-Type: application/json" \ -d '{ "url": "https://www.youtube.com/watch?v=dQw4w9WgXcQ", "formats": ["audio"] }'
The video format extracts best-quality video from supported websites (e.g. YouTube) and returns a signed Google Cloud Storage URL. This is useful for building video processing pipelines, moderation tools, or media archiving workflows.
Video extraction costs 5 credits per page (1 base + 4 additional).
from firecrawl import Firecrawlfirecrawl = Firecrawl( # No API key needed to get started — add one for higher rate limits: # api_key="fc-YOUR-API-KEY",)doc = firecrawl.scrape("https://www.youtube.com/watch?v=dQw4w9WgXcQ", formats=["video"])print(doc.video) # Signed GCS URL to the video file
import { Firecrawl } from 'firecrawl';const firecrawl = new Firecrawl({ // No API key needed to get started — add one for higher rate limits: // apiKey: "fc-YOUR-API-KEY",});const doc = await firecrawl.scrape('https://www.youtube.com/watch?v=dQw4w9WgXcQ', { formats: ['video']});console.log(doc.video); // Signed GCS URL to the video file
# No API key needed to get started — add -H "Authorization: Bearer $FIRECRAWL_API_KEY" for higher rate limits:curl -s -X POST "https://api.firecrawl.dev/v2/scrape" \ -H "Content-Type: application/json" \ -d '{ "url": "https://www.youtube.com/watch?v=dQw4w9WgXcQ", "formats": ["video"] }'
Use the question format to ask a natural-language question about the page. Firecrawl returns the answer in the response’s answer field.
The question format costs 5 credits per page (1 base + 4 additional for the LLM call).
Options inside the format object:
question (required for type: "question"): the question to answer. Maximum 10,000 characters.
You can combine question with other formats — for example, request markdown and question together to get page content and an answer in a single call.
from firecrawl import Firecrawlfirecrawl = Firecrawl( # No API key needed to get started — add one for higher rate limits: # api_key="fc-YOUR-API-KEY",)doc = firecrawl.scrape( "https://firecrawl.dev", formats=[{"type": "question", "question": "What is Firecrawl?"}],)print(doc.answer)
import { Firecrawl } from 'firecrawl';const firecrawl = new Firecrawl({ // No API key needed to get started — add one for higher rate limits: // apiKey: "fc-YOUR-API-KEY",});const doc = await firecrawl.scrape('https://firecrawl.dev', { formats: [{ type: 'question', question: 'What is Firecrawl?' }],});console.log(doc.answer);
# No API key needed to get started — add -H "Authorization: Bearer $FIRECRAWL_API_KEY" for higher rate limits:curl -s -X POST "https://api.firecrawl.dev/v2/scrape" \ -H "Content-Type: application/json" \ -d '{ "url": "https://firecrawl.dev", "formats": [ { "type": "question", "question": "What is Firecrawl?" } ] }'
The question format is also available in /search via scrapeOptions, which runs the same extraction across each search result.
Use the highlights format to find relevant source text from the page. Firecrawl returns the selected text in the response’s highlights field.
The highlights format costs 5 credits per page (1 base + 4 additional for the LLM call).
Options inside the format object:
query (required for type: "highlights"): the source-text selection request. Maximum 10,000 characters.
You can combine highlights with other formats — for example, request markdown and highlights together to get page content and source text in a single call.
from firecrawl import Firecrawlfirecrawl = Firecrawl( # No API key needed to get started — add one for higher rate limits: # api_key="fc-YOUR-API-KEY",)doc = firecrawl.scrape( "https://firecrawl.dev", formats=[{"type": "highlights", "query": "What is Firecrawl?"}],)print(doc.highlights)
import { Firecrawl } from 'firecrawl';const firecrawl = new Firecrawl({ // No API key needed to get started — add one for higher rate limits: // apiKey: "fc-YOUR-API-KEY",});const doc = await firecrawl.scrape('https://firecrawl.dev', { formats: [{ type: 'highlights', query: 'What is Firecrawl?' }],});console.log(doc.highlights);
# No API key needed to get started — add -H "Authorization: Bearer $FIRECRAWL_API_KEY" for higher rate limits:curl -s -X POST "https://api.firecrawl.dev/v2/scrape" \ -H "Content-Type: application/json" \ -d '{ "url": "https://firecrawl.dev", "formats": [ { "type": "highlights", "query": "What is Firecrawl?" } ] }'
The highlights format is also available in /search via scrapeOptions, which runs the same extraction across each search result.
Set redactPII: true to redact personally identifiable information from returned markdown. The markdown field contains the redacted result.See PII Redaction for SDK, cURL, CLI, and MCP examples.
Firecrawl allows you to perform various actions on a web page before scraping its content. This is particularly useful for interacting with dynamic content, navigating through pages, or accessing content that requires user interaction.
We recommend Interact over actions: our newer, more powerful way to interact with scraped pages.Interact runs as a stateful browser session that stays alive across calls, so you can drive a page turn-by-turn with either:
Natural language for flexible, non-deterministic flows. e.g. “search for ‘wireless headphones’, filter to 4+ stars under $200, and return the results”.
Playwright or agent-browser code for deterministic steps. e.g. await page.click('#export').
Interact also supports profiles, persistent sessions, and a live embeddable browser view (with an interactive mode where end users can drive the browser themselves).
Here is an example of how to use actions to navigate to google.com, search for Firecrawl, click on the first result, and take a screenshot.It is important to almost always use the wait action before/after executing other actions to give enough time for the page to load.
{ "success": true, "data": { "markdown": "Our first Launch Week is over! [See the recap 🚀](blog/firecrawl-launch-week-1-recap)...", "actions": { "screenshots": [ "https://alttmdsdujxrfnakrkyi.supabase.co/storage/v1/object/public/media/screenshot-75ef2d87-31e0-4349-a478-fb432a29e241.png" ], "scrapes": [ { "url": "https://www.firecrawl.dev/", "html": "<html><body><h1>Firecrawl</h1></body></html>" } ] }, "metadata": { "title": "Home - Firecrawl", "description": "Firecrawl crawls and converts any website into clean markdown.", "language": "en", "keywords": "Firecrawl,Markdown,Data,Mendable,Langchain", "robots": "follow, index", "ogTitle": "Firecrawl", "ogDescription": "Turn any website into LLM-ready data.", "ogUrl": "https://www.firecrawl.dev/", "ogImage": "https://www.firecrawl.dev/og.png?123", "ogLocaleAlternate": [], "ogSiteName": "Firecrawl", "sourceURL": "http://google.com", "statusCode": 200 } }}
For workflows that require richer browser control after scraping, such as authenticated sessions, multi-step navigation, or a live view of the page, we recommend Interact over extending the actions array.
When you specify the location settings, Firecrawl will use an appropriate proxy if available and emulate the corresponding language and timezone settings. By default, the location is set to ‘US’ if not specified.
To use the location and language settings, include the location object in your request body with the following properties:
country: ISO 3166-1 alpha-2 country code (e.g., ‘US’, ‘AU’, ‘DE’, ‘JP’). Defaults to ‘US’.
languages: An array of preferred languages and locales for the request in order of priority. Defaults to the language of the specified location.
from firecrawl import Firecrawlfirecrawl = Firecrawl( # No API key needed to get started — add one for higher rate limits: # api_key="fc-YOUR-API-KEY",)doc = firecrawl.scrape('https://example.com', formats=['markdown'], location={ 'country': 'US', 'languages': ['en'] })print(doc)
import { Firecrawl } from 'firecrawl';const firecrawl = new Firecrawl({ // No API key needed to get started — add one for higher rate limits: // apiKey: "fc-YOUR-API-KEY",});const doc = await firecrawl.scrape('https://example.com', { formats: ['markdown'], location: { country: 'US', languages: ['en'] },});console.log(doc.metadata);
# No API key needed to get started — add -H "Authorization: Bearer $FIRECRAWL_API_KEY" for higher rate limits:curl -X POST "https://api.firecrawl.dev/v2/scrape" \ -H "Content-Type: application/json" \ -d '{ "url": "https://example.com", "formats": ["markdown"], "location": { "country": "US", "languages": ["en"] } }'
To make requests faster, Firecrawl serves results from cache by default when a recent copy is available.
Default freshness window: maxAge = 172800000 ms (2 days). If a cached page is newer than this, it’s returned instantly; otherwise, the page is scraped and then cached.
Performance: This can speed up scrapes by up to 5x when data doesn’t need to be ultra-fresh.
Always fetch fresh: Set maxAge to 0. Note that this bypasses the cache entirely, so every request goes through the full scraping pipeline, meaning that the request will take longer to complete and is more likely to fail. Use a non-zero maxAge if freshness on every request is not critical.
Avoid storing: Set storeInCache to false if you don’t want Firecrawl to cache/store results for this request.
Cache-only lookup: Set minAge to perform a cache-only lookup without triggering a fresh scrape. The value is in milliseconds and specifies the minimum age the cached data must be. If no cached data is found, a 404 with error code SCRAPE_NO_CACHED_DATA is returned. Set minAge to 1 to accept any cached data regardless of age.
Change tracking: Requests that include changeTracking bypass the cache, so maxAge is ignored.
Credits: Cached results still cost 1 credit per page. Caching improves speed, not credit usage.
Example (force fresh content):
from firecrawl import Firecrawlfirecrawl = Firecrawl(api_key='fc-YOUR_API_KEY')doc = firecrawl.scrape(url='https://example.com', max_age=0, formats=['markdown'])print(doc)
You can now batch scrape multiple URLs at the same time. It takes the starting URLs and optional parameters as arguments. The params argument allows you to specify additional options for the batch scrape job, such as the output formats.
It is very similar to how the /crawl endpoint works. It submits a batch scrape job and returns a job ID to check the status of the batch scrape.The sdk provides 2 methods, synchronous and asynchronous. The synchronous method will return the results of the batch scrape job, while the asynchronous method will return a job ID that you can use to check the status of the batch scrape.
If you’re using the sync methods from the SDKs, it will return the results of the batch scrape job. Otherwise, it will return a job ID that you can use to check the status of the batch scrape.
You can then use the job ID to check the status of the batch scrape by calling the /batch/scrape/{id} endpoint. This endpoint is meant to be used while the job is still running or right after it has completed as batch scrape jobs expire after 24 hours.
Firecrawl supports Zero Data Retention (ZDR) for teams with strict data handling requirements. When enabled, Firecrawl will not persist any page content or extracted data beyond the lifetime of the request.To enable ZDR, set zeroDataRetention: true in your request:
ZDR is available on Enterprise plans and must be enabled for your team. Visit firecrawl.dev/enterprise to get started.ZDR adds 1 additional credit per page on top of the base scrape cost.
Screenshots are not available in ZDR mode. Because screenshots require uploading to persistent storage, they are incompatible with the ZDR guarantee. Requests that include both zeroDataRetention: true and a screenshot format will return an error.