All guides
TOOL · FIRECRAWL · BEGINNER · ZERO ASSUMED

Firecrawl: scrape any website in 3 lines.

Most websites are JS-heavy and break basic curl. Firecrawl handles the rendering, extracts clean markdown, and gives your agent text it can actually reason on.

▸ When you're done

You can hand a URL to your agent and get clean structured content back. Works on SPAs, login walls, paginated lists. Free tier covers your first 500 pages.

8 min walkthrough
2 tools · all free tier
Copy-paste ready · no theory
The stack
◢ The build · 4 steps · 8 min

Follow these in order. Don't skip.

Step 01 / 04

Sign up + grab your API key

Firecrawl docs
STEP 01
firecrawl.dev → Sign up → Dashboard → API Keys.
.env.local
1FIRECRAWL_API_KEY=fc-...
Step 02 / 04

Scrape one URL → clean markdown

Terminal · install
1npm install @mendable/firecrawl-js
src/lib/firecrawl.ts
1import FirecrawlApp from "@mendable/firecrawl-js";
2
3const fc = new FirecrawlApp({ apiKey: process.env.FIRECRAWL_API_KEY! });
4
5export async function scrape(url: string) {
6 const res = await fc.scrapeUrl(url, {
7 formats: ["markdown", "links"],
8 onlyMainContent: true,
9 });
10 if (!res.success) throw new Error(res.error || "scrape failed");
11 return {
12 markdown: res.markdown ?? "",
13 links: res.links ?? [],
14 title: res.metadata?.title,
15 };
16}
Step 03 / 04

Crawl a whole site

When you need every product page, every doc page, every blog post — start a crawl, poll for completion.

src/lib/firecrawl.ts (additions)
1export async function crawl(url: string, opts: { limit?: number } = {}) {
2 const job = await fc.crawlUrl(url, {
3 limit: opts.limit ?? 50,
4 scrapeOptions: { formats: ["markdown"], onlyMainContent: true },
5 });
6 if (!job.success) throw new Error(job.error || "crawl failed");
7 return job.data; // array of pages
8}
Step 04 / 04

Hand it to your agent

The whole point: your agent gets clean text, not HTML soup.

src/app/api/agent/research/route.ts
1import OpenAI from "openai";
2import { scrape } from "@/lib/firecrawl";
3
4const ai = new OpenAI({ apiKey: process.env.OPENAI_API_KEY });
5
6export async function POST(req: Request) {
7 const { url, question } = await req.json();
8 const page = await scrape(url);
9
10 const completion = await ai.chat.completions.create({
11 model: "gpt-5-mini",
12 messages: [
13 {
14 role: "system",
15 content:
16 "You answer questions strictly from the provided page content. Cite the source URL.",
17 },
18 {
19 role: "user",
20 content: `URL: ${url}\n\nPAGE:\n${page.markdown.slice(0, 12000)}\n\nQUESTION: ${question}`,
21 },
22 ],
23 });
24
25 return Response.json({
26 answer: completion.choices[0].message.content,
27 source: url,
28 });
29}
You're done when
Pattern: scrape → trim to 12k chars → pass as context. This single recipe powers competitive intel, doc Q&A, lead enrichment, content analysis.
Ship-it checklist
4 CHECKS
  • Firecrawl key in .env.local AND Vercel
  • src/lib/firecrawl.ts exports scrape() and crawl()
  • An /api/agent/research endpoint that takes URL + question
  • Tested against 3 different sites (a SPA, a login-walled site, a static blog)