---
title: "ETF holdings API: fetch top holdings, sector and country exposure as JSON"
description: "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."
canonical: https://fundfactsapi.com/blog/etf-holdings-api
published: 2026-08-26
updated: 2026-08-26
publisher: FundFacts API
---

# 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.

"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](https://fundfactsapi.com/) 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

```bash
curl 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:

```typescript
type Weighted = { label: string; weight: number };

export function lookThrough(
  positions: { isin: string; weight: number }[],      // portfolio weights, sum = 100
  funds: 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 `name` fields of two `topHoldings` arrays and sum the minimum weight of each shared name.

```typescript
export 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);
}
```

> **Note** Top holdings cover the ten largest positions, which is what factsheets publish. For a 1,344-holding index fund they represent roughly a quarter of the portfolio; overlap computed on them is a lower bound, not the full picture.

## Use case 3: charts on a product page

Because every breakdown is already `{ label, weight }`, feeding a chart library is a one-liner:

```typescript
const donut = fund.data.sector.map((s) => ({ name: s.label, value: s.weight }));
```

The [landing page](https://fundfactsapi.com/#showcase) 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](https://fundfactsapi.com/signup), 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](https://fundfactsapi.com/docs).

## About FundFacts API

FundFacts turns any fund or ETF ISIN into one dated factsheet (fees and KID costs, the 1–7 risk indicator, holdings, sector and country exposure, returns) read from the fund house's own documents: as a client-ready PDF under your firm's name for advisers and wealth managers, as JSON through its API for developers, or inside ChatGPT and Claude through its MCP server.

FundFacts works inside ChatGPT and Claude as a connector (a remote MCP server): ask about any fund or ETF by ISIN, ticker or name and the assistant answers from FundFacts' dated figures, fees, risk, holdings, exposure and returns, with fund cards, side-by-side comparisons, holdings overlap and portfolio look-through.

- Free API key, no card: https://fundfactsapi.com/signup
- Documentation: https://fundfactsapi.com/docs (OpenAPI: https://fundfactsapi.com/openapi.json)
- MCP server: https://fundfactsapi.com/api/mcp (set-up for every client: https://fundfactsapi.com/mcp-server)
- Pricing: https://fundfactsapi.com/pricing
- Site index for AI assistants: https://fundfactsapi.com/llms.txt
- Informational data, not investment advice.
