ETF Holdings History API
Return one row per holding with its portfolio weight in every reporting period the fund has filed — the pivot a position-over-time table needs, assembled server-side instead of in your client. Positions are keyed on a stable security identity rather than the instrument name, which matters because filers rewrite names between quarters: ARK's 58 distinct holdings across six periods would scatter into far more rows if keyed on the text. Holdings the fund has since exited are still returned, at the bottom, because what a manager sold is half the reason to read a table like this. The API is free to use.
Use this endpoint to:
https://data.businessquant.com/funds/holdings/matrix?ticker={ticker}&limit={limit}&api_key={api_key}
Related endpoints:
For a single period in full position-level detail, use the Fund Holdings API. For allocation over time by bucket rather than by holding, use the Portfolio Exposure API with periods. The Reporting Periods API lists the periods that become the keys of the weights object, and the Fund Overview API carries fees and objective.
1. Request Parameters
One identifier plus optional paging. There is no period argument, because the point of this endpoint is that every period arrives at once.
| Parameter | Description |
|---|---|
| api_key |
Required
Your unique API key for authentication.
|
| ticker |
Conditional Identifier
The fund or ETF ticker. A ticker identifies a share class, not a fund — VOO and VFIAX are two classes of the same portfolio, so the fees you get back are the ones that class charges. Either ticker or seriesid is required.
Example:
ticker=VOO |
| seriesid |
Conditional Identifier
The SEC Series ID of the fund. Resolves to the fund's lowest-cost share class. Use this when you want the fund rather than a particular class, or for funds that have no ticker of their own.
Example:
seriesid=S000002839 |
| page |
Optional
Page number, 1-based. Default 1.
Example:
page=2 |
| limit |
Optional
Holdings per page. Range 1–5000, default 100. Note this pages holdings, not holding-periods — each row already carries every period.
Example:
limit=500 |
Rows are ordered by current weight, not by history. The largest position the fund holds today sorts first. A holding that has been fully exited has a current weight of zero and sorts to the bottom, ranked among its peers by the largest weight it ever reached — so the tail of the table is the fund's disposal history, in order of how big those positions once were.
What identifies a holding across quarters. Not the name. Measured on IVV: 549 distinct securities produce 1,278 distinct instrument_name strings across the filing history, because filers rewrite them — “NVIDIA”, “NVIDIA Corp.”, “NVIDIA CORPORATION” — from one quarter to the next. Grouping on the name would scatter one position across three rows. Rows here are grouped on a stable security identity, and the label you get back is the most recent name the filer used, because that is the one a reader will recognise. The identity key itself is never returned.
curl -X GET "https://data.businessquant.com/funds/holdings/matrix?ticker=ARKK&limit=8&api_key=YOUR_API_KEY"
{
"metadata": {
"ticker": "ARKK",
"seriesid": "S000042977",
"classid": "C000133121",
"cik": 1579982,
"fund_name": "ARK Innovation ETF",
"class_name": "ARK Innovation ETF",
"gross_expense_ratio_pct": 0.75,
"net_expense_ratio_pct": 0.75,
"net_assets_usd": 6482466914.09,
"net_assets_asof": "2026-04-30",
"periods": [
"2026-04-30",
"2026-01-30",
"2025-10-31",
"2025-07-31",
"2025-04-30",
"2025-01-31"
],
"period_count": 6,
"period_latest": "2026-04-30",
"period_earliest": "2025-01-31",
"holdings_count": 58,
"returned": 8,
"pagination": {
"current_page": 1,
"limit": 8,
"total_records": 58,
"total_pages": 8
}
},
"data": [
{
"instrument_name": "TESLA INC",
"instrument_ticker": "TSLA",
"issuer_name": "TESLA INC COM",
"sector": "Consumer Discretionary",
"industry": "Auto Manufacturers",
"country_name": "United States",
"asset_class": "Equity",
"periods_held": 6,
"weight_latest_pct": 9.7352,
"weight_max_pct": 13.4234,
"weights": {
"2025-01-31": 13.4234,
"2025-04-30": 11.9455,
"2025-07-31": 9.814,
"2025-10-31": 12.3106,
"2026-01-30": 10.8803,
"2026-04-30": 9.7352
}
},
{
"instrument_name": "TEMPUS AI INC",
"instrument_ticker": "TEM",
"issuer_name": "TEMPUS AI INC CL A",
"sector": "Healthcare",
"industry": "Diagnostic Labs",
"country_name": "United States",
"asset_class": "Equity",
"periods_held": 6,
"weight_latest_pct": 5.3555,
"weight_max_pct": 5.3555,
"weights": {
"2025-01-31": 3.6009,
"2025-04-30": 5.0923,
"2025-07-31": 4.2153,
"2025-10-31": 5.0353,
"2026-01-30": 4.872,
"2026-04-30": 5.3555
}
},
{
"instrument_name": "ADVANCED MICRO DEVICES INC",
"instrument_ticker": "AMD",
"issuer_name": "ADVANCED MICRO DEVICES INC COM",
"sector": "Technology",
"industry": "Semiconductor Design",
"country_name": "United States",
"asset_class": "Equity",
"periods_held": 6,
"weight_latest_pct": 5.1839,
"weight_max_pct": 5.1839,
"weights": {
"2025-01-31": 1.2131,
"2025-04-30": 1.6782,
"2025-07-31": 3.5779,
"2025-10-31": 4.2662,
"2026-01-30": 3.9753,
"2026-04-30": 5.1839
}
},
{
"instrument_name": "CRISPR THERAPEUTICS AG",
"instrument_ticker": "CRSP",
"issuer_name": "CRISPR THERAPEUTICS AG NAMEN AKT",
"sector": "Healthcare",
"industry": "Gene and Cell Therapy",
"country_name": "Switzerland",
"asset_class": "Equity",
"periods_held": 6,
"weight_latest_pct": 4.9803,
"weight_max_pct": 5.4807,
"weights": {
"2025-01-31": 4.1767,
"2025-04-30": 4.8185,
"2025-07-31": 5.3379,
"2025-10-31": 5.0598,
"2026-01-30": 5.4807,
"2026-04-30": 4.9803
}
},
{
"instrument_name": "SHOPIFY INC",
"instrument_ticker": "SHOP",
"issuer_name": "SHOPIFY INC CL A SUB VTG SHS",
"sector": "Technology",
"industry": "E-commerce Software",
"country_name": "Canada",
"asset_class": "Equity",
"periods_held": 6,
"weight_latest_pct": 4.3743,
"weight_max_pct": 4.742,
"weights": {
"2025-01-31": 4.3086,
"2025-04-30": 4.5018,
"2025-07-31": 4.675,
"2025-10-31": 4.742,
"2026-01-30": 4.245,
"2026-04-30": 4.3743
}
},
{
"instrument_name": "ROKU INC",
"instrument_ticker": "ROKU",
"issuer_name": "ROKU INC COM CL A",
"sector": "Technology",
"industry": "Operating Systems",
"country_name": "United States",
"asset_class": "Equity",
"periods_held": 6,
"weight_latest_pct": 4.3298,
"weight_max_pct": 9.1137,
"weights": {
"2025-01-31": 9.1137,
"2025-04-30": 7.7826,
"2025-07-31": 6.9316,
"2025-10-31": 5.7217,
"2026-01-30": 5.0014,
"2026-04-30": 4.3298
}
},
{
"instrument_name": "ROBINHOOD MARKETS INC",
"instrument_ticker": "HOOD",
"issuer_name": "ROBINHOOD MARKETS INC COM CL A",
"sector": "Financial Services",
"industry": "Retail Brokerage",
"country_name": "United States",
"asset_class": "Equity",
"periods_held": 6,
"weight_latest_pct": 4.3018,
"weight_max_pct": 5.1095,
"weights": {
"2025-01-31": 5.1095,
"2025-04-30": 4.7845,
"2025-07-31": 4.1809,
"2025-10-31": 4.7924,
"2026-01-30": 3.9982,
"2026-04-30": 4.3018
}
},
{
"instrument_name": "COINBASE GLOBAL INC",
"instrument_ticker": "COIN",
"issuer_name": "COINBASE GLOBAL INC COM CL A",
"sector": "Technology",
"industry": "Blockchain Infrastructure",
"country_name": "United States",
"asset_class": "Equity",
"periods_held": 6,
"weight_latest_pct": 4.2318,
"weight_max_pct": 8.4906,
"weights": {
"2025-01-31": 8.4906,
"2025-04-30": 7.8933,
"2025-07-31": 7.2271,
"2025-10-31": 5.8517,
"2026-01-30": 4.6229,
"2026-04-30": 4.2318
}
}
]
}
2. Response Fields
The row shape here differs from every other endpoint in this group: weights is an object keyed by reporting period, not a scalar. That is what makes one row a complete time series.
metadata
| Field | Type | Description |
|---|---|---|
| ticker, seriesid, classid, cik | string | Fund and share-class identity. |
| fund_name, class_name | string | Registered fund name and resolved share class. |
| net_assets_usd, net_assets_asof | float, date | Fund net assets and the date reported. |
| periods | array | Every reporting period on file, newest first. These are exactly the keys that appear in each row's weights object. |
| period_count, period_latest, period_earliest | integer, date, date | Size and bounds of the period list. |
| holdings_count | integer | Distinct securities the fund has held across the entire window, before paging. |
| returned | integer | Rows in this page of data. |
| pagination | object | current_page, limit, total_records, total_pages. |
data — one row per holding
| Field | Type | Description |
|---|---|---|
| instrument_name | string | Name of the security, taken from the most recent period it appears in. |
| instrument_ticker | string | Exchange ticker where one exists. null for bonds and derivatives. |
| issuer_name | string | Name of the issuing entity. |
| sector | string | Sector classification of the issuer. |
| industry | string | Industry classification, one level below sector. |
| country_name | string | Country of the issuer. |
| asset_class | string | Equity, Fixed Income, Derivative, Fund & Cash Equivalents or Other. |
| periods_held | integer | How many of the fund's reporting periods this security appears in. Equal to period_count means held throughout. |
| weight_latest_pct | float | Weight in the most recent period. 0 for a position the fund has exited. Rows are sorted on this, descending. |
| weight_max_pct | float | The largest weight this position ever reached across the window. |
| weights | object | Weight per reporting period, keyed by date — {"2026-04-30": 9.7352, …}. Periods where the security was not held are absent from the object rather than present as zero. |
Absent means not held; it does not mean zero. A period missing from a row's weights object is a period in which the fund did not hold that security at all. Rendering it as 0 is usually the right visual choice, but keep the distinction if you are computing anything — an average weight over held periods and an average over all periods are different numbers, and periods_held is the divisor for the first.
3. Response Viewer
A real response for ticker=ARKK&limit=8. Each row is one holding and each dated column is a key from that row's weights object. Colour marks the direction of change against the prior period and a dash marks a period the security was not held — both computed in this page, not returned by the API.
API Response — Holdings Over Time
ARK Innovation ETF · ticker=ARKK&limit=8
| Holding | Ticker | Sector | Held | 2025-01-31 | 2025-04-30 | 2025-07-31 | 2025-10-31 | 2026-01-30 | 2026-04-30 |
|---|---|---|---|---|---|---|---|---|---|
| TESLA INC | TSLA | Consumer Discretionary | 6/6 | 13.42% | 11.95% | 9.81% | 12.31% | 10.88% | 9.74% |
| TEMPUS AI INC | TEM | Healthcare | 6/6 | 3.60% | 5.09% | 4.22% | 5.04% | 4.87% | 5.36% |
| ADVANCED MICRO DEVICES INC | AMD | Technology | 6/6 | 1.21% | 1.68% | 3.58% | 4.27% | 3.98% | 5.18% |
| CRISPR THERAPEUTICS AG | CRSP | Healthcare | 6/6 | 4.18% | 4.82% | 5.34% | 5.06% | 5.48% | 4.98% |
| SHOPIFY INC | SHOP | Technology | 6/6 | 4.31% | 4.50% | 4.68% | 4.74% | 4.25% | 4.37% |
| ROKU INC | ROKU | Technology | 6/6 | 9.11% | 7.78% | 6.93% | 5.72% | 5.00% | 4.33% |
| ROBINHOOD MARKETS INC | HOOD | Financial Services | 6/6 | 5.11% | 4.78% | 4.18% | 4.79% | 4.00% | 4.30% |
| COINBASE GLOBAL INC | COIN | Technology | 6/6 | 8.49% | 7.89% | 7.23% | 5.85% | 4.62% | 4.23% |
4. More Request Examples
One call returns the whole grid; paging walks the holdings axis only.
https://data.businessquant.com/funds/holdings/matrix?ticker=ARKK&limit=500&api_key=YOUR_API_KEY
https://data.businessquant.com/funds/holdings/matrix?ticker=VOO&limit=25&api_key=YOUR_API_KEY
https://data.businessquant.com/funds/holdings/matrix?seriesid=S000042977&limit=100&api_key=YOUR_API_KEY
https://data.businessquant.com/funds/holdings/matrix?ticker=IVV&limit=200&page=2&api_key=YOUR_API_KEY
5. Use Cases
Everything below would otherwise require one request per period plus a client-side join.
Position Weight Sparklines
Each row's weights object is already a time series keyed by date — feed it straight to a sparkline beside the holding name with no reshaping.
Conviction Screening
Compare weight_latest_pct against weight_max_pct. A position at a fraction of its peak is being wound down; one at its peak is being built. periods_held separates a long-held core position from a recent addition.
Exit Analysis
Rows with weight_latest_pct of zero are positions the fund has fully exited. Combine the exit period with prices from the Stock Quotes API to measure what the manager's sell decisions were actually worth.
Turnover Attribution
Aggregate per-period weight changes across all rows to compute realised turnover from filings, rather than relying on the single annual figure a fund reports in its prospectus.
Manager Consistency Scoring
The ratio of holdings with periods_held equal to period_count against total holdings is a direct measure of how much of a portfolio is genuinely long-term.
Backtest Input Assembly
One request produces the full holdings panel for a fund. Combine with the Stock Quotes API to reconstruct what the fund's portfolio would have returned under different weighting rules.
Frequently Asked Questions
The questions developers ask most often about this endpoint.
Why does a period sometimes not appear in a row's weights object?
Because the fund did not hold that security in that period. Absent means not held — it is not the same as a zero weight, and the API does not pad the object. Render it as a gap or a zero as your chart requires, but use periods_held rather than the count of periods when you average.
How are holdings matched across quarters when the name changes?
On a stable security identity, not on the name. Filers rewrite instrument names between filings — measured on IVV, 549 distinct securities generate 1,278 distinct name strings — so name-based grouping would split one position across several rows. The label returned is the most recent name the filer used, because that is the recognisable one; the underlying key is never exposed in the response.
What is the difference between this and calling /funds/holdings once per period?
Result shape and request count. Calling the holdings endpoint per period returns one row per (holding, period) and leaves you to pivot several thousand rows in the client to render a few hundred. This endpoint does that pivot server-side and returns it in a single request, with holdings the fund has exited already included — which a per-period loop would silently drop unless you unioned every period yourself.
Why is a position I know the fund holds showing a weight of zero in the latest period?
A weight_latest_pct of zero means the security is absent from the most recent period the fund filed — check metadata.period_latest, which may predate the trade you are thinking of. N-PORT is filed periodically, not continuously, so the most recent filed portfolio can be several weeks behind the live one.
Does limit control periods or holdings?
Holdings. Every row already contains every period, so limit and page walk the security axis only. metadata.holdings_count is the total number of distinct securities the fund has held across the whole window, which is what pagination is counting.
Is the ETF Holdings History API free to use?
Yes, the API is free to use. Sign up for an API key and start pulling per-holding weight histories immediately — no credit card required.