How do you update fifty thousand tickers without fifty thousand calls?
/exchange-details/US reported 51,837 active tickers on 2026-07-28, and a few hundred more or fewer on any other day. If your job is to store yesterday's close for all of them, calling /eod/{ticker} once per symbol means fifty-odd thousand requests every evening. That is the problem /eod-bulk-last-day/{exchange} exists to solve.
One call, one exchange, one day
The endpoint returns the last trading day's data for every symbol on an exchange in a single response. Its parameters:
exchangein the path —US,VI,LSE, and so on.type—eod(the default),splitsordividends.date— a specificYYYY-MM-DD, defaulting to the last trading day.symbols— a comma-separated subset, EOD only.filter— the single valueextended, JSON only.fmt—jsonorcsv.
A default EOD row looks like this:
{"code":"ABIN","exchange_short_name":"VI","date":"2024-11-05",
"open":54.28,"high":54.28,"low":53.96,"close":53.96,
"adjusted_close":53.96,"volume":11}
Same seven price fields as /eod, plus code and exchange_short_name so you know which instrument you are looking at. Note that adjusted_close is present, and note that the volume on that row is 11 shares — bulk output is the whole exchange, including the instruments almost nobody traded.
One column in a nightly bulk archive does not mean what its name says. adjusted_close on the row for day T is computed with the dividends and splits known on the night you fetched it, and none of the later ones. In an archive built one night at a time every row was adjusted as of its own date, so the column is the raw close under another name, and a return computed from it steps by the full split ratio on the ex-date. Use the archive's close as raw, keep the type=splits and type=dividends feeds beside it, and derive the adjusted series at read time. A bulk pull for a past date is a different case; check it first.
The thing it deliberately is not
Bulk is one day, not history. There is no from/to pair. date selects a single session. To build a five-year history for an exchange you either loop the endpoint over every past trading day, or use /eod per ticker — and for one ticker's deep history, /eod is the right tool and always was.
The split is clean and worth stating as a rule: /eod is one symbol, many days. Bulk is one day, many symbols. Every price-history pipeline is some arrangement of those two.
Three payloads from one path
The type parameter changes the response schema entirely, which is easy to miss because it is one word in a query string.
type=eodgives the OHLCV rows above.type=splitsgives{"code":"AQMS","exchange":"US","date":"2024-11-05","split":"1.000000/20.000000"}— notesplitis a string ratio, not a number, and you parse it yourself.type=dividendsgivesdividend,currency,declarationDate,recordDate,paymentDate,periodandunadjustedValue, with the date fields nullable and frequently null.
There is an inconsistency here you should not have to discover in production: the EOD rows name the exchange field exchange_short_name, while splits and dividends rows call it exchange. Same endpoint, same call pattern, two field names.
Because splits and dividends arrive here as a daily feed, this endpoint is also how you keep a corporate-actions log current — which is what you need to recompute adjusted prices yourself, as the intraday lesson described.
The extended variant
filter=extended, JSON only, adds a substantial block per row: name, type, MarketCapitalization, Beta, ema_50d, ema_200d, hi_250d, lo_250d, prev_close, change, change_p, avgvol_14d, avgvol_50d, avgvol_200d.
That is a daily screener snapshot arriving with your price update. Useful, and worth two cautions: the payload grows sharply for a large exchange, and the derived fields are the vendor's computation, not yours — you did not choose the moving-average conventions or the beta method.
Cost, and the survivorship point
A bulk request is metered as a much larger unit than a single-ticker call, for the obvious reason. The economics still favour it heavily for whole-market updates, and Course 1 covers the quota arithmetic. The practical consequence is that you want bulk requests to succeed on the first attempt: fetch once, write to durable storage, and derive everything else from your copy.
One more property, easy to overlook and quietly valuable. Ask for a past date and the response contains the symbols that were trading then — including companies that have since been delisted, acquired or renamed. A history assembled from bulk snapshots is therefore free of the survivorship problem described in survivorship-and-biases, whereas one assembled by looping today's ticker list over past dates is not. Same data source, two pipelines, and only one of them tells the truth about the past.
The update is an upsert over a trailing window, not an append. Three facts force it. The session closes before the vendor's end-of-day load finishes, so a job run at a fixed hour can meet a day still being written; /exchange-details/US reported 44,000 of 52,000 tickers updated for the previous day on 2026-07-28, and the rest arrive later or not at all. Recent rows revise, as Course 1 measured on Apple's 2026-07-24 volume. Renamed tickers reappear under a new code. So each night re-request the last five to ten sessions, write by (code, exchange, date) with replace-on-conflict, and alert when the row count for the newest date falls well below the previous one.
Try it now
- Here is
/eod-bulk-last-day/VIpinned todate=2026-09-25: its first two rows, with the row count in the caption. Set that count against the 89 active codes/exchange-symbol-list/VIheld the same day. Then check the two rows against/eod/ATS.VIand/eod/VLA.VIfor the same date, below, and confirm the numbers match. The last table is the same bulk path with no date, as it stood at the last refresh.
- Here is the same path for
USwithtype=splits(14 September 2026) and withtype=dividends(15 September 2026), first row of each. List the keys of each against the default row above: you now have three different row schemas from one endpoint. Mark the exchange field in each, and every value that arrives as a string where you might expect a number.
- Here is the path with
date=2021-09-28&symbols=TWTR: Twitter, a company that no longer trades. On 28 September 2026/eod/TWTR.USfor September 2026 returned an empty array. That row is the reason bulk snapshots are worth archiving: say what a history built by looping today's ticker list would hold for this company.