data_updater_v2.data_clients.fmp
FMPClient — Financial Modeling Prep (the /stable API, and its older /api/v3 where /stable gates our plan), as this container pulls it.
Every endpoint, cap and quirk below was probed live on our plan (2026-10-03/04, scratchpad
fmpu/u1-u6, cov1-cov3, v3probe* and bars/probe_*) unless marked. FMP
answers JSON lists, even for one symbol; an unknown symbol is 200 []; an error is a JSON
body, sometimes under a 200 (a rate limit says {"Error Message": "Limit Reach ..."} with
HTTP 200), which the runner reads as a fault, never as rows. Plan-gated values answer 402
(“Premium Query Parameter”).
What /stable serves on our plan, by family (census of 2026-10-04: every index, every commodity, samples of the rest):
equities, ETFs everything: daily raw / full / dividend-adjusted, intradaycrypto everythingcommodities daily ``non-split-adjusted`` and ``dividend-adjusted`` for all 40; ``full``, ``light`` and every intraday interval only for GCUSD, SIUSD, BZUSD, ESUSD (the other 36 are 402)indices 9 of 428 (^GSPC ^DJI ^IXIC ^RUT ^VIX ^N225 ^FTSE ^HSI ^STOXX50E) on ``full`` / ``light`` / intraday; the raw and dividend-adjusted series are 402 for all of them (^GSPC comes back empty)/api/v3 serves what /stable gates: daily for 410 indices and all 40 commodities, 1-minute for
382 indices and all 40 commodities, with values equal to /stable’s wherever both serve (every
OHLCV field, every day compared). It is FMP’s older API: v3_check asks whether it still
answers, and the prices section falls back to /stable the moment it does not.
Sections, one block per concept, direct pulls first, the section’s operations after:
1 · DECLARATIONS provider, base URL, auth, endpoints, walks, limits, the US venues2 · UNIVERSE lists (ETFs, equities, indices, crypto, commodities), profiles, delistings, listings, symbol changes, venues3 · CALENDAR holidays and early closes by exchange, trading hours4 · PRICES daily (raw / split-adjusted / dividend-adjusted) and intraday5 · CORPORATE ACTIONS splits and dividends: per symbol (full history) and by calendar6 · INDEX CONSTITUENTS S&P 500, Nasdaq 100, Dow 30: today's constituents and their history7 · METRICS the three finals — equity_metrics (the statement route or the market-cap route, the sweep on an update), etf_metrics and crypto_metrics (FMP's market caps, the count over our close) — on the vendor verbs: market caps, share counts (today, the sweep, the as-reported quarters), a fund's NAV / net assets / expense ratioWhat a section returns: a catalog pull (one list, or a few lists combined) returns its rows,
and REFUSES to return a partial list (FetchError), because a catalog with silent holes is
dirty data. A per-symbol pull (profile, prices, splits, dividends) returns the
RawResponse itself, so the flow sees ok / complete / coverage per symbol and
decides; one bad symbol never sinks the others.
The FMP facts every section leans on:
- Daily prices on /stable: at most 5,000 rows per call, newest first; the walk cascades
the end bound back from the oldest row it got.
non-split-adjustedis the RAW series and names its columnsadjOpen…adjCloseanyway (raw prices, misleading names; novwap).fullis split-adjusted and carriesvwap;dividend-adjustedis FMP’s own backward adjustment (cross-check only: it rescales all history on every dividend). Crypto and commodities never split: their raw and full series are equal. - Daily prices on /api/v3 (
historical-price-full/{symbols}): one symbol’s WHOLE history in one call, no row cap (^GSPC: 24,806 rows back to 1927); a comma list takes at most 5 symbols (more are dropped silently) and serves each only ONE YEAR back fromto(silently, whateverfromsays). Withoutfromit serves 5 years. One symbol is{symbol, historical}, several{historicalStockList: [...]}, none served{}(200, also for a weekend window). - Intraday
historical-chart/{interval}: two SILENT truncations — at most 5,000 rows, and only a span counted BACK fromto(1min 3 calendar days, probed repeatedly; 5min 10, 15min 45, 30min 30, 1hour 89, 4hour 180 from ONE probe each — 30min’s 273 rows are exactly 21 sessions x 13 bars, which may be a row limit rather than a span: re-probe before relying on the non-1min spans). So the walk cuts the ask into them.from/tomust be DATES: a datetime string is IGNORED and FMP serves the latest days instead (probed). US/Eastern wall-clock stamps, regular session only for US listings (09:30-15:59), no rows for closed days. Minutes reach back about 12 years for equities and ETFs (AAPL, SPY), 6 for ^GSPC and BTCUSD, 1 for GCUSD. /api/v3’shistorical-chart/{interval}/{symbol}serves the same spans back fromto, the same Eastern stamps and the same values (^GSPC, GCUSD 1min compared). - FMP’s
AMEXvenue code is NYSE Arca (exchangeFullName“New York Stock Exchange Arca”): keep both names. - One symbol per profile call: comma lists return nothing, and every bulk route is 402 on
our plan (
profile-bulk,eod-bulk,etf-holder-bulk,peers-bulk,key-metrics-ttm-bulk,scores-bulk: probed 2026-10-05), as is everybatch-*-quote.batch-quote(no asset in the name) works, 500 symbols a call (QUOTES_PER_CALL), but drops indices; /api/v3’squotes/indexquotes all 428 in one call. - The screener silently caps at 10,000 rows whatever
limitsays, so it is asked one US venue at a time (largest: NASDAQ equities, 6,327). - Dividends / splits by calendar serve only about the last 3 months on our plan (older
fromis 402); history comes per symbol, in one call each, full. - FMP revises and back-fills past dividends, and re-adjusts past minute bars on new splits inconsistently: the flows store raw values and diff revisions; nothing here adjusts.
- The full endpoint reference:
Data V2/Free Stock Market API and Financial Statements API _ FMP.pdf.
Classes
FMPClient
Bases: BaseClient
Financial Modeling Prep: one host, one key, the /stable API and /api/v3 where /stable gates our plan.
Carried state: the base’s only (the config, the owned common-http client, the borrowed
limiter and its scope, the runner). FMP owns no resource of its own, so it has no init:
declarations and sections. v3_check keeps nothing: each daily call asks anew.
calendar_floor
calendar_floor() -> dt.dateThe earliest from the calendars serve us: CALENDAR_REACH years before
today’s UTC date (FMP’s day may have turned before ours), plus a day of margin past
FMP’s own cut (2021-10-05 refused, 10-06 served on 2026-10-05).
commodities
commodities() -> list[dict[str, Any]]The ACTIVE continuous US futures: the commodity list, kept when batch-quote
quoted them within COMMODITY_FRESH (all 40 on 2026-10-04, last quoted the
Friday before). currency is USD, or USX for contracts priced in CENTS (KEUSX
wheat, ZLUSX soybean oil …).
compute_crypto_metrics
compute_crypto_metrics(caps: Iterable[Mapping[str, Any]], bars: Iterable[Any]) -> tuple[list[dict[str, Any]], list[dict[str, Any]]]A coin’s days from FMP’s market caps and our bars: each cap row with supply in
coins beside it, the market cap over the close (how FMP built it).
compute_equity_metrics
compute_equity_metrics(counts: Iterable[Mapping[str, Any]], bars: Iterable[Any], start: dt.date | None = None, end: dt.date | None = None, route: str = 'statements') -> tuple[list[dict[str, Any]], list[dict[str, Any]]]A stock’s days from its counts and our as-traded bars, no call made: one row per bar day in [start, end] carrying the latest count dated on or before that day and the market cap, the count times the close, both in the day’s own units. The counts step: a count holds until the next; a day before a symbol’s first count has no row. Returns (rows, dropped): a count row without a count, or for a symbol with no bars, dropped by reason. The split table never enters: a filed count is stepped as filed.
compute_etf_metrics
compute_etf_metrics(caps: Iterable[Mapping[str, Any]], bars: Iterable[Any]) -> tuple[list[dict[str, Any]], list[dict[str, Any]]]A fund’s days from FMP’s market caps and our as-traded bars, no call made: each
cap row with shares_outstanding beside it, the market cap over the close of its
day. A row with no bar on its day or a close of zero drops by reason.
constituent_changes
constituent_changes(index: Index) -> list[dict[str, Any]]Every add / remove in an index’s history, newest first: date, symbol /
addedSecurity, removedTicker / removedSecurity, reason (sp500 1,529 back to 1957,
nasdaq 451 back to 1985, dowjones 86 back to 1994). With symbol_changes this
rebuilds the constituents on any past day.
constituents
constituents(index: Index) -> list[dict[str, Any]]Today’s constituents of an index (sp500: 504, nasdaq: 101, dowjones: 30): symbol, name, sector, subSector, headQuarter, dateFirstAdded, cik, founded.
crypto_metrics
crypto_metrics(symbols: str | Symbols, start: dt.date | None = None, end: dt.date | None = None, daily: Iterable[Any] | Mapping[str, Iterable[Any]] | None = None) -> RawResponse | dict[str, RawResponse]A coin’s day, built or updated, as the fund’s: FMP’s market cap (the price times
the circulating supply) and, with daily, the supply in coins — the market cap
over the day’s close (compute_crypto_metrics). A coin’s bars are always as traded.
cryptos
cryptos() -> list[dict[str, Any]]The ACTIVE USD crypto pairs. FMP’s list carries no active flag (its fields:
symbol, name, exchange, icoDate, circulating / total supply), so a pair is active
when FMP still prices it: its quote was updated within CRYPTO_FRESH and it
traded volume. Probed 2026-10-04: of 4,791 USD pairs 1,974 were quoted within a
day, 1,860 of them with volume (the 114 at zero volume are kept out); the rest last
updated months or years ago (IOTA in May 2026, ECOP in Dec 2023). One
batch-quote pass (10 calls); each row carries its quote’s price.
daily
daily(symbols: str | Symbols, start: dt.date | None = None, end: dt.date | None = None, asset: Asset, splits: Iterable[Any] | Mapping[str, Iterable[Any]] | None = None) -> RawResponse | dict[str, RawResponse]Daily bars in the common shape (bars.py), one symbol or many, oldest first,
as traded (unadjusted). start None = DAILY_FLOOR; end None = today.
Equities and ETFs walk /stable’s raw series (non-split-adjusted), and so do the
STABLE_INDICES (full). Crypto, commodities and the other indices are asked
from /api/v3 when v3_check passes: five
symbols a call when the window is at most V3_SPAN days (what a comma call
serves), one symbol a call otherwise (one call is its whole history); anything it
leaves unserved, or everything when the check fails, is asked from /stable
(STABLE_DAILY). Each response keeps its facts (ok, coverage, calls); one served
out of a five-symbol call holds that call by pointer with a fifth of its cost. One
symbol gives its response; many give symbol -> response.
splits (equities and ETFs; the checked split table: models or rows with symbol in
FMP’s spelling, date, split_to, split_from — or symbol -> rows) makes the history raw
from FMP’s full series, the one its price vendor adjusts correctly: each day’s
prices times every split after it, its volume divided by them (full volume is
split-adjusted too: AAPL 2020-08-28 187.6M against 46.9M traded). FMP’s own
non-split-adjusted series is full times FMP’s OWN split list (209 of 209 names,
every day, 2026-10-08): wrong wherever that list is — CB halved before 2006-04-19 for
old Chubb’s split, LABX adjusted for a split FMP never listed. Without splits that
series is what comes back: right for a day no split has followed (the latest).
daily_as_served
daily_as_served(symbol: str, start: dt.date, end: dt.date, basis: DailyBasis = 'raw') -> RawResponseOne symbol’s daily rows exactly as /stable serves them, newest first, for
cross-checks: raw (non-split-adjusted, its raw columns named adj*), split
(full: OHLC + change, changePercent, vwap), dividend (FMP’s backward dividend
adjustment — it rescales history on every dividend). An index’s raw is asked
from full.
delisted
delisted(since: dt.date | None = None) -> list[dict[str, Any]]Delistings, newest first; with since, down to it (the boundary page comes
whole: the flow keeps rows on or after since).
delistings
delistings(since: dt.date | None = None) -> RawResponseUS delistings FMP’s delisted list dates from since on (None: all it keeps —
16,639 world-wide on 2026-10-10, back to about 2022; announced ones ahead of today
too), oldest first, in the common shape (listings.sound_delistings): symbol
(ours), date, name, exchange; the lines off the US venues or with no day (FMP’s
1970-01-01) taken out, named. A failed answer comes back as it is.
dividends
dividends(symbols: str | Symbols, start: dt.date | None = None, end: dt.date | None = None) -> RawResponse | dict[str, RawResponse]Dividend history as payouts, oldest first (_dividend): symbol, ex_date (FMP’s
date: the day the price drops by it — the date it is applied on), period, amount
(unadjusted, as paid), declaration / record / payment dates. start / end keep
the ex-dates in [start, end]. One symbol, or many as one common-http batch.
dividends_calendar
dividends_calendar(start: dt.date, end: dt.date) -> RawResponseEvery dividend with its ex-date in [start, end], world-wide, in dividends’
shape: 90-day windows, every 4,000-row page (one day can exceed 4,000 rows: paging,
not narrower windows, covers it). A start older than CALENDAR_REACH is clipped
to it and the response says clipped.
equities
equities() -> list[dict[str, Any]]The ACTIVE US exchange-traded operating companies: the screener per US venue (not
ETFs, not mutual funds, trading only), 6,080 rows on 2026-10-05. It still carries
papers that are not shares — preferreds, notes, warrants, units, rights, funds — and
says nothing about them: what each one IS comes from its profile (profiles:
security_type) and the cross-references, never from a cut here.
equity_metrics
equity_metrics(symbols: str | Symbols, start: dt.date | None = None, end: dt.date | None = None, daily: Iterable[Any] | Mapping[str, Iterable[Any]] | None = None, splits: Iterable[Any] | Mapping[str, Iterable[Any]] | None = None) -> RawResponse | dict[str, RawResponse]A stock’s day, built or updated: one answer per symbol (one symbol asked: the
answer), rows {symbol, date, shares_outstanding, market_cap, route} oldest first,
route the way the count came — statements, market_cap or sweep.
start or end given is the build, two routes. The statement route first:
today’s count per name (shares), then every quarter FMP holds (shares with a
window); a name keeps the route when its latest quarter’s count, times the settled
splits since that period end (the splits table’s rows, or symbol -> them), sits
within UNITS_TOLERANCE of today’s count. A name with no statement count at all,
or whose statement counts another class or unit, takes the market-cap route
(market_cap_history over the window); without splits a name that split since
its last quarter reads as other units and takes that route too, harmlessly. Neither
given is the update: today’s count per name, the sweep past SHARES_BULK_OVER.
With daily — our as-traded daily bars, the raw tables’ rows (dicts or models), one
iterable keyed by their own symbol or a mapping symbol -> bars — the rows are
finished: on the statement route each bar day carries the latest count filed on or
before it and the count times the close (compute_equity_metrics); on the
market-cap route the count is the cap over the close; on the update the count is dated
by the latest bar it values, since the refresh stamp lands after the session. Without
bars the statement route hands back the quarters cut to the window, the other routes
FMP’s rows. The bars must be as traded: a split-adjusted close gives today’s units
against a count in its own; the raw tables are, and nothing here checks it.
etf_infos
etf_infos(symbols: Iterable[str]) -> dict[str, RawResponse]Each ETF’s fund facts, one call each, as one common-http batch; each row read as
profiles reads a profile (an etf/info line is a product by its shape).
etf_metrics
etf_metrics(symbols: str | Symbols, start: dt.date | None = None, end: dt.date | None = None, daily: Iterable[Any] | Mapping[str, Iterable[Any]] | None = None) -> RawResponse | dict[str, RawResponse]A fund’s day, built or updated, one answer per symbol: FMP’s market cap as served
and, with daily (our as-traded bars, as equity_metrics takes them), the
count beside it — the market cap over the close of its day, both the day’s own
(compute_etf_metrics). start or end given is the build,
market_cap_history over the window; neither is the update, today’s batch
(market_caps). A day with no bar of ours, or a close of zero, drops by reason.
What FMP’s number is, and how far it sits from the fund’s own AUM, is told on
market_cap_history; NAV, net assets and the expense ratio are navs.
etfs
etfs() -> list[dict[str, Any]]The ACTIVE US exchange-traded ETF universe: the screener, one US venue at a time,
trading only (ETNs included: FMP marks them isEtf too). Probed 2026-10-04:
5,747 rows; every US-listed name in etf-list is among them. One row per symbol.
fetch_active_list
fetch_active_list() -> RawResponseEvery stock and ETF trading today, world-wide: symbol, name. Alive = in this list.
fetch_commodity_list
fetch_commodity_list() -> RawResponseThe 40 continuous futures FMP serves (GCUSD gold, CLUSD crude, ESUSD E-mini, …):
symbol, name, tradeMonth, currency. FMP carries no venue for them (exchange is
null; COMEX / NYMEX / CBOT / ICE are not anywhere in FMP).
fetch_crypto_list
fetch_crypto_list() -> RawResponseEvery crypto pair (4,793), one aggregate venue CCC: symbol, name, icoDate,
circulatingSupply, totalSupply. Nearly all are USD pairs.
fetch_delisted
fetch_delisted(since: dt.date | None = None) -> RawResponseDelistings newest first, 100 a page, world-wide, announced ones ahead of today:
symbol, companyName, exchange, ipoDate, delistedDate. since stops the walk at
the first page reaching back past it (that page comes whole, and the response says
complete=False because we, not the vendor, ended it).
fetch_etf_info
fetch_etf_info(symbol: str) -> RawResponseOne ETF’s fund facts: assetClass, domicile, etfCompany (the issuer), expenseRatio, assetsUnderManagement, inceptionDate, nav, navCurrency, holdingsCount, sectorsList, isin, securityCusip, isActivelyTrading, updatedAt.
fetch_etf_list
fetch_etf_list() -> RawResponseEvery ETF FMP carries, world-wide, alive or dead: symbol, name.
fetch_exchanges
fetch_exchanges() -> RawResponseFMP’s venue catalog: 63 venues, exchange code, name, country, suffix, delay.
fetch_holidays
fetch_holidays(exchange: str = 'NYSE', start: dt.date | None = None, end: dt.date | None = None) -> RawResponseOne exchange’s closures AND early closes, newest first: exchange, date, name, isClosed, adjOpenTime / adjCloseTime (the shortened session). History reaches back to 1990; announced dates run ahead (to the end of next year). Without bounds FMP serves only the last ~12 months, so bounds are always sent.
fetch_index_list
fetch_index_list() -> RawResponseEvery index FMP serves (428): symbol (^ prefix), name, exchange, currency.
fetch_ipos
fetch_ipos(start: dt.date, end: dt.date) -> RawResponseNew listings by date, world-wide: symbol, date, company, exchange, actions, shares, priceRange, marketCap. History reaches back to about 2005.
fetch_market_hours
fetch_market_hours(exchange: str = 'NYSE') -> RawResponseOne exchange’s regular hours today: openingHour, closingHour (with UTC offset), timezone, isMarketOpen.
fetch_profile
fetch_profile(symbol: str) -> RawResponseOne listing’s profile: ids (cik, isin, cusip), exchange (code + full name),
currency, country, ipoDate, sector, industry, description, website,
isEtf / isFund / isAdr / isActivelyTrading, and today’s price, marketCap,
averageVolume. Stocks and ETFs; an index, crypto pair or commodity has none
(200 []), and so has an unknown symbol.
fetch_screener
fetch_screener(venue: str, etf: bool, active: bool | None = None) -> RawResponseOne US venue’s ETFs (etf=True) or operating companies (etf=False: not an
ETF, not a mutual fund) from the screener: symbol, companyName, exchange (code +
full name), country, sector, industry, marketCap, price, volume, avgVolume, beta,
lastAnnualDividend, isEtf / isFund / isActivelyTrading. active None = alive and
dead together. complete is False when the 10,000 cap was hit.
fetch_sec_profile
fetch_sec_profile(symbol: str) -> RawResponseOne listing’s SEC identity: cik, registrantName, SIC code / description / group,
isin, ipoDate, isActive, isEtf / isAdr / isFund, assetType, business and mailing
address, state, country — and openFigiComposite, which is the ISSUER’s (keyed by
CIK): a note’s or a preferred’s is its company’s common stock (STRC, STRD ->
MicroStrategy’s; SOJC -> Southern’s; TBB -> AT&T’s), and it can be stale (OKE:
OpenFIGI has ONEOK’s new one). Probed 2026-10-05 on 615 names: 234 of 235 common
stocks agree with OpenFIGI; only 122 of 250 ETFs have a sec-profile at all; a note
without an SEC filer of its own (TMUSL) is 200 [].
fetch_stock_list
fetch_stock_list() -> RawResponseEvery equity, ETF and fund FMP carries, world-wide, alive or dead: symbol, name.
fetch_symbol_changes
fetch_symbol_changes() -> RawResponseEvery ticker change FMP has (5,511 on 2026-10-09: one line dated 1969-12-31, the
rest from 1998), newest first: date, companyName, oldSymbol, newSymbol. Without a
large limit FMP serves 100.
holidays
holidays(exchange: str = 'NYSE', start: dt.date | None = None, end: dt.date | None = None) -> list[dict[str, Any]]The closures and early closes of one exchange, oldest first.
indices
indices() -> list[dict[str, Any]]The ACTIVE US indices: index-list rows under a US index code (US_INDEX_CODES)
and priced in USD, 320 of 428 (currency alone is not the filter: FMP marks
Johannesburg Top 40, Merval, RTS and MSCI World USD too), kept when /api/v3’s
quotes/index (one call, all 428) quoted them within INDEX_FRESH: 316 on
2026-10-04; the 4 dead (^RMZ, ^DJBTR, ^DJBPR, ^DJBAR) last quoted years ago. The list
row is the profile (symbol, name, exchange, currency): FMP has no index profile.
intraday
intraday(symbols: str | Symbols, start: dt.date | None = None, end: dt.date | None = None, interval: Interval = '1min', asset: Asset) -> RawResponse | dict[str, RawResponse]Intraday bars in the common shape, one symbol or many, between two dates
(inclusive), oldest first. FMP stamps every bar, crypto included, at its START on
the US/Eastern wall clock; each becomes a UTC date plus its utc/est parts.
Crypto’s coin volume is its dollar volume over the bar’s typical price.
start None = INTRADAY_FLOOR; end None = today.
Every family walks /stable; an index or commodity /stable gates (402: all but 9 indices, 36 of 40 commodities) walks /api/v3’s same chart instead (same spans, stamps and values).
Equities’ and ETFs’ minutes come back as FMP serves them: split-adjusted as of the
pull, and not always correctly (97 symbol-days around splits, 2026-10-05: 86
adjusted exactly by the splits, 11 wrong — a split applied twice, not at all, or
partly). Putting them back on the raw scale needs the raw daily bars (and the checked
splits), so it is done where those are: intraday.unify.
The dates are the family’s own days: ET session dates for listings; for crypto, UTC days. FMP answers a crypto ask by the ET day (2026-10-02 = 04:00 UTC 10-02 to 03:59 UTC 10-03, probed 2026-10-07), so a UTC day’s first 4-5 hours sit in the ET day before: a crypto walk starts one ET day earlier and keeps the bars whose UTC date lies in [start, end].
ipos
ipos(start: dt.date, end: dt.date) -> list[dict[str, Any]]New listings between two dates, world-wide.
listings
listings(start: dt.date, end: dt.date) -> RawResponseUS listings FMP’s IPO calendar dates in [start, end] (it reaches back to about
2000; a future end lists what is expected), oldest first, in the common shape
(listings.sound_listings): symbol (ours), date, name, exchange, status
(expected, priced, filed, withdrawn), shares, price_low, price_high; the lines that
are no US listing taken out, named. A failed or cut answer comes back as it is.
market_cap_history
market_cap_history(symbols: str | Symbols, start: dt.date | None = None, end: dt.date | None = None) -> RawResponse | dict[str, RawResponse]FMP’s market cap a day per symbol, oldest first, rows {symbol, date, market_cap}
from start (None: FMP’s first row — AAPL 1980, SPY 2006, BTCUSD 2013) to end
(None: the last finished session; mid-session FMP serves the forming day, which is
not asked), one walk per symbol under the cascade (5,000 rows a cut), many symbols as
symbol -> answer. Processed (_process_market_caps): cut to the window, a second row
of a day and a cap of zero dropped by reason. What the figure is: an equity’s is the
close times the quarter’s basic weighted-average count written back over the quarter
once the statement is in (about a percent off the day’s own count; past rows move); a
fund’s the close times a count refreshed every few weeks and flat between (1% off
SPY’s AUM over NAV, 6% QQQ, ~15% mid-size, 2x low on a young fund); a coin’s the price
times circulating supply, right (nc-core, 2026-10-09).
market_caps
market_caps(symbols: str | Iterable[str]) -> RawResponseToday’s market cap for each symbol as ONE answer: rows {symbol, date, market_cap}
in input order (the day as FMP dates it, the cap as a float), a cap of zero or none
dropped by reason, an index absent (FMP drops it without a word). The symbols ride
market-capitalization-batch MARKET_CAP_BATCH a call as one common-http batch;
a failed call fails the answer (a day’s caps are one fact). Read before the open the
figure is the finished day’s at its last print (SPY to the dollar, AAPL 0.035% off its
history row: nc-core 2026-10-10).
navs
navs(symbols: Iterable[str]) -> dict[str, RawResponse]Each fund’s day from etf/info, one call each as one common-http batch, symbol ->
answer with one row {symbol, date, stamp, nav, nav_currency, net_assets, expense_ratio, holdings_count}: nav as of updatedAt (stamp, Eastern wall
clock; date its day, else the day read), which lags the session by one or two days
per fund (762.01 on 2026-09-21 was the 09-18 NAV); net_assets FMP’s
assetsUnderManagement; expense_ratio as a fraction (FMP’s percent over 100, as the
universe stores it: TECL’s 0.94 lands 0.0094); holdings_count as FMP counts them. A
line with neither a NAV nor net assets drops by reason; 200 [] is no row (AAPL).
Live 2026-10-18: SPY nav 771.8 / net assets 823.1B / 504 holdings, stamped 01:55 that
Sunday; a stock’s shares-float stamp lands any hour (AAPL 15:42 on a Sunday), so the
DataClient dates every stamped figure by the last session closed at its stamp.
profiles
profiles(symbols: Iterable[str]) -> dict[str, RawResponse]Each symbol’s profile, one call each (FMP has no batch endpoint on our plan), run
as one common-http batch under the limiter. Per symbol: ok with one row, ok and
empty (FMP has no profile), or failed — the flow decides. Each row also says what
the line IS (papers.fmp_kind: its flags, its spelling, its name): asset_class
and security_type, and an ETF’s leverage / is_leveraged from its name
(papers.gearing); a line that is not a US listing carries refused, why.
quote
quote(symbols: str | Iterable[str]) -> dict[str, dict[str, Any]]Today’s quote for each symbol, as FMP serves it: price, change, changePercentage,
volume, dayLow / dayHigh, yearLow / yearHigh, open, previousClose, priceAvg50 /
priceAvg200, marketCap, exchange, name, timestamp (epoch seconds, UTC). Stocks, ETFs,
crypto and commodities ride batch-quote (500 a call, one common-http batch). It
drops indices silently, so when it leaves any symbol unanswered, /api/v3’s
quotes/index (one call, every index) answers the indices, and what is left after
that is asked alone from quote. A symbol FMP does not answer, or gates on our plan
(402), is absent, and so are the indices when /api/v3 fails; any other failed call
raises.
sec_profiles
sec_profiles(symbols: Iterable[str]) -> dict[str, RawResponse]Each listing’s SEC identity (fetch_sec_profile), one call each, as one
common-http batch.
shares
shares(symbols: str | Symbols, start: dt.date | None = None, end: dt.date | None = None) -> RawResponse | dict[str, RawResponse]Share counts as FMP has them, one answer per symbol (one symbol asked: the answer), in two shapes.
No window is today’s count: one row {symbol, date, stamp, shares_outstanding, float_shares, free_float, source} — the latest SEC cover to the share in listed
units (an ADR’s ADS, a multi-class company folded to the listing’s class), already
scaled by a split since the cover (APH’s 2 for 1 of 2026-09-03 with the June 10-Q
still named); free_float a PERCENT; source the filing FMP read (None for a
fund, and in the sweep); stamp FMP’s refresh clock (Eastern wall clock, any hour:
AAPL 15:42 on a Sunday) and date its day — the count values the last session
finished before the stamp. Up to SHARES_BULK_OVER names one call each; more read
the sweep (shares_all, ten calls for the world), an unserved name its own drop. A
fund is counted too (float zeroed); a name FMP does not count is 200 [] (ok, no
row); a count of zero drops by reason.
A window is the history, equities only: the as-reported statement, one call per name
holding every quarter FMP has since 2009 (AAPL 69; TWTR to its delisting; a fund
answers []), rows {symbol, date, shares_outstanding, fact, period, fiscal_year, amendment} oldest first, date the period end and the count the first of
SHARE_FACTS the quarter tags, AS FILED in the units of its period end, never
restated (AAPL’s 2020-06-27 reads 4,283,939,000). Cut on the period end to the
window; a quarter tagging no count and a second row of a period end (the amendment
kept) drop by reason. The row carries no filing day: the income statement’s
filingDate for the same period end is the known-on day.
shares_all
shares_all() -> RawResponseEvery name FMP counts, one walk of the bulk (SHARES_PAGE a page from 0, the
short page the end: 10 pages, 47,673 names on 2026-10-09): the rows of shares
keyed by FMP’s own symbol, without source (the bulk carries none), a count of zero
dropped by reason. Ten calls for the world: the daily update’s route.
shares_history
shares_history(symbols: str | Symbols, start: dt.date | None = None, end: dt.date | None = None) -> RawResponse | dict[str, RawResponse]A stock’s count day by day (/api/v4/historical/shares_float), oldest first, from
start (None: SHARES_HISTORY_FLOOR, 2021-05-18) to end (None: today): the
rows of shares with the two counts read from FMP’s strings, processed
(_process_shares_history): cut to the window, a second row of a day and a count of
zero dropped by reason, and a day repeating the day before’s three values FOLDED — the
series steps, so the first day of each value is kept and a name’s ~1,300 days land as
its ~75 steps, the folds one line of dropped. Equities only: a fund answers
[]. What it is: FMP’s float series, not the cover series — in 2021 it flickers day
to day between float32 copies of the last two covers; since 2024 it matches the covers
exactly but lags them by weeks (the 2026-07-17 cover appears 2026-09-14). The SEC cover
is the anchor; this is the daily coverage between covers (nc-core, 2026-09-22).
splits
splits(symbols: str | Symbols, start: dt.date | None = None, end: dt.date | None = None) -> RawResponse | dict[str, RawResponse]Split history as facts, oldest first: symbol, date (the first day on the new share
count), split_to, split_from (2-for-1 = 2 / 1, a 1-for-8 reverse = 1 / 8),
split_type (FMP’s label: stock-split, stock-dividend, spin-off, adr-change; None).
Announced future splits are included. start / end keep the dates in
[start, end] (None = no bound). One symbol, or many as one common-http batch.
Processed (_process_splits).
A “stock-dividend” row below one on a cash payout’s day is KEPT: it is a real share consolidation paired with a return of capital, not the payout told twice — the price held through both (TRI 2018-11-27: 907 for 1000 beside $4.64, close 48.96 -> 49.22; TRI 2023-06-23, QGEN 2017-01-25 and 2026-01-08 alike; EODHD lists TRI 2023 too), and dropping it would count the cash as a return the holder never had.
splits_calendar
splits_calendar(start: dt.date, end: dt.date) -> RawResponseEvery split with its date in [start, end], world-wide (filter to our universe in the
flow), in splits’ shape and processed as splits: 90-day windows, every page.
A start older than CALENDAR_REACH is clipped to it and the response says
clipped.
symbol_changes
symbol_changes() -> RawResponseEvery ticker change FMP tells (back to 1998), oldest first, in the common shape
(changes.sound_changes: old_symbol / new_symbol in our spelling, date, name), the
lines that are no rename taken out, named. FMP’s change lines keep the ACT spelling of
their day (BBBY.W, GRAF.U: papers.spell_fmp reads them; a when-issued
.V line is refused). A failed or cut answer comes back as it is (ok / complete).
us_venues
us_venues() -> list[dict[str, Any]]FMP’s US venue rows (countryCode US), OTC included, as FMP describes them.
v3_check
v3_check() -> dict[str, Any]Does /api/v3 still answer for us? One /api/v3 call for the CANARIES and one
/stable call per peer, over the last ten days. Live when every canary is served and
reaches the latest day its peer reaches on /stable (a peer /stable cannot answer is
no evidence: its canaries need only be served). Returns live; reason (None
when live, else every failure, in one sentence); the latest day each canary reached
on /api/v3 (v3) and each peer on /stable (stable), None = not served.
Functions
read_kind
read_kind(row: Mapping[str, Any]) -> dict[str, Any]One FMP line with what it IS added: asset_class, security_type, leverage,
is_leveraged (an ETF’s, from its name; None otherwise) and refused (None, or why
the line is not a US listing).