Build a Finance Research Agent with Live Web Search + Scraping Using Firecrawl
Ask an LLM about a company's most recent earnings. It won't refuse. It will answer confidently, cite specific numbers, and mostly be wrong.
Not hallucinating wrong. Stale wrong. LLM training data has a cutoff. Financial data has a quarterly clock. The two don't align.
A finance research agent fixes this by removing the LLM from the loop on facts. It scrapes live sources first, then hands the data to the model for analysis. The model reasons. It doesn't recall.
In this guide, you'll build a finance research agent that scrapes live data from SEC EDGAR, company IR pages, and the web, then passes it to an LLM for analysis.
TL;DR
| Problem | Solution |
|---|---|
| LLM training data goes stale quarterly | Agent scrapes live sources every run; no stale data |
| SEC filings are raw HTML/XBRL | Firecrawl's Scrape returns clean markdown; no parser needed |
| Hard to find the right source across the web | Firecrawl Search discovers relevant URLs dynamically |
| Extracting structured metrics (revenue, EPS) from long documents | Firecrawl scrape with Zod schema returns typed JSON |
What is a finance research agent?
A finance research agent is a system that combines live web retrieval with an LLM to answer financial questions using current, sourced information. The key word is live. The agent doesn't recall financial data from training. It fetches it from the web each time it runs, extracts what's relevant, and passes it as context to the model.
This is fundamentally different from a finance chatbot. A chatbot draws on training data. A finance research agent draws on the real web at query time: SEC filings, company IR pages, financial news.
Firecrawl's web scraping service handles retrieval and extraction. Point it at a financial page and it returns clean markdown or structured JSON: both ready to pass directly to an LLM.
Why financial data is hard to get right in LLM applications
The training cutoff problem
LLMs have a knowledge cutoff. For financial use cases, this is a hard constraint. A model trained on data through early 2025 has no knowledge of what happened in Q4 2025 earnings season. When you ask about a company's most recent quarter, the model either declines or, more dangerously, answers with data from a prior period. The distinction between training data vs live web data matters more in finance than almost any other domain.

@nicbstme on building AI agents for financial services.
A 2025 arXiv study evaluating 197,000+ financial questions found that LLMs display what researchers call "retrograde knowledge bias": they answer revenue questions correctly for 54% of companies when asked about 2017 data, but only 6% when asked about 1995 data. Accuracy degrades unpredictably across time periods, even for public data that has been available for decades.
The FailSafeQA benchmark found a 41% hallucination rate for financial queries under adversarial inputs. These are questions that are slightly ambiguous or incomplete, exactly the kind a real user would ask.
The cost barrier for developers
Institutional financial data is expensive.

@FundamentEdge on why finance is one of the hardest LLM verticals.
Bloomberg Terminal runs approximately $28,320–$31,980 per seat per year, according to third-party transaction records (as of May 2026). Bloomberg doesn't publish official pricing. FactSet starts around $4,000/user and scales to $30,000/user depending on modules. LSEG Workspace (formerly Refinitiv Eikon) runs $10,000–$42,000/user.
For developers building AI applications, this is a non-starter. As one developer put it on Hacker News, the major vendors are "predatory and will change the goalposts to charge you as much as you can bear."

r/algotrading: where do developers get financial data for backtesting?
The yfinance trap
yfinance is an open-source Python library that pulls financial data from Yahoo Finance. It's widely used for fetching historical prices, fundamentals, and earnings data with a simple API. The obvious workaround for developers, but it's not a real API. It scrapes Yahoo Finance's HTML. Yahoo tightened its rate limits around early 2024, and developers now report hitting YFRateLimitError after 4–5 requests per day. A tool that breaks at 5 requests/day isn't usable in any agent workflow.
Why public web data fills the gap
SEC EDGAR is a public, government-operated database. Every 10-K, 10-Q, 8-K, and earnings release is available at no cost. Company IR pages are public. Financial news is public. The data is there. The challenge is extracting it reliably from pages that use JavaScript rendering or XBRL markup. That's a smart web search and scraping problem, not a data access problem.
That's exactly what Firecrawl solves.
Key data sources for a finance research agent
| Data source | Example | Content type | Best Firecrawl primitive |
|---|---|---|---|
| SEC EDGAR | edgar.sec.gov | 10-K, 10-Q, 8-K filings | Scrape + JSON extraction |
| Company IR pages | investor.apple.com | Earnings releases, press releases | Scrape |
| Financial news | reuters.com/finance | Analysis, price action, macro context | Search + Scrape |
| Federal Reserve / Treasury | federalreserve.gov | Rate decisions, economic data | Scrape |
| Earnings call transcripts | fool.com, seekingalpha.com | Management commentary | Scrape |
| Market aggregators | finance.yahoo.com | Prices, summary data | Scrape + JSON extraction |
| Alternative data | job boards, review sites, web traffic | Hiring signals, sentiment, product traction | Search + Scrape |
The agent should use Search before scraping. Company IR page structures change frequently. SEC EDGAR filing URLs for specific filings are not guessable. A search step dynamically discovers the right current URL instead of relying on hardcoded paths that break.

r/algotrading: manually scraping 12 years of SEC EDGAR data.
Among these sources, company IR pages deserve special attention. They're often the fastest source for earnings data. A company publishes its earnings release on its IR page the moment it goes out, before any aggregator picks it up and days before the corresponding 10-Q lands on EDGAR. For time-sensitive analysis, this gap matters.

Apple's IR page, updated minutes after each earnings release. The SEC filing typically lands on EDGAR days later.
Why Firecrawl handles financial data well
Most financial data sources are not friendly to standard scrapers. Here's what makes them hard, and how Firecrawl handles each issue.
JS-heavy pages and dynamic rendering
The SEC EDGAR filing viewer uses dynamic rendering. Many company IR pages are React SPAs. A standard fetch + HTML parser returns empty or incomplete content.

What you get when you hit SEC.gov without JavaScript rendering. Firecrawl renders the page and returns the filing content.
Firecrawl renders JavaScript server-side. The same API call works on static HTML and JS-heavy SPAs. No configuration change needed.
Clean markdown by default
Firecrawl strips navigation, footers, cookie banners, and ad chrome by default. What comes back is the document content, not the page wrapper. For a 10-K filing, this means the financial statements and MD&A text, not the 30% EDGAR navigation chrome surrounding them.
Every token of noise is a token wasted in the LLM context window. Clean input makes the analysis better and the cost lower.
Structured extraction without selectors
Traditional scrapers require CSS selectors or XPaths. These break every time a site redesigns. Firecrawl's JSON extraction mode accepts a Zod schema. You describe the fields you want; Firecrawl locates them using LLM-assisted extraction. This works across different company IR page layouts without custom selectors per company.
Try it for yourself at firecrawl.dev/playground: paste any earnings release URL and see the clean markdown output.
Parsing local files
Not all financial documents live on the web. Downloaded 10-K PDFs, emailed research reports, and exported spreadsheets are common inputs for finance workflows.
Firecrawl's /parse endpoint handles these directly. Upload a PDF, DOCX, or spreadsheet and get back the same clean markdown or structured JSON that /scrape returns for web pages. Tables come back with reading order preserved. You can request structured JSON extraction in the same call.
This means one consistent pipeline handles both live web retrieval and local file processing. No separate AI PDF parser needed.
How the agent pipeline works
The finance research agent follows four steps: Search, Scrape, Extract, Analyze. Each maps to a Firecrawl primitive or the LLM layer.
Step 1: Search for relevant sources
The Search API takes a natural-language query and returns URLs with page content. For a finance agent, the query might be:
"Apple FY26 Q2 earnings release investor relations"
The response includes the URL, title, and optionally the full page content in markdown. For simple queries, the search result already contains enough context. No second scrape needed.
Searching first means the agent always finds the current, correct source, even if company IR page URLs change between quarters. For a broader comparison of deep research APIs suited to agentic research workflows, that guide covers the tradeoffs. For a comparison of all leading search tools for AI agents including Brave, Exa, Tavily, and Serper, see the full guide.
Step 2: Scrape the page into clean markdown
The Scrape API takes a URL and returns clean markdown. onlyMainContent: true strips headers, footers, and nav. What remains is the document body.
For a 10-K filing, this is hundreds of pages. The agent should scrape the filing, then chunk by section headers before passing to the LLM. Firecrawl's markdown preserves heading structure, which makes section-based chunking straightforward.
Step 3: Extract structured metrics as JSON
When you need typed values (revenue, EPS, guidance), define a Zod schema and pass it to the scrape endpoint as a JSON format object. Firecrawl extracts those values and returns structured JSON. No selectors. No regex. The schema works across different IR page layouts.
Step 4: Analyze with an LLM
The LLM receives the scraped markdown and extracted JSON as context. It's not being asked to recall financial data from training. It's being asked to reason over the data you just retrieved. This is the difference between asking a model to know something and asking it to think about something.
Always include the source URL and a retrieval timestamp in the prompt. The model can then cite its sources in the output.
Full TypeScript code: a finance research agent
Install dependencies:
npm install firecrawl ai @ai-sdk/anthropic zodSet environment variables:
FIRECRAWL_API_KEY=fc-...
ANTHROPIC_API_KEY=sk-ant-...The agent is split into two files. tools.ts defines three Firecrawl-powered agent tools. agent.ts wires them into a generateText loop with a finance-specific system prompt.
Part 1: Define the tools (tools.ts)
import { Firecrawl, type SearchResultWeb } from "firecrawl";
import { tool, zodSchema } from "ai";
import { z } from "zod";
const firecrawl = new Firecrawl({ apiKey: process.env.FIRECRAWL_API_KEY });
const FinancialMetricsSchema = z.object({
company_name: z.string(),
reporting_period: z.string(),
revenue_millions: z.number().optional(),
net_income_millions: z.number().optional(),
earnings_per_share: z.number().optional(),
revenue_guidance_next_quarter: z.number().optional(),
key_risks: z.array(z.string()).optional(),
});
export const searchFinancialSources = tool({
description: "Search the web for financial news, SEC filings, and earnings releases",
inputSchema: z.object({
query: z.string().describe('Search query, e.g. "Apple FY26 Q2 earnings investor relations"'),
limit: z.number().optional().default(5),
}),
execute: async ({ query, limit }) => {
const results = await firecrawl.search(query, { limit });
const items = (results.web ?? []) as SearchResultWeb[];
return items.map((r) => ({
url: r.url ?? "",
title: r.title ?? "",
content: r.description ?? "",
}));
},
});
export const scrapeFinancialPage = tool({
description:
"Scrape a financial page (SEC filing, earnings release, IR page) and return clean markdown",
inputSchema: z.object({
url: z.string().url().describe("URL to scrape"),
}),
execute: async ({ url }) => {
const result = await firecrawl.scrape(url, {
formats: ["markdown"],
onlyMainContent: true,
});
return { content: result.markdown?.slice(0, 10000) ?? "" };
},
});
export const extractFinancialMetrics = tool({
description:
"Extract structured financial metrics (revenue, EPS, guidance) as typed JSON from a page",
inputSchema: z.object({
url: z.string().url().describe("Earnings release or IR page URL"),
}),
execute: async ({ url }) => {
const result = await firecrawl.scrape(url, {
formats: [
{
type: "json",
schema: zodSchema(FinancialMetricsSchema).jsonSchema as Record<string, unknown>,
},
],
});
return result.json ?? {};
},
});Part 2: The agent (agent.ts)
