ETF holdings API: holdings, sector and country weights by ISIN as JSON
FundFacts API returns the holdings of an ETF or fund from its ISIN as JSON: the largest positions with their weights, the number of holdings, and the sector, country, region and asset-class weights, with the as-of date of the fund house's documents. One GET request with an API key; the Free plan gives 15 lookups a month without a card.Published October 6, 2026What the holdings block contains
Every fund payload carries the same keys; one that does not apply to the fund is an empty array or null, never missing.
| Field | Shape | What it holds |
|---|---|---|
data.topHoldings | [{ name, weight }] | Largest positions in the fund house's order, weight in percent. |
data.holdingsBasis | "portfolio", "substituteBasket" or null | The fund's own positions, or a synthetic ETF's swap collateral. |
data.keyFacts.holdings | number | Number of positions in the portfolio. |
data.sector | [{ label, weight }] | Sector weights (GICS-style for equity funds). |
data.geography, data.region | [{ label, weight }] | Country and regional weights. |
data.assetAllocation | [{ label, weight }] | Equity, bond and cash split. |
data.creditQuality, data.maturity | [{ label, weight }] | Rating and maturity buckets for bond funds. |
data.profile.concentration | string | From the weight of the ten largest positions. |
data.dataAsOf | date | As-of date of the figures. |
The same payload carries fees, the risk indicator and returns: see the ETF data API page.
A request and a trimmed response
bashcurl -s https://fundfactsapi.com/api/v1/funds/IE00B4L5Y983 \-H "Authorization: Bearer $FUNDFACTS_API_KEY"
json{"isin": "IE00B4L5Y983","name": "iShares Core MSCI World UCITS ETF","cached": true,"generatedAt": "2026-09-02T20:32:58.872Z","expiresAt": "2026-09-03T20:32:58.872Z","data": {"keyFacts": {"holdings": 1253},"topHoldings": [{ "name": "NVIDIA", "weight": 5.48 },{ "name": "APPLE", "weight": 5.24 },{ "name": "MICROSOFT", "weight": 3.88 }],"sector": [{ "label": "Information Technology", "weight": 29.8 },{ "label": "Financials", "weight": 16.46 },{ "label": "Industrials", "weight": 11.03 }],"geography": [{ "label": "United States", "weight": 72.07 },{ "label": "Japan", "weight": 5.85 },{ "label": "United Kingdom", "weight": 3.49 }],"assetAllocation": [{ "label": "Equity", "weight": 99.75 },{ "label": "Cash Collateral and Margins", "weight": 0.02 }],"profile": {"concentration": "diversified"},"dataAsOf": "2026-09-01"}}
This is the real payload for iShares Core MSCI World UCITS ETF, trimmed to three rows per array. Its topHoldings lists the ten largest of 1,253 positions, which together weigh 26.7% of the fund. To check the shape without a key, the demo endpoint returns the same envelope for any fund already loaded (30 requests a minute per IP, not for production):
bashcurl -s https://fundfactsapi.com/api/v1/demo/funds/IE00B4L5Y983
Top ten or longer list: what each fund house publishes
topHoldings is what the fund house discloses. It is never estimated.
- Most funds: the top ten. The list comes from the factsheet's top-ten table, or from the ten heaviest lines of the fund house's holdings file where it publishes one (iShares does).
- Longer lists from a few fund houses. Where the fund house's data lists more positions, up to 50 are kept (Amundi, Franklin Templeton, J.P. Morgan and UBS among them).
- Names without weights. Some fund houses publish names only. Every
weightis thennull; a list is never mixed. - Synthetic ETFs. A swap-based ETF holds a substitute basket that backs the swap.
holdingsBasisis thensubstituteBasket, andsectorandgeographydescribe the index instead.
The sector, country and asset weights describe the whole portfolio, from the fund house's own tables or its holdings file, so they are not limited to the listed positions. Where a holdings file exists, keyFacts.holdings counts its lines. The coverage pages state, per fund house, which sections the payload fills.
Overlap and look-through
Two endpoints do the holdings arithmetic for you:
GET /overlap?isins=A,Breturns, for each pair of funds, the overlap percentage (the sum of the smaller weight of every shared holding, matched by issuer name), the shared positions and how much of each portfolio was disclosed. When a fund publishes only its top ten, the figure is a lower bound; it isnull, not 0, when a fund gives names without weights.POST /portfoliotakes weighted positions and returns the combined sector, country, region, asset and currency weights, the combined top holdings, the blended TER and the weighted risk indicator, each with a coverage figure.
Both are included in Pro, Scale and Enterprise and count one request per fund. The free ETF overlap checker and portfolio look-through run without a key on funds already loaded.
What it does not do
- No complete constituent file:
topHoldingsstops at the fund house's list, and each row is a name and a weight, without the holding's ISIN or ticker. - No intraday holdings, and no regulatory filings such as 13F or N-PORT. Holdings come from the fund house's own documents.
- No prices or quotes. Use a market-data API alongside for those.
- Stocks, bonds and indices are not funds. They return
404 fund_not_found, which is not counted, up to as many misses a month as the plan's allowance.
Price
Free is $0: 15 lookups a month, no card, personal use, one ISIN per call. Starter is $9.99 a month for 500 requests and batches of 10, Pro $49 for 9,000 and batches of 50, Scale $249 for 60,000 and batches of 200 (VAT included). Holdings overlap, portfolio look-through and commercial use of the data come with Pro, Scale and Enterprise. One request is counted per fund found; /search and the demo endpoint are free. See pricing.
Get started in three steps
- See the payload without a key.
curl -s https://fundfactsapi.com/api/v1/demo/funds/IE00B4L5Y983returns the full payload of any fund already loaded, holdings and exposure included. - Create a free key. Sign up with an e-mail address or Google. The Free plan gives 15 lookups a month without a card, and the key is in the dashboard.
- Request your ETF by ISIN. Call
GET /api/v1/funds/{isin}withAuthorization: Bearer <key>. A fund nobody requested in the last 24 hours takes 15 seconds to 3 minutes to load the first time, so set a 300-second timeout. The quickstart has the SDK version.
Frequently asked questions
Is there a free ETF holdings API?
Yes. The FundFacts API Free plan gives 15 lookups a month without a card, with the full payload (holdings, sector and country weights included) for personal, non-commercial use. The demo endpoint /api/v1/demo/funds/{isin} needs no key for funds already loaded.
Does it cover European UCITS ETFs?
Yes. UCITS ETFs are at the core of the coverage, including iShares, Xtrackers, SPDR, Amundi, Vanguard and Invesco. Each share class is looked up by its own ISIN, and the coverage pages say which sections each fund house fills.
Can I get the full holdings list?
Not as one complete file. topHoldings lists the largest positions the fund house publishes: ten for most funds, up to 50 where its data lists more. The sector, country and asset weights describe the whole portfolio, and keyFacts.holdings gives the number of positions.
How often are holdings updated?
Each ISIN is re-read from the fund house's documents when its payload is older than 24 hours. The holdings are as recent as those documents: data.dataAsOf dates them, typically the last month end for a factsheet's top-ten table.
Does it return ETF prices?
No. There are no quotes, intraday prices or iNAV. The payload carries calendar and annualised returns and monthly return series built from the fund house's published NAV history; use a market-data API alongside for prices.
Can I look up an ETF by ticker?
Yes, through GET /search?q=: it matches an exchange ticker such as VWCE or EUNL, a name or an ISIN prefix among funds already loaded, returns the ISIN and is not counted. The holdings call itself takes the ISIN.
Can I compare the holdings of two ETFs?
GET /overlap?isins=A,B returns the overlap percentage and the shared positions, on Pro, Scale and Enterprise. The free ETF overlap checker does the same in the browser for funds already loaded.