
Firecrawl Scraper
- 523 installs
- 14 repo stars
- Updated August 1, 2026
- benedictking/benedictking-skills
Firecrawl Scraper is a Claude Code skill that routes coding agents to Firecrawl API endpoints for scraping, crawling, screenshots, PDF parsing, and structured extraction when developers need reliable web data inside agen
About
Firecrawl Scraper is a MIT-licensed agent skill (version 1.0.2) that teaches Claude Code when and how to call Firecrawl API endpoints for single-page scrape, full-site crawl, batch URL processing, screenshots, and PDF parsing. The skill selects the correct endpoint from user intent—markdown, HTML, JSON, screenshot, or PDF output—and runs via Bash with network access to Firecrawl. Developers reach for Firecrawl Scraper when agents must fetch real page content, crawl documentation sites, or extract structured data without hand-writing scraper scripts or hallucinating page content. It is user-invocable and designed for Claude Code with Node.js as a runtime dependency.
- Supports scrape, crawl, map, batch-scrape, crawl-status, batch-status and batch-errors endpoints
- Two-phase architecture that isolates HTTP execution in a sub-skill to reduce token usage
- Automatically selects the correct Firecrawl endpoint based on user intent
- Returns clean markdown, HTML, JSON, screenshots or structured data from any web target
- Designed for Claude Code with Node.js and network access
Firecrawl Scraper by the numbers
- 523 all-time installs (skills.sh)
- Ranked #1,717 of 16,546 AI & Agent Building skills by installs in the Skillselion catalog
- Data as of Aug 2, 2026 (Skillselion catalog sync)
npx skills add https://github.com/benedictking/benedictking-skills --skill firecrawl-scraperAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 523 |
|---|---|
| repo stars | ★ 14 |
| Last updated | August 1, 2026 |
| Repository | benedictking/benedictking-skills ↗ |
How do agents scrape pages with Firecrawl?
Let their coding agent scrape websites, crawl pages, extract structured data, capture screenshots, or parse PDFs using the Firecrawl API.
Who is it for?
Developers who want Claude Code agents to fetch, crawl, or structure live web content through Firecrawl without writing custom scraper code.
Skip if: Developers who need offline scraping, browser automation without Firecrawl, or projects blocked from external API network calls.
When should I use this skill?
User asks to scrape a page, crawl a website, batch-scrape URLs, capture a screenshot, parse a PDF, or extract structured data from the web.
What you get
Markdown, HTML, JSON extracts, screenshots, PDF parses, and crawl result sets from target URLs.
- Scraped page content
- Crawl datasets
- Screenshots and PDF extracts
By the numbers
- Skill version 1.0.2
- Supports scrape, crawl, and batch-scrape Firecrawl endpoints
Files
Firecrawl Scraper Skill
Trigger Conditions & Endpoint Selection
Choose Firecrawl endpoint based on user intent:
- scrape: Need to extract content from a single web page (markdown, html, json, screenshot, pdf)
- crawl: Need to crawl entire website with depth control and path filtering
- map: Need to quickly get a list of all URLs on a website
- batch-scrape: Need to scrape multiple URLs in parallel
- crawl-status: Given crawl job ID, check crawl progress/results (optional
--wait) - batch-status: Given batch job ID, check batch scrape progress/results (optional
--wait) - batch-errors: Given batch job ID, retrieve batch scrape errors
Recommended Architecture (Main Skill + Sub-skill)
This skill uses a two-phase architecture:
1. Main skill (current context): Understand user question → Choose endpoint → Assemble JSON payload 2. Sub-skill (fork context): Only responsible for HTTP call execution, avoiding conversation history token waste
Execution Method
Use Task tool to invoke firecrawl-fetcher sub-skill, passing command and JSON (stdin):
Task parameters:
- subagent_type: Bash
- description: "Call Firecrawl API"
- prompt: cat <<'JSON' | node scripts/firecrawl-api.cjs <scrape|crawl|map|batch-scrape|crawl-status|batch-status|batch-errors> [--wait]
{ ...payload... }
JSONPayload Examples
1) Scrape Single Page
cat <<'JSON' | node scripts/firecrawl-api.cjs scrape
{
"url": "https://example.com",
"formats": ["markdown", "links"],
"onlyMainContent": true,
"includeTags": [],
"excludeTags": ["nav", "footer"],
"waitFor": 0,
"timeout": 30000
}
JSONAvailable formats:
"markdown","html","rawHtml","links","images","summary"{"type": "json", "prompt": "Extract product info", "schema": {...}}{"type": "screenshot", "fullPage": true, "quality": 85}
2) Scrape with Actions (Page Interaction)
cat <<'JSON' | node scripts/firecrawl-api.cjs scrape
{
"url": "https://example.com",
"formats": ["markdown"],
"actions": [
{"type": "wait", "milliseconds": 2000},
{"type": "click", "selector": "#load-more"},
{"type": "wait", "milliseconds": 1000},
{"type": "scroll", "direction": "down", "amount": 500}
]
}
JSONAvailable actions:
wait,click,write,press,scroll,screenshot,scrape,executeJavascript
3) Parse PDF
cat <<'JSON' | node scripts/firecrawl-api.cjs scrape
{
"url": "https://example.com/document.pdf",
"formats": ["markdown"],
"parsers": ["pdf"]
}
JSON4) Extract Structured JSON
cat <<'JSON' | node scripts/firecrawl-api.cjs scrape
{
"url": "https://example.com/product",
"formats": [
{
"type": "json",
"prompt": "Extract product information",
"schema": {
"type": "object",
"properties": {
"name": {"type": "string"},
"price": {"type": "number"},
"description": {"type": "string"}
},
"required": ["name", "price"]
}
}
]
}
JSON5) Crawl Entire Website
cat <<'JSON' | node scripts/firecrawl-api.cjs crawl
{
"url": "https://docs.example.com",
"formats": ["markdown"],
"includePaths": ["^/docs/.*"],
"excludePaths": ["^/blog/.*"],
"maxDiscoveryDepth": 3,
"limit": 100,
"allowExternalLinks": false,
"allowSubdomains": false
}
JSON5.1) Crawl + Wait for Completion
cat <<'JSON' | node scripts/firecrawl-api.cjs crawl --wait
{
"url": "https://docs.example.com",
"formats": ["markdown"],
"limit": 100
}
JSON6) Map Website URLs
cat <<'JSON' | node scripts/firecrawl-api.cjs map
{
"url": "https://example.com",
"search": "documentation",
"limit": 5000
}
JSON7) Batch Scrape Multiple URLs
cat <<'JSON' | node scripts/firecrawl-api.cjs batch-scrape
{
"urls": [
"https://example.com/page1",
"https://example.com/page2",
"https://example.com/page3"
],
"formats": ["markdown"]
}
JSONReturns async job response: { "success": true, "id": "<batch-id>", "url": "..." }
7.1) Batch Scrape + Wait for Completion
cat <<'JSON' | node scripts/firecrawl-api.cjs batch-scrape --wait
{
"urls": [
"https://example.com/page1",
"https://example.com/page2"
],
"formats": ["markdown"]
}
JSON7.2) Check Batch Scrape Status
node scripts/firecrawl-api.cjs batch-status <batch-id>Wait for completion:
node scripts/firecrawl-api.cjs batch-status <batch-id> --wait7.3) Get Batch Scrape Errors
node scripts/firecrawl-api.cjs batch-errors <batch-id>8) Check Crawl Status
node scripts/firecrawl-api.cjs crawl-status <crawl-id>Wait for completion:
node scripts/firecrawl-api.cjs crawl-status <crawl-id> --waitKey Features
Formats
- markdown: Clean markdown content
- html: Parsed HTML
- rawHtml: Original HTML
- links: All links on page
- images: All images on page
- summary: AI-generated summary
- json: Structured data extraction with schema
- screenshot: Page screenshot (PNG)
Content Control
onlyMainContent: Extract only main content (default: true)includeTags: CSS selectors to includeexcludeTags: CSS selectors to excludewaitFor: Wait time before scraping (ms)maxAge: Cache duration (default: 48 hours)
Actions (Browser Automation)
wait: Wait for specified timeclick: Click element by selectorwrite: Input text into fieldpress: Press keyboard keyscroll: Scroll pageexecuteJavascript: Run custom JS
Crawl Options
includePaths: Regex patterns to includeexcludePaths: Regex patterns to excludemaxDiscoveryDepth: Maximum crawl depthlimit: Maximum pages to crawlallowExternalLinks: Follow external linksallowSubdomains: Follow subdomains
Environment Variables & API Key
Two ways to configure API Key (priority: environment variable > .env):
1. Environment variable: FIRECRAWL_API_KEY 2. .env file: Place in .env, can copy from .env.example
Response Format
All endpoints return JSON with:
success: Boolean indicating successdata: Extracted content (format depends on endpoint)- For crawl: Returns job ID, use
crawl-status(or GET /v2/crawl/{id}) to check status - For batch-scrape: Returns async job response (
{ success, id, url }), usebatch-status(or GET /v2/batch/scrape/{id}) to poll status - Batch status response includes
status,total,completed,creditsUsed,expiresAt,next,data[] next: pagination URL for large/incomplete results (script returns raw response; follow manually if needed)
# Firecrawl API Key Configuration
# Get your API key from: https://www.firecrawl.dev/app/api-keys
FIRECRAWL_API_KEY=your_api_key_here
Firecrawl Fetcher Sub-skill
Note: This is an internal sub-skill, invoked by the firecrawl-scraper main skill through the Task tool.Purpose
Execute Firecrawl API calls in an independent context with context: fork, avoiding carrying main conversation context, reducing token consumption.
Received Parameters
Receives complete command through Task's prompt, using stdin for JSON:
cat <<'JSON' | node .claude/skills/firecrawl-scraper/firecrawl-api.cjs <scrape|crawl|map|batch-scrape|crawl-status|batch-status|batch-errors> [--wait]
{ ...payload... }
JSONBatch scrape workflow:
batch-scrapesubmits a batch job and returns{ success, id, url }.batch-scrape --waitsubmits and polls until the batch completes.batch-status <id> [--wait]checks batch progress; with--waitpolls to terminal status.batch-errors <id>retrieves batch scrape errors.- Batch status response includes
status,total,completed,data[], and optionallynextfor pagination.
Output
Returns Firecrawl API's JSON response as-is (pretty printed).
#!/usr/bin/env node
/**
* Firecrawl API Helper Script
* Provides a CLI wrapper around Firecrawl endpoints for skill integration.
*
* Usage:
* node firecrawl-api.js <scrape|crawl|map|batch-scrape|crawl-status|batch-status|batch-errors> [<json-string>]
* cat payload.json | node firecrawl-api.js scrape
* node firecrawl-api.js scrape --file ./payload.json
* node firecrawl-api.js crawl --wait < payload.json
* node firecrawl-api.js crawl-status <crawl-id> [--wait]
* node firecrawl-api.js batch-scrape --wait < payload.json
* node firecrawl-api.js batch-status <batch-id> [--wait]
* node firecrawl-api.js batch-errors <batch-id>
*/
const https = require('https');
const fs = require('fs');
const path = require('path');
const API_BASE = 'https://api.firecrawl.dev';
function loadApiKey() {
if (process.env.FIRECRAWL_API_KEY) {
return process.env.FIRECRAWL_API_KEY;
}
const envPath = path.join(__dirname, '..', '.env');
if (!fs.existsSync(envPath)) {
return null;
}
const envContent = fs.readFileSync(envPath, 'utf8');
const match = envContent.match(/FIRECRAWL_API_KEY\s*=\s*(.+)/);
if (!match) {
return null;
}
return match[1].trim().replace(/^[\"']|[\"']$/g, '');
}
function usage() {
const cmd = path.basename(process.argv[1] || 'firecrawl-api.js');
console.error(
[
'Usage:',
` node ${cmd} <scrape|crawl|map|batch-scrape|crawl-status|batch-status|batch-errors> [<json-string>]`,
` cat payload.json | node ${cmd} scrape`,
` node ${cmd} scrape --file ./payload.json`,
` node ${cmd} crawl --wait < payload.json`,
` node ${cmd} crawl-status <crawl-id> [--wait]`,
` node ${cmd} batch-scrape --wait < payload.json`,
` node ${cmd} batch-status <batch-id> [--wait]`,
` node ${cmd} batch-errors <batch-id>`,
'',
'Options:',
' --wait Wait for job completion (crawl / crawl-status / batch-scrape / batch-status)',
' --id Job id (crawl-status / batch-status)',
'',
'Env:',
' FIRECRAWL_API_KEY (env var) or .env file next to this script',
].join('\n'),
);
}
function readStdin() {
return new Promise((resolve, reject) => {
let data = '';
process.stdin.setEncoding('utf8');
process.stdin.on('data', (chunk) => {
data += chunk;
});
process.stdin.on('end', () => resolve(data));
process.stdin.on('error', reject);
});
}
async function readPayload(args) {
const fileFlagIndex = args.findIndex((arg) => arg === '--file');
if (fileFlagIndex !== -1) {
const filePath = args[fileFlagIndex + 1];
if (!filePath) {
throw new Error('Missing value for --file');
}
const content = fs.readFileSync(filePath, 'utf8');
return JSON.parse(content);
}
const dataFlagIndex = args.findIndex((arg) => arg === '--data');
if (dataFlagIndex !== -1) {
const json = args[dataFlagIndex + 1];
if (!json) {
throw new Error('Missing value for --data');
}
return JSON.parse(json);
}
if (args[0] && !args[0].startsWith('-')) {
return JSON.parse(args[0]);
}
if (process.stdin.isTTY) {
throw new Error('No payload provided (pass JSON arg, --data, --file, or pipe via stdin)');
}
const stdin = await readStdin();
if (!stdin.trim()) {
throw new Error('Empty stdin payload');
}
return JSON.parse(stdin);
}
function requestJson(method, endpointPath, apiKey, payload) {
return new Promise((resolve, reject) => {
const body = payload === undefined ? null : JSON.stringify(payload);
const url = new URL(endpointPath, API_BASE);
const req = https.request(
url,
{
method,
headers: {
'Authorization': `Bearer ${apiKey}`,
...(body === null
? {}
: {
'Content-Type': 'application/json',
'Content-Length': Buffer.byteLength(body),
}),
'User-Agent': 'Firecrawl-Skill/1.0',
},
timeout: 60_000,
},
(res) => {
let data = '';
res.setEncoding('utf8');
res.on('data', (chunk) => {
data += chunk;
});
res.on('end', () => {
const ok = res.statusCode && res.statusCode >= 200 && res.statusCode < 300;
if (!ok) {
reject(new Error(`API Error ${res.statusCode}: ${data}`));
return;
}
try {
resolve(JSON.parse(data));
} catch {
resolve(data);
}
});
},
);
req.on('error', reject);
req.on('timeout', () => {
req.destroy(new Error('Request timed out'));
});
if (body !== null) {
req.write(body);
}
req.end();
});
}
function sleep(ms) {
return new Promise((resolve) => setTimeout(resolve, ms));
}
function takeFlag(args, flag) {
const index = args.indexOf(flag);
if (index === -1) {
return false;
}
args.splice(index, 1);
return true;
}
function takeFlagValue(args, flag) {
const index = args.indexOf(flag);
if (index === -1) {
return null;
}
const value = args[index + 1];
if (!value) {
throw new Error(`Missing value for ${flag}`);
}
args.splice(index, 2);
return value;
}
function isTerminalSuccessStatus(status) {
return new Set(['completed', 'complete', 'done', 'success', 'succeeded', 'finished']).has(status);
}
function isTerminalFailureStatus(status) {
return new Set(['failed', 'error', 'cancelled', 'canceled']).has(status);
}
async function getCrawlStatus(apiKey, crawlId) {
const safeId = encodeURIComponent(crawlId);
return requestJson('GET', `/v2/crawl/${safeId}`, apiKey);
}
async function getBatchStatus(apiKey, batchId) {
const safeId = encodeURIComponent(batchId);
return requestJson('GET', `/v2/batch/scrape/${safeId}`, apiKey);
}
async function getBatchErrors(apiKey, batchId) {
const safeId = encodeURIComponent(batchId);
return requestJson('GET', `/v2/batch/scrape/${safeId}/errors`, apiKey);
}
function extractJobId(result) {
return [result?.id, result?.jobId, result?.data?.id, result?.data?.jobId]
.find((v) => typeof v === 'string' && v.trim().length > 0) || null;
}
function extractJobStatus(result) {
const status = result?.status ?? result?.data?.status;
if (typeof status !== 'string') return null;
return status.trim().toLowerCase();
}
async function waitForJobCompletion(apiKey, jobId, getStatus, label) {
const pollIntervalMs = 3_000;
for (;;) {
const statusResult = await getStatus(apiKey, jobId);
const status = extractJobStatus(statusResult);
if (!status || isTerminalSuccessStatus(status)) return statusResult;
if (isTerminalFailureStatus(status)) {
throw new Error(`${label} ${jobId} ended with status "${status}"`);
}
await sleep(pollIntervalMs);
}
}
const ENDPOINT_BY_COMMAND = {
scrape: '/v2/scrape',
crawl: '/v2/crawl',
map: '/v2/map',
'batch-scrape': '/v2/batch/scrape',
};
(async () => {
const command = process.argv[2];
if (!command || command === '--help' || command === '-h') {
usage();
process.exit(command ? 0 : 1);
}
const args = process.argv.slice(3);
if (args.includes('--help') || args.includes('-h')) {
usage();
process.exit(0);
}
const apiKey = loadApiKey();
if (!apiKey) {
console.error('Missing Firecrawl API key: set FIRECRAWL_API_KEY or create .env next to firecrawl-api.js');
process.exit(1);
}
try {
const wait = takeFlag(args, '--wait');
// Shared: parse job id from CLI args or JSON payload (crawl-status / batch-status)
async function parseJobId(args, explicitId) {
let jobId = explicitId;
if (!jobId) {
if (args[0] && !args[0].startsWith('-') && !args[0].trim().startsWith('{')) {
jobId = args[0];
}
const hasJsonPayload =
args.includes('--file') ||
args.includes('--data') ||
(args[0] && args[0].trim().startsWith('{')) ||
!process.stdin.isTTY;
if (!jobId && hasJsonPayload) {
const payload = await readPayload(args);
jobId = extractJobId(payload);
}
}
return jobId;
}
if (command === 'crawl-status') {
const explicitId = takeFlagValue(args, '--id');
const crawlId = await parseJobId(args, explicitId);
if (!crawlId) {
throw new Error('Missing crawl id (pass <crawl-id>, --id <crawl-id>, or a JSON payload containing id/jobId)');
}
const result = wait
? await waitForJobCompletion(apiKey, crawlId, getCrawlStatus, 'Crawl job')
: await getCrawlStatus(apiKey, crawlId);
console.log(JSON.stringify(result, null, 2));
return;
}
if (command === 'batch-status') {
const explicitId = takeFlagValue(args, '--id');
const batchId = await parseJobId(args, explicitId);
if (!batchId) {
throw new Error('Missing batch id (pass <batch-id>, --id <batch-id>, or a JSON payload containing id/jobId)');
}
const result = wait
? await waitForJobCompletion(apiKey, batchId, getBatchStatus, 'Batch scrape job')
: await getBatchStatus(apiKey, batchId);
console.log(JSON.stringify(result, null, 2));
return;
}
if (command === 'batch-errors') {
const explicitId = takeFlagValue(args, '--id');
const batchId = await parseJobId(args, explicitId);
if (!batchId) {
throw new Error('Missing batch id (pass <batch-id>, --id <batch-id>, or a JSON payload containing id/jobId)');
}
const result = await getBatchErrors(apiKey, batchId);
console.log(JSON.stringify(result, null, 2));
return;
}
const endpoint = ENDPOINT_BY_COMMAND[command];
if (!endpoint) {
usage();
process.exit(1);
}
if (wait && command !== 'crawl' && command !== 'batch-scrape') {
throw new Error('--wait is only supported for crawl, crawl-status, batch-scrape, or batch-status');
}
const payload = await readPayload(args);
const result = await requestJson('POST', endpoint, apiKey, payload);
if (wait) {
const jobId = extractJobId(result);
if (!jobId) {
throw new Error(`Missing job id in ${command} response (expected id/jobId)`);
}
const label = command === 'crawl' ? 'Crawl job' : 'Batch scrape job';
const getStatus = command === 'crawl' ? getCrawlStatus : getBatchStatus;
const finalResult = await waitForJobCompletion(apiKey, jobId, getStatus, label);
console.log(JSON.stringify(finalResult, null, 2));
return;
}
console.log(JSON.stringify(result, null, 2));
} catch (error) {
console.error(`Error: ${error.message}`);
process.exit(1);
}
})();
Related skills
How it compares
Choose Firecrawl Scraper when you already use Firecrawl and want endpoint selection baked into agent workflows instead of ad-hoc curl or custom SDK scripts.
FAQ
What Firecrawl endpoints does firecrawl-scraper use?
Firecrawl Scraper maps intent to Firecrawl endpoints: scrape for a single page, crawl for an entire site, and batch operations for multiple URLs. Output formats include markdown, HTML, JSON, screenshots, and PDF parsing depending on the request.
What does firecrawl-scraper require to run?
Firecrawl Scraper requires Claude Code, Node.js, network access to the Firecrawl API, and Bash/Read tool permissions. The skill is MIT-licensed at version 1.0.2 and is marked user-invocable in its manifest.