High Granular Tick Data API: US Stocks Learn more

Tick Data API: US Stock Market

The Tick Data API returns individual trades on the US equity market — one record per execution, with a millisecond timestamp, the price, the size, the venue that printed it and the sale conditions that qualify it. History reaches back to the first trading day of 2008, and the data covers every US venue rather than a single exchange feed.

Coverage spans approximately 80,000 tickers — current and delisted companies alike — with 16,000+ securities updated every trading day, and the archive now holds roughly 10 terabytes of history in total. It keeps growing with every session added.

This is the raw material for market-microstructure work: measuring spreads and price impact, reconstructing the tape, backtesting at the execution level, and separating on-exchange prints from off-exchange ones. Note the response shape before you write a parser — ticks come back as parallel arrays, one array per field, not as a list of objects.

Requesting ticks

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.

https://eodhd.com/api/ticks/?s=AAPL&from=1694455200&to=1694541600&limit=1000&api_token=YOUR_API_TOKEN&fmt=json

Parameters

api_token string required
Your EODHD API token
s string optional
Ticker symbol, with or without the exchange suffix — AAPL and AAPL.US are equivalent. US equities only; a non-US symbol returns HTTP 404. Omitting the parameter silently falls back to AAPL, so always pass it
from integer optional
Start of the window as a UNIX timestamp in seconds, UTC. For example 1694455200 is 2023-09-11 18:00:00 UTC
to integer optional
End of the window as a UNIX timestamp in seconds, UTC. For example 1694541600 is 2023-09-12 18:00:00 UTC
limit integer optional
Maximum number of ticks to return. When passed it must be between 1 and 10000 — anything outside that range, including 0, returns HTTP 422. When omitted there is no cap at all and the entire window is returned (Default: no limit, Range: 1-10000)
fmt enum optional
Response format. Only json is supported — fmt=csv is accepted and ignored (Default: json)

Two units in one endpoint: from and to are UNIX timestamps in seconds, UTC, while the ts field in the response is in milliseconds. Divide by 1000 before feeding a returned timestamp back into a query. Always pass both bounds explicitly — when they are omitted the endpoint falls back to yesterday, and on a non-trading day that fallback returns HTTP 404.

The 10,000 ceiling applies only when you pass limit — omitting it removes the cap entirely. The full AAPL session of 11 September 2023 comes back as 722,444 ticks in a 33 MB response, and it still costs one API call however large it is. Decide deliberately which you want.

To walk a long window in slices, page on seq rather than on time — but scope the paging to one trading day. seq is unique and strictly increasing within a session and resets at the start of the next one: in a single response spanning two sessions it drops from 511,729,487 back to 65,050, and 32 ticks end up sharing a seq with a trade from the other day. Time is no better as a cursor, because ts repeats — 56% of the ticks in that AAPL session share a millisecond with another trade.

Both bounds are inclusive, and they are compared in milliseconds: a tick is returned when from × 1000 ≤ ts ≤ to × 1000. Two consequences worth knowing. A request where from equals to matches only trades landing on that exact millisecond, so in practice it comes back empty. And two adjacent slices that share a boundary second both contain the trades printed in that second — advance from by one second, or de-duplicate on seq within the day.

Results arrive in time order: ts never decreases through a response, though it does repeat, while seq strictly increases within a session. An empty window is not an error — it returns HTTP 200, and the body is either the usual object with seven empty arrays or a bare empty JSON array, so accept both shapes. HTTP 404 means something else: an unknown symbol, a non-US symbol, a delisted symbol that falls outside tick coverage, or the yesterday fallback described above. And whatever you pass in fmt, the response is JSON.

Request Example

https://eodhd.com/api/ticks/?s=AAPL&from=1694455200&to=1694541600&limit=5&api_token=demo&fmt=json
(Sign up for free to get an API token)
curl --location "https://eodhd.com/api/ticks/?s=AAPL&from=1694455200&to=1694541600&limit=5&api_token=demo&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/ticks/?s=AAPL&from=1694455200&to=1694541600&limit=5&api_token=demo&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/ticks/?s=AAPL&from=1694455200&to=1694541600&limit=5&api_token=demo&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/ticks/?s=AAPL&from=1694455200&to=1694541600&limit=5&api_token=demo&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 demo token covers a limited set of tickers — AAPL, MSFT, TSLA and AMZN all respond — and returns HTTP 403 for anything outside it, GOOGL and SPY included. Use your own token for anything beyond a first look.

Response Example

{
    "mkt": ["V", "V", "Q", "Q", "H"],
    "price": [179.25, 179.25, 179.25, 179.25, 179.25],
    "seq": [376150475, 376150494, 376151129, 376151173, 376153538],
    "shares": [100, 100, 100, 32, 9],
    "sl": ["@   ", "@   ", "@F  ", "@F I", "@F I"],
    "sub_mkt": ["", "", "", "", ""],
    "ts": [1694455200017, 1694455200017, 1694455200017, 1694455200017, 1694455200023]
}

Every array has the same length and the same ordering, so tick n is the combination of index n across all seven arrays. The first tick above is 100 shares at 179.25 on venue V, a regular sale with no qualifiers. Two details are visible straight away: the first four trades all carry the same millisecond, and the fourth and fifth are odd lots — 32 and 9 shares — which is what the trailing I in sl marks.

Response Fields

FieldTypeDescription
tsintegerTrade timestamp, milliseconds since the UNIX epoch, UTC
pricenumberExecution price
sharesintegerSize of the trade in shares. Sizes below 100 are odd lots and are flagged with I in sl
mktstringVenue that reported the trade, as a single-character participant code — see the table below
sub_mktstringReporting facility, filled in exactly when mkt is D and empty for every on-exchange print
slstringSale conditions — a fixed-width four-position field holding several codes at once, not a single code. Spaces are meaningful padding. See the section below
seqintegerTrade sequence number — unique and strictly increasing within a trading session, and reset at the start of the next one. The right cursor for paging inside a single day, but not unique across days

Sign up & Get Data

Sale conditions — the sl field

This is the field most likely to break a parser. sl is not one code — it is a fixed-width string of four positions, and a single trade commonly carries several conditions at once. Each position holds a different family of codes:

PositionWhat it carriesValues seen in one full AAPL session
1Trade type@ regular sale on almost every print, plus C cash sale and N next day. It can also be blank — in 2008 history several hundred prints a day leave it empty
2Execution qualifierBlank, F intermarket sweep, and the digits 4, 6 and 7
3TimingBlank, T form T (outside regular hours), Z sold out of sequence
4Size and price qualifierI odd lot, W average price, V contingent trade, P prior reference price, and occasionally Q, M and X

The five most frequent values in that session, in order, were @  I (regular sale, odd lot — 412,430 prints), @ alone (regular sale, no qualifiers — 120,061), @F I (sweep and odd lot — 84,916), @F (sweep — 65,162) and @ TI (outside regular hours, odd lot — 18,671). Thirty-two distinct values appeared in the day in total.

Read each position independently and trim the padding rather than comparing the whole string, and keep values you cannot decode: the digits that turn up in position 2 are not in the code list below, which covers letters only. The individual letter codes are:

CodeConditionCodeCondition
@Regular SaleNNext Day
AAcquisitionOOpening Prints
BBunched TradePPrior Reference Price
CCash SaleQMarket Center Official Open
DDistributionRSeller
EPlaceholderSSplit Trade
FIntermarket SweepTForm T
GBunched Sold TradeUExtended trading hours, sold out of sequence
HPrice Variation TradeVContingent Trade
IOdd Lot TradeWAverage Price Trade
KRule 155 Trade (AMEX)XCross or Periodic Auction Trade
LSold LastYYellow Flag Regular Trade
MMarket Center Official CloseZSold, out of sequence

Venue codes — the mkt field

CodeVenueCodeVenue
ANYSE American, LLCMChicago Stock Exchange
BNASDAQ BX, Inc.NNew York Stock Exchange, LLC
CNYSE National, Inc.PNYSE Arca, Inc.
DFINRA Alternative Display FacilityQThe NASDAQ Stock Market, LLC
EMarket IndependentSConsolidated Quotation System
HMIAX ExchangeTThe NASDAQ Stock Market, LLC
IInternational Securities ExchangeUMembers Exchange
JCboe EDGA Exchange, Inc.WCboe Stock Exchange
KCboe EDGX Exchange, Inc.XNASDAQ PSX, Inc.
LLong-term Stock ExchangeYCboe BYX Exchange, Inc.
uOther OTC MarketsZCboe BZX Exchange, Inc.

The list above is the published mapping and it is not exhaustive — V, for one, is absent from it and accounted for about 1% of prints in the AAPL session used throughout this page. Treat mkt as an opaque single character and keep codes you do not recognise rather than discarding those trades. Expect most volume to be reported off-exchange, too: code D was 57% of ticks in that session, more than every exchange combined, and every D row carries a reporting facility in sub_mkt.

Reporting facility — the sub_mkt field

CodeFacility
(empty)On-exchange print — mkt is not D, so no reporting facility is involved
QFINRA/Nasdaq Trade Reporting Facility, Carteret
NFINRA/NYSE Trade Reporting Facility
BFINRA/Nasdaq Trade Reporting Facility, Chicago

Where to go next

Tick data is the finest granularity we publish. One step coarser is the Intraday Historical Data API, with one-minute, five-minute and one-hour bars; coarser still is the End-of-Day API. For a live stream rather than history, see the Real-Time Data API via WebSockets.

To find the symbols this API accepts, list the US universe with the Exchanges API, and check how requests are counted against your plan in API Limits.

Tick Data is also sold on its own, without a full EODHD subscription, as a standalone product on our Marketplace — useful if trade-level US history is the only dataset you need. The sidebar links to the product page.

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