Workflows

How do I compare two ETFs or funds programmatically?

Fetch both ISINs in one batch call, put TER, risk indicator, size, top holdings, sector and country weights and calendar returns side by side, then add the overlap endpoint for shared holdings. Code included.Updated 12 September 2026 · by FundFacts API

Short answer

Send both ISINs to POST https://fundfactsapi.com/api/v1/funds (or two GET calls on the Free plan) and read the same fields from each payload: headlineMetrics.ter, riskRating, keyFacts.aum, profile.category, topHoldings, sector, geography, calendarReturns and annualisedReturns. For shared holdings call GET /overlap?isins=A,B (Pro and above), which returns the overlap percentage and the common names. The MCP server's compare_funds tool does the same for AI assistants.

Fetch both

python
import os, requests
H = {"Authorization": f"Bearer {os.environ['FUNDFACTS_API_KEY']}"}
r = requests.post("https://fundfactsapi.com/api/v1/funds", json={"isins": ["IE00B4L5Y983", "IE00B3RBWM25"], "wait": True}, headers=H, timeout=300)
funds = {x["isin"]: x for x in r.json()["results"] if x["status"] == "ok"}

Put the figures side by side

python
def row(label, get):
return [label] + [get(funds[i]) for i in ("IE00B4L5Y983", "IE00B3RBWM25")]
table = [
row("Name", lambda f: f["name"]),
row("Category", lambda f: f["data"]["profile"]["category"]),
row("TER", lambda f: f["data"]["headlineMetrics"]["ter"]),
row("Risk (1-7)", lambda f: f["data"]["riskRating"]),
row("Size", lambda f: f["data"]["keyFacts"]["aum"]),
row("Holdings", lambda f: f["data"]["keyFacts"]["holdings"]),
row("Top 10 weight", lambda f: round(sum(h["weight"] for h in f["data"]["topHoldings"][:10]), 1)),
row("US weight", lambda f: next((g["weight"] for g in f["data"]["geography"] if g["label"] == "United States"), None)),
row("3y vol", lambda f: f["data"]["headlineMetrics"]["volatility3y"]),
row("Max drawdown", lambda f: f["data"]["metrics"]["maxDrawdown"]),
row("2025 return", lambda f: dict(zip(f["data"]["calendarReturns"]["years"], f["data"]["calendarReturns"]["fund"])).get("2025")),
row("As of", lambda f: f["data"]["dataAsOf"]),
]
for r in table:
print(f"{r[0]:<14} {str(r[1]):<28} {str(r[2]):<28}")

Shared holdings

bash
curl -s "https://fundfactsapi.com/api/v1/overlap?isins=IE00B4L5Y983,IE00B3RBWM25" -H "Authorization: Bearer $FUNDFACTS_API_KEY" \
| jq '.pairs[0] | {overlap, sharedCount, top: .shared[:5]}'

overlap is the percentage of portfolio weight the two funds have in common (the sum over shared names of the smaller weight), shared lists each common holding with its weight in both. Pro and above; the free ETF overlap tool runs it for you in the browser.

Presenting it

Keep the strings as printed (a TER of "0.20%" next to "0.22%" reads better than 0.002), show dataAsOf under the table, and avoid ranking words: a comparison presents facts, not a recommendation. The comparison tool guide covers the UI and caching, and the ETF comparison website use case the SEO angle.

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.
bash
curl -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]}'

Frequently asked questions

Can I compare more than two?

Yes: batch up to 10 ISINs on Starter, 50 on Pro and 200 on Scale, and /overlap accepts several ISINs and returns every pair.

Do I need a credit card to try it?

No. The Free plan is 15 lookups per month with no card; sign up with an e-mail address or Google and the key is shown in the dashboard.

Try it on your own ISINs

One request returns key facts, holdings, risk and performance as JSON. Free plan, no card.