Options Data for US Stocks: End-of-Day and Historical Learn more

REVIEW COPY — End-of-Day Historical Data API

The End-of-Day Historical Data API returns the daily price history of a single instrument: open, high, low, close, adjusted close and volume, one row per trading day, for stocks, ETFs, mutual funds, indices, forex pairs and cryptocurrencies. You can aggregate the same series into weekly or monthly bars, slice it by date, sort it either way, or reduce it to a single number.

History is deep. The S&P 500 series starts on 30 December 1927 and carries 24,794 daily bars; the oldest US common stocks we hold begin on 2 January 1962. Every figure on this page was measured against the live API on 17 September 2026, and every request example runs as written.

Test Drive with "DEMO" Key
  1. You can start with "DEMO" API key to test the data for a few tickers only: AAPL.US, TSLA.US, VTI.US, AMZN.US, BTC-USD.CC and EURUSD.FOREX. For these tickers, all of our types of data (APIs), including Real-Time Data, are available without limitations.
  2. Register for the free plan to receive your API key (limited to 20 API calls per day) with access to End-Of-Day Historical Stock Market Data API for any ticker, but within the past year only. Plus a List of tickers per Exchange is available.
  3. We recommend to explore our plans, starting from $19.99, to access the necessary type of data without limitations.

This endpoint serves one symbol per request. To download a whole exchange for a single date, or to pull many tickers in one go, use the Bulk API. For today’s price while the market is still open, use the Live (Delayed) API; for intraday bars, the Intraday API.

End-of-Day Historical Data

https://eodhd.com/api/eod/{SYMBOL}?api_token=YOUR_API_TOKEN&fmt=json

Method GET. Authentication is by api_token in the query string. The default output format is CSV; add fmt=json for a JSON array. With no date range, the response is the instrument’s entire history.

Path Parameter

SYMBOL string required
Ticker in EODHD format, SYMBOL.EXCHANGE — for example AAPL.US, BP.LSE, SAP.XETRA or RELIANCE.NSE. The .US suffix may be omitted: a request for AAPL returns the same series as AAPL.US. A ticker we do not carry returns HTTP 404 Ticker Not Found, and that failed request is still billed as one call

Query Parameters

api_token string required
Your EODHD API token. Missing or invalid returns HTTP 401 Unauthenticated
fmt enum optional
Output format. Allowed values: csv, json. Any other value silently falls back to CSV (Default: csv)
period enum optional
Bar size. Allowed values: d (daily), w (weekly), m (monthly). Weekly and monthly bars are built from the daily series: OHLC spans the period and volume is summed. Any other value silently falls back to d (Default: d)
order enum optional
Sort by date. Allowed values: a (ascending, oldest first), d (descending, newest first). Any other value silently falls back to a (Default: a)
from date optional
Start of the window, YYYY-MM-DD, inclusive. Must be YYYY-MM-DD — a date written any other way is reinterpreted rather than rejected, and you get a window you did not ask for (Default: earliest available)
to date optional
End of the window, YYYY-MM-DD, inclusive. Works on its own. A window that ends before it starts, or that lies entirely in the future, returns HTTP 200 with an empty array (Default: latest available)
filter enum optional
Return one value instead of the series. Allowed values: last_date, last_open, last_high, last_low, last_close, last_volume. This list is exhaustive — anything else, including last_adjusted_close, returns an empty value

Unrecognised values are not errors. A misspelled fmt, period or order is accepted, ignored, and quietly replaced by the default, so a typo in your client produces plausible data rather than a failure. Validate these three values on your side before sending the request.

Request Example

https://eodhd.com/api/eod/MCD.US?api_token=demo&from=2024-01-02&to=2024-01-05&period=d&fmt=json
(Sign up for free to get an API token)
curl --location "https://eodhd.com/api/eod/MCD.US?api_token=demo&from=2024-01-02&to=2024-01-05&period=d&fmt=json"
(Sign up for free to get an API token)
$curl = curl_init();

curl_setopt_array($curl, array(
    CURLOPT_URL => 'https://eodhd.com/api/eod/MCD.US?api_token=demo&from=2024-01-02&to=2024-01-05&period=d&fmt=json',
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_ENCODING => '',
    CURLOPT_MAXREDIRS => 10,
    CURLOPT_TIMEOUT => 0,
    CURLOPT_FOLLOWLOCATION => true,
    CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
    CURLOPT_CUSTOMREQUEST => 'GET',
));

$data = curl_exec($curl);
curl_close($curl);

try {
    $data = json_decode($data, true, 512, JSON_THROW_ON_ERROR);
    var_dump($data);
} catch (Exception $e) {
    echo 'Error. '.$e->getMessage();
}
(Sign up for free to get an API token)
import requests

url = f'https://eodhd.com/api/eod/MCD.US?api_token=demo&from=2024-01-02&to=2024-01-05&period=d&fmt=json'
data = requests.get(url).json()

print(data)
(Sign up for free to get an API token)
library(httr)
library(jsonlite)

url <- 'https://eodhd.com/api/eod/MCD.US?api_token=demo&from=2024-01-02&to=2024-01-05&period=d&fmt=json'
response <- GET(url)

if (http_type(response) == "application/json") {
    content <- content(response, "text", encoding = "UTF-8")
    cat(content)
} else {
    cat("Error while receiving data\n")
}
(Sign up for free to get an API token)
New to coding? Our ChatGPT assistant can generate code in any language tailored to our API. Simply describe how you want to use our data, and get a working piece of code. Don’t forget to replace the API token with your own.

Try it now (it's free)!

How to use it (YouTube)

Response Example, JSON

The first two of the four rows the request above returns:

[
  {
    "date": "2024-01-02",
    "open": 295.05,
    "high": 297.28,
    "low": 295.05,
    "close": 297.04,
    "adjusted_close": 277.9465,
    "volume": 4458400
  },
  {
    "date": "2024-01-03",
    "open": 297,
    "high": 297.99,
    "low": 294.25,
    "close": 294.39,
    "adjusted_close": 275.4668,
    "volume": 3114800
  }
]

Response Example, CSV

Drop fmt=json and the same request returns CSV with a header row — the format Excel, Google Sheets and pandas read without any parsing code:

Date,Open,High,Low,Close,Adjusted_close,Volume
2024-01-02,295.05,297.28,295.05,297.04,277.9465,4458400
2024-01-03,297,297.99,294.25,294.39,275.4668,3114800
2024-01-04,295.32,297.27,290.92,291.74,272.9872,4615400
2024-01-05,289.21,290.33,287.2,288.99,270.4139,3407300

Response Fields

FieldTypeDescription
datestring (date)Trading date, YYYY-MM-DD. For weekly and monthly bars, the first trading day of the period
opennumberOpening price, as traded — not adjusted
highnumberHighest price of the period, as traded
lownumberLowest price of the period, as traded
closenumberClosing price, as traded — not adjusted
adjusted_closenumberClosing price adjusted for both splits and dividends
volumeintegerTraded volume, adjusted for splits. Summed across the period for weekly and monthly bars

Aggregation is arithmetic, not a separate feed. The weekly bar for the week of 2 January 2024 on MCD.US opens at 295.05 (Tuesday’s open), closes at 288.99 (Friday’s close), spans a high of 297.99 and a low of 287.20, and reports a volume of 15,595,900 — exactly the sum of the four daily volumes above.

Raw, Split-Adjusted and Fully Adjusted Prices

This is the single most common source of confusion with end-of-day data, so it is worth being precise. The OHLC fields are raw — the prices as they printed on the tape that day, with no adjustment for anything. The adjusted_close field is adjusted for both splits and dividends. The volume field is adjusted for splits only.

Apple’s 4-for-1 split on 31 August 2020 makes the three views easy to see. All three rows below are the same trading day, 27 August 2020:

ViewSourceCloseVolume
Raw, as tradedclose from this API500.04155,552,400
Split-adjustedTechnical API, function=splitadjusted125.01155,552,400
Split- and dividend-adjustedadjusted_close from this API121.1519155,552,400

500.04 divided by four is 125.01, which is why a chart of raw closes shows a 75% crash on 31 August 2020 that never happened. The further step down to 121.1519 is the accumulated dividend adjustment. Volume is identical in all three rows because it is already split-adjusted in the base series. If you need OHLC adjusted for splits but not dividends, use the Technical API with function=splitadjusted; the split and dividend events themselves come from the Splits and Dividends API.

Adjusted closes are recomputed, not stored. Every new dividend re-scales the whole history behind it, so the adjusted_close for a day in 2024 is a different number this month than it was last month — by design, and for every vendor that publishes adjusted prices. Never treat an adjusted close as a stable key, never compare a cached one against a fresh one to detect a data change, and re-download the series rather than patching the tail onto it.

Symbols, Exchanges and History Depth

Tickers follow the SYMBOL.EXCHANGE convention, where the suffix is our exchange code rather than a MIC — US for all American venues, LSE for London, XETRA for Frankfurt, PA for Euronext Paris, NSE for India, FOREX for currency pairs, CC for crypto and INDX for indices. The full list comes from the Exchanges API, the tickers inside one exchange from its symbol list, and if you only know a company name, the Search API resolves it.

The table below is a live sample across asset classes, measured on 17 September 2026 — first available bar and total number of daily bars returned by a single request:

SymbolInstrumentHistory startsDaily bars
GSPC.INDXS&P 500 index1927-12-3024,794
DJI.INDXDow Jones Industrial Average1950-01-0219,693
IBM.USUS common stock1962-01-0216,285
MCD.USUS common stock1966-07-0515,150
AAPL.USUS common stock1980-12-1211,532
BP.LSELondon Stock Exchange1988-07-019,799
SAP.XETRAXETRA, Germany1994-09-137,976
RELIANCE.NSENSE, India1996-01-017,718
AIR.PAEuronext Paris1999-06-046,980
VTI.USUS ETF2001-05-316,361
EURUSD.FOREXForex pair2002-05-068,171
BTC-USD.CCCryptocurrency2010-07-135,911

Mutual funds are covered too and behave like any other symbol, except that volume is reported as zero because funds do not trade on an order book.

Delisted and renamed symbols behave differently, and the difference catches people out. A delisted ticker keeps its history: TWTR.US still returns Twitter’s prices up to the day it stopped trading in October 2022 — see Delisted Stock Companies Data for the full list. A renamed ticker does not — FB.US returns an empty array, because that series now lives under META.US. When a long history stops unexpectedly, check the US Stock Symbol Rename History API before concluding the data is missing.

Single Value Output

The filter parameter collapses the response to one value — the latest date, open, high, low, close or volume. It is built for spreadsheets: a single cell formula such as Excel’s WEBSERVICE or Google Sheets’ IMPORTDATA gets a number it can use directly, with nothing to parse.

https://eodhd.com/api/eod/MCD.US?api_token=demo&filter=last_close&fmt=json
(Sign up for free to get an API token)
curl --location "https://eodhd.com/api/eod/MCD.US?api_token=demo&filter=last_close&fmt=json"
(Sign up for free to get an API token)
$curl = curl_init();

curl_setopt_array($curl, array(
    CURLOPT_URL => 'https://eodhd.com/api/eod/MCD.US?api_token=demo&filter=last_close&fmt=json',
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_ENCODING => '',
    CURLOPT_MAXREDIRS => 10,
    CURLOPT_TIMEOUT => 0,
    CURLOPT_FOLLOWLOCATION => true,
    CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
    CURLOPT_CUSTOMREQUEST => 'GET',
));

$data = curl_exec($curl);
curl_close($curl);

try {
    $data = json_decode($data, true, 512, JSON_THROW_ON_ERROR);
    var_dump($data);
} catch (Exception $e) {
    echo 'Error. '.$e->getMessage();
}
(Sign up for free to get an API token)
import requests

url = f'https://eodhd.com/api/eod/MCD.US?api_token=demo&filter=last_close&fmt=json'
data = requests.get(url).json()

print(data)
(Sign up for free to get an API token)
library(httr)
library(jsonlite)

url <- 'https://eodhd.com/api/eod/MCD.US?api_token=demo&filter=last_close&fmt=json'
response <- GET(url)

if (http_type(response) == "application/json") {
    content <- content(response, "text", encoding = "UTF-8")
    cat(content)
} else {
    cat("Error while receiving data\n")
}
(Sign up for free to get an API token)
New to coding? Our ChatGPT assistant can generate code in any language tailored to our API. Simply describe how you want to use our data, and get a working piece of code. Don’t forget to replace the API token with your own.

Try it now (it's free)!

How to use it (YouTube)

The two output formats differ here, which matters if you are parsing the result. With fmt=json the body is a bare JSON scalar — a number such as 248.56 for last_close, or a quoted date string such as “2026-09-16” for last_date. In CSV, the default, the body is two lines: a header reading Value, then the number. A filtered request still costs one API call.

End-of-Day Historical Prices Update Time

We update each stock exchange 2-3 hours after the market closes. Major US exchanges, NYSE and NASDAQ, are updated within 15 minutes after the market closes. US mutual funds, PINK, OTCBB, and some indices update only the next morning, starting at 3-4 am ET and usually ending at 5-6 am ET. For these symbols we always hold the updated price up to 3-4 am.

Instruments that never close are the exception. Crypto and forex pairs trade around the clock, so their last bar is the current UTC day while every equity, index and fund still shows the previous session — at 06:51 UTC on 17 September 2026, BTC-USD.CC and EURUSD.FOREX were already stamped 2026-09-17 while every exchange-traded symbol read 2026-09-16. Treat the last row of a 24/7 series as a partial bar that is still being written, not a settled close.

Errors

StatusBodyCause
401UnauthenticatedThe api_token is missing, malformed or invalid
403ForbiddenThe token is valid but not entitled to that symbol — most often the demo key, which covers only a handful of tickers
404Ticker Not Found.We do not carry that symbol, or the exchange suffix does not exist. Both cases return the same message
200empty arrayNot an error: the window ends before it starts, lies entirely in the future, or the symbol has been renamed away

Under the demo token the entitlement check runs before the symbol lookup, so an unknown ticker comes back as 403, not 404. Use your own token when you are testing error handling.

A 404 is billed like any other request — one API call — so a loop over an unverified ticker list consumes your daily allowance at the same rate as a loop over a good one. Check the list against the Exchanges API first.

Plans and API Calls

End-of-day data is included in every paid plan. The free plan gives you the same endpoint for any ticker, but only the past year of history and 20 API calls a day. Each request — whatever its length, and including a 404 — costs one API call against your daily limit. Requests are also capped per minute, independently of the call budget. The full accounting of calls, requests and daily limits is on the API Limits page, and the live counter for your own account is on the dashboard.

Sign up & Get Data

Compare plans and find your fit
Free and paid plans for individual and commercial use
Go to Pricing
Chat