ETF holdings API: fetch top holdings, sector and country exposure as JSON
How to pull ETF and fund holdings, sector weights, geographic exposure and asset allocation from an API, and how to turn them into portfolio look-through and concentration checks.Published 26 August 2026 · 3 min read · by FundFacts API"What is actually inside this fund?" is the question behind most fund research tools: look-through for a client portfolio, concentration limits for a compliance check, a sector donut on a product page. This article walks through the holdings and exposure blocks returned by FundFacts API and shows how to use them.
The exposure blocks at a glance
| Field | Shape | Typical use |
|---|---|---|
data.topHoldings | [{ name, weight }] (top 10) | Holdings table, overlap analysis |
data.sector | `[{ label, weight }]` | Sector donut, GICS tilt |
data.geography | `[{ label, weight }]` | Country/region map, home bias |
data.region | `[{ label, weight }]` | Coarser regional split |
data.assetAllocation | `[{ label, weight }]` | Equity / bond / cash split for multi-asset funds |
data.creditQuality | `[{ label, weight }]` | Rating buckets for bond funds |
data.maturity | `[{ label, weight }]` | Maturity ladder for bond and money-market funds |
data.instrument | `[{ label, weight }]` | Instrument mix for money-market funds |
data.keyFacts.holdings | `number` | Total number of positions |
Weights are percentages of the portfolio (0–100). Arrays that do not apply to the asset class are empty, so an equity ETF has an empty creditQuality and a bond fund has an empty sector or a sector array describing instrument types — one renderer handles both.
Example: a global equity ETF
bashcurl https://fundfactsapi.com/api/v1/funds/IE00B4L5Y983 \-H "Authorization: Bearer $FUNDFACTS_API_KEY"
json{"topHoldings": [{ "name": "NVIDIA Corp", "weight": 5.43 },{ "name": "Apple Inc", "weight": 5.08 },{ "name": "Microsoft Corp", "weight": 3.92 }],"sector": [{ "label": "Technology", "weight": 30.5 },{ "label": "Financial Services", "weight": 16.34 },{ "label": "Industrials", "weight": 10.83 }],"geography": [{ "label": "North America", "weight": 75.54 },{ "label": "Europe Developed", "weight": 12.26 },{ "label": "Japan", "weight": 5.78 }]}
Use case 1: portfolio look-through
Given a client portfolio of funds with weights, the effective exposure to a sector is the weight-averaged sum of each fund's sector weights:
typescripttype Weighted = { label: string; weight: number };export function lookThrough(positions: { isin: string; weight: number }[], // portfolio weights, sum = 100funds: Record<string, { sector: Weighted[] }>, // API responses by ISIN): Weighted[] {const acc = new Map<string, number>();for (const p of positions) {for (const s of funds[p.isin].sector) {acc.set(s.label, (acc.get(s.label) ?? 0) + (p.weight / 100) * s.weight);}}return [...acc].map(([label, weight]) => ({ label, weight: +weight.toFixed(2) })).sort((a, b) => b.weight - a.weight);}
Run the same function over geography for country exposure and over topHoldings to spot the single names your client holds through three different funds at once.
Use case 2: concentration and overlap checks
- Top-10 concentration:
sum(topHoldings.weight). Above 40–50 % the fund is effectively a bet on a handful of names. - Largest position:
topHoldings[0].weight. UCITS diversification rules cap single issuers at 10 %, so a value near 10 % is worth flagging. - Overlap between two funds: intersect the
namefields of twotopHoldingsarrays and sum the minimum weight of each shared name.
typescriptexport function overlap(a: Weighted[], b: Weighted[]) {const byName = new Map(b.map((h) => [h.label, h.weight]));return a.reduce((sum, h) => sum + Math.min(h.weight, byName.get(h.label) ?? 0), 0);}
Use case 3: charts on a product page
Because every breakdown is already { label, weight }, feeding a chart library is a one-liner:
typescriptconst donut = fund.data.sector.map((s) => ({ name: s.label, value: s.weight }));
The landing page renders sector donuts, holdings bars and country exposure directly from the sample response — no transformation layer.
Use case 4: bond and money-market funds
For fixed income the interesting arrays are creditQuality (AAA … below B), maturity (1–3 years, 3–5 years …) and, for money-market funds, instrument (commercial paper, certificates of deposit, repos). Combine them with metrics.averageRating, metrics.effectiveDuration and metrics.wam / metrics.wal from the metrics block to build a credit-and-duration profile.
Freshness
Holdings change; the API refreshes each ISIN at most every 24 hours and tells you when in generatedAt and data.dataAsOf (the "as of" date published with the portfolio). For compliance reporting, store dataAsOf next to the numbers you used.
Try it
Create a free key, request one of your funds and inspect data.topHoldings and data.sector in the dashboard. The complete field list, including every exposure array, is in the documentation.