Good decisions come from good data. Bad decisions come from a CSV you scraped off a website that changed its layout three weeks ago.
That's the part nobody tells you when you start building anything that touches stock prices: getting the numbers is easy. Getting numbers you can actually trust, at scale, for years of history, without your script breaking every time a website updates its frontend, is a different problem.
If you're:
- backtesting a trading strategy,
- building a portfolio tracker,
- or feeding price history into an AI agent,
this is the part of your stack that decides whether the rest of it means anything.
Why web scraping isn't a real solution
The instinct is understandable. Yahoo Finance shows the price right there in the browser. Why not just scrape it?
Because that price on the page isn't built for you. It's built for a human reading a chart, and the moment you try to automate against it, you inherit every problem that comes with scraping a site that was never designed to be an API.
The layout changes without notice. Your parser breaks on a Tuesday and you don't find out until your backtest returns garbage numbers. Rate limits and IP blocks show up with no warning and no documentation to check against. And free sources like Yahoo Finance don't guarantee you're getting the same adjusted price twice: request the same historical date on two different days and you can get two different numbers, because the adjustment recalculates as new splits and dividends occur.
None of this is a minor inconvenience. If your backtest is quietly using unadjusted closes for half your date range, your Sharpe ratio is a work of fiction.
There are three mistakes almost everyone makes the first time they touch stock data programmatically:
-
Mixing up
closeandadjusted_close. The raw close doesn't account for splits or dividends. If you compute returns from raw close, a stock that did a 4:1 split shows up as an 75% crash on your chart. It didn't crash. Your data did. - Ignoring pagination and date ranges on long history pulls. Thirty years of daily bars for one ticker is over 7,500 rows. Pull it wrong and you'll silently truncate history without an error telling you so.
- Hardcoding the API token in the script. It ends up in a GitHub commit within a month. Every time.
The real problem is reliability, not access
Getting stock prices was never the hard part. Getting consistent, correctly adjusted, well-documented stock prices, for enough history to build something real, is what actually matters.
This is where a proper data API earns its keep.
EODHD's End-of-Day API
EODHD covers more than 150,000 tickers across stocks, ETFs, funds, indices, forex, and crypto, with history going back over 30 years for many US symbols (Ford's data starts in June 1972). Bars are available daily, weekly, or monthly, and every request returns the full price series for a symbol, or a sliced date range if you set one.
You can test it right now without registering. The DEMO key gives full, unrestricted access to a handful of tickers: AAPL.US, TSLA.US, VTI.US, AMZN.US, BTC-USD.CC, and EURUSD.FOREX.
If you're already testing something with a client, drop the affiliate link and this CTA here as a soft nudge:
Try it before you build on it
Test the End-of-Day API right now with the free DEMO key, no signup required.
→ Get your API token
EODHD vs web scraping vs Yahoo Finance
1. EODHD — structured API, paid tiers
A REST API with documented parameters, consistent adjusted pricing, and 30 years of depth for major US tickers.
Pros
- Consistent
adjusted_closecalculated the same way every time you query it - 150,000+ tickers across stocks, ETFs, forex, and crypto, one auth method for all of them
- Official libraries for Python, R, Node, PHP, and an MCP server for AI agents
Cons
- Free plan caps at 20 calls/day and one year of history
- Paid plans start at $19.99/month, which is a real cost for a side project
Best for: anything that needs to run unattended, backtests, dashboards, agents, and can't tolerate silent data errors.
2. Web scraping — free, fragile
Parsing HTML off a finance site instead of calling an API it wasn't built to expose.
Pros
- No cost, no signup
- Works on literally any page with visible data
Cons
- Breaks the moment the site changes its markup, with no warning and no changelog to check
- No guarantee the adjusted price is calculated consistently between two requests
- Sits in a legal gray zone against most sites' terms of service
Best for: a one-off manual check. Nothing you plan to run twice.
3. Yahoo Finance (unofficial endpoints) — free, unstable
The most common free fallback, usually accessed through unofficial wrappers rather than a supported API.
Pros
- Free, and covers a huge range of tickers
- Widely documented in tutorials, easy to get something running fast
Cons
- No official API. Every wrapper depends on endpoints Yahoo can change or kill without notice
- Historical adjusted prices have been reported to shift retroactively between pulls
- No SLA, no support channel, no changelog when something breaks
Best for: quick prototyping when correctness doesn't matter yet.
Getting the data with Python
There are two ways to do this. Raw requests, or the official eodhd library.
Option 1: Raw requests
import requests
url = "https://eodhd.com/api/eod/MCD.US"
params = {
"api_token": "YOUR_TOKEN",
"from": "2024-01-02",
"to": "2024-01-05",
"period": "d",
"fmt": "json"
}
response = requests.get(url, params=params)
data = response.json()
print(data)
Output:
[
{
"date": "2024-01-02",
"open": 295.05,
"high": 297.28,
"low": 295.05,
"close": 297.04,
"adjusted_close": 279.9221,
"volume": 4458400
},
{
"date": "2024-01-03",
"open": 297,
"high": 297.99,
"low": 294.25,
"close": 294.39,
"adjusted_close": 277.4248,
"volume": 3114800
}
]
Notice the gap between close and adjusted_close. That's not a bug, it's McDonald's dividend history baked into the price. close is the raw traded price. adjusted_close accounts for both splits and dividends, and volume is adjusted for splits. If you need OHLC adjusted for splits only (not dividends), that's a separate call to the Technical Indicators endpoint with function=splitadjusted.
Key parameters worth knowing:
-
period:d,w, ormfor daily, weekly, monthly bars -
order:afor oldest-first,dfor newest-first -
filter: skips the whole series and returns one bare value instead,last_close,last_volume, and similar options exist. Useful if you just need today's number for a spreadsheet function and don't want to parse an array for it
Option 2: The official eodhd Python library
pip install eodhd -U
from eodhd import APIClient
api = APIClient("YOUR_TOKEN")
resp = api.get_eod_historical_stock_market_data(
symbol="MCD.US",
period="d",
from_date="2024-01-02",
to_date="2024-01-05",
order="a"
)
print(resp)
Same data, less boilerplate. The library also wraps fundamentals, intraday data, splits, dividends, screener queries, and technical indicators under one client, so if this is the first of several EODHD endpoints you'll be touching, it's worth installing from the start.
Turning the response into something usable
A raw JSON array is fine for a script. For anything you're going to analyze, you want it in pandas.
import pandas as pd
import requests
url = "https://eodhd.com/api/eod/AAPL.US"
params = {
"api_token": "YOUR_TOKEN",
"from": "2025-01-01",
"period": "d",
"fmt": "json"
}
data = requests.get(url, params=params).json()
df = pd.DataFrame(data)
df["date"] = pd.to_datetime(df["date"])
df.set_index("date", inplace=True)
annual_return = (df["adjusted_close"].iloc[-1] / df["adjusted_close"].iloc[0]) - 1
print(f"AAPL return: {annual_return:.2%}")
Nine lines, and you've got a proper time-indexed DataFrame with a return calculation that actually accounts for corporate actions. Try the same thing with a raw close from a scraped page and watch your number quietly drift every time there's a split.
From here you can build:
- a stock screener that filters by return over a custom window
- a risk model comparing volatility across a basket of tickers
- a scheduled job that refreshes a portfolio dashboard every night after market close
Where this fits into agentic workflows
The EOD endpoint on its own is a data pull. Wire it into an MCP server or a connector API, and it becomes something an AI agent can query on its own, without you writing a new integration every time you want to ask a different question.
EODHD ships an official MCP server with two versions: v1 authenticates with an API key in the URL and works with ChatGPT, Claude Code, and custom agents. v2 runs OAuth 2.1 with dynamic client registration, built for Claude Desktop and other MCP clients with native OAuth support. Either version exposes 72 read-only tools across market data, fundamentals, news, and technicals, plus over 100 pages of API documentation embedded as MCP resources, so an agent can look up parameter names and plan coverage without spending an API call to do it.
In practice, that means you can point an agent at the server and ask a question in plain language instead of writing a new integration for it. Here's what that looks like calling Claude directly with the EODHD MCP server attached:
import anthropic
client = anthropic.Anthropic()
response = client.messages.create(
model="claude-sonnet-4-6",
max_tokens=1024,
mcp_servers=[
{
"type": "url",
"url": "https://mcp.eodhd.com/v2/mcp",
"name": "eodhd-mcp"
}
],
messages=[
{
"role": "user",
"content": "Pull McDonald's daily closes for Q1 2024 and flag any day where volume was 3x the 30-day average."
}
]
)
print(response.content)
No hardcoded ticker logic, no separate script for volume spikes. The agent calls the EOD tool, pulls the window it needs, computes the rolling average, and flags the outliers, all inside one request. Change the question next week ("do the same for AAPL and TSLA, compare them") and there's no new code to write, just a different prompt.
That's the shift agentic AI brings to financial data: the API doesn't just answer one hardcoded question, it becomes something an agent can reason over.
If you're building or advising on this kind of stack, this is worth testing directly:
Building an AI agent that needs financial data?
The EODHD MCP server gives your agent structured access to 30+ years of price history, no custom API wrapper required.
→ Explore the MCP server
Key takeaways
-
adjusted_closeis the number you want for return calculations, notclose. Confusing the two is the single most common mistake in this space. - The
filterparameter (last_close,last_volume, etc.) skips full-series parsing when you only need one number. - Web scraping and free sources like Yahoo Finance trade reliability for convenience. That trade gets expensive the moment your analysis needs to be reproducible.
FAQs
❓ Is there a free end-of-day stock data API?
✅ Yes. EODHD's free plan includes the End-of-Day API with 20 calls per day, limited to the past year of history. The DEMO key gives unlimited access to six test tickers with no signup at all.
❓ What's the difference between close and adjusted_close?
✅ close is the raw traded price for that day. adjusted_close factors in every split and dividend since, so historical prices stay comparable across time. Always use adjusted_close for return calculations and charting.
❓ Can an AI agent query this data directly?
✅ Yes, through EODHD's official MCP server. It exposes 72 tools over the Model Context Protocol so agents like Claude can request market data, fundamentals, and technicals directly in a conversation, without a custom API integration.
Looking for technical content for your company? I can help — LinkedIn · kevinmenesesgonzalez@gmail.com
Want to see more breakdowns like this?
I write about fintech APIs, Python, and AI agent tooling.
→ Visit kevinmeneses.com
Top comments (0)