Originally published on medium.com


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:
this is the part of your stack that decides whether the rest of it means anything.
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:
**close** and **adjusted_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.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 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
A REST API with documented parameters, consistent adjusted pricing, and 30 years of depth for major US tickers.
Pros
adjusted_close calculated the same way every time you query itCons
Best for: anything that needs to run unattended, backtests, dashboards, agents, and canât tolerate silent data errors.
Parsing HTML off a finance site instead of calling an API it wasnât built to expose.
Pros
Cons
Best for: a one-off manual check. Nothing you plan to run twice.
The most common free fallback, usually accessed through unofficial wrappers rather than a supported API.
Pros
Cons
Best for: quick prototyping when correctness doesnât matter yet.
There are two ways to do this. Raw requests, or the official eodhd library.
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, or m for daily, weekly, monthly barsorder: a for oldest-first, d for newest-firstfilter: 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 itpip 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.
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:
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
adjusted_close is the number you want for return calculations, not close. Confusing the two is the single most common mistake in this space.filter parameter (last_close, last_volume, etc.) skips full-series parsing when you only need one number._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