How do I do a look-through of a portfolio of funds (real sector, country and fee exposure)?
POST /api/v1/portfolio with positions and weights returns the weighted TER, weighted risk indicator, aggregated sector, country, region, asset and credit-quality exposure and the combined top holdings, with coverage per breakdown. Code and formula.Updated 12 September 2026 · by FundFacts APIShort answer
Send the positions to POST https://fundfactsapi.com/api/v1/portfolio as { positions: [{ isin, weight }, …] } (weights are normalised to 100). The response contains fees.weightedTer, risk.weightedSrri, and aggregated assetAllocation, sector, geography, region, creditQuality, currency and topHoldings, each with a coverage figure stating what share of the portfolio disclosed that breakdown. Pro and above, one request per position; the free portfolio look-through tool on this site runs it in the browser.
The call
bashcurl -s --max-time 300 -X POST https://fundfactsapi.com/api/v1/portfolio \-H "Authorization: Bearer $FUNDFACTS_API_KEY" -H "Content-Type: application/json" \-d '{"positions":[{"isin":"IE00B4L5Y983","weight":60},{"isin":"IE00B3RBWM25","weight":25},{"isin":"LU1681043599","weight":15}]}'
json{"positions": [ { "isin": "IE00B4L5Y983", "name": "iShares Core MSCI World UCITS ETF", "weight": 60, "covered": true, "ter": 0.2, "riskRating": 6, "category": "Global Equity", "kind": "equity" } ],"coverage": 100,"fees": { "weightedTer": 0.19, "coverage": 100, "annualCostPer10k": 19 },"risk": { "weightedSrri": 5.6, "band": "high", "coverage": 100 },"kinds": [ { "label": "equity", "weight": 85 }, { "label": "fixedIncome", "weight": 15 } ],"assetAllocation": { "items": [ { "label": "Equity", "weight": 84.8 } ], "coverage": 100 },"sector": { "items": [ { "label": "Information Technology", "weight": 24.9 } ], "coverage": 85 },"geography": { "items": [ { "label": "United States", "weight": 58.3 } ], "coverage": 100 },"topHoldings": { "items": [ { "label": "NVIDIA", "weight": 4.4 } ], "coverage": 85, "note": "…" },"pending": [],"rules": "fundfacts-portfolio/1"}
The arithmetic
For each breakdown, every fund's weights are multiplied by the fund's portfolio weight and summed by label; coverage is the sum of the portfolio weights of the funds that disclosed that breakdown, so you can tell "58% US of a fully disclosed portfolio" from "58% US of the 70% that disclosed geography". Weighted TER and weighted SRRI are plain weighted averages over disclosed values, each with its own coverage; risk.band maps the weighted SRRI to low / medium / high and fees.annualCostPer10k restates the TER in currency per 10,000 invested. Labels are normalised across fund houses before summing.
Doing it yourself from GET /funds/{isin} is the same loop; the endpoint saves the label normalisation and the coverage bookkeeping. Aggregating sector and country exposure shows the hand-rolled version.
Practical notes
- Bond funds contribute
creditQualityandmaturity, equity fundssector;kindsshows the asset split of the whole portfolio. - Cold ISINs come back in
pendingand are warmed; re-send the call a minute later for a complete picture. - One request per position; up to 50 positions on Pro, 200 on Scale. Cache by the sorted list of (isin, weight) until the earliest
expiresAt.
The portfolio look-through use case and the robo-advisor use case cover product design; the MCP tool is analyze_portfolio.
Verify it yourself
The demo endpoint returns the live payload for a fund that is already in the store, without a key. Everything on this page can be checked against it.bashcurl -s https://fundfactsapi.com/api/v1/demo/funds/IE00B4L5Y983 | jq '{name, asOf: .data.dataAsOf, ter: .data.headlineMetrics.ter, risk: .data.riskRating, top: .data.topHoldings[:3]}'