---
title: "How to get fund data from an ISIN with a REST API (curl, JavaScript, Python)"
description: "Step-by-step: call a fund data API with an ISIN and get key facts, holdings, risk and performance as JSON. Includes curl, fetch and requests examples and error handling."
canonical: https://fundfactsapi.com/blog/fund-data-api-from-isin
published: 2026-08-19
updated: 2026-08-19
publisher: FundFacts API
---

# How to get fund data from an ISIN with a REST API (curl, JavaScript, Python)

> Step-by-step: call a fund data API with an ISIN and get key facts, holdings, risk and performance as JSON. Includes curl, fetch and requests examples and error handling.

Getting reliable fund data used to mean either an enterprise data licence or a fragile PDF scraper. This tutorial shows the third option: one HTTP request that takes an ISIN and returns a complete, structured factsheet as JSON. We will use [FundFacts API](https://fundfactsapi.com/), but the patterns — bearer auth, a long timeout for cold requests, quota headers — apply to any fund data API.

## 1. Get an API key

Sign up for a [free account](https://fundfactsapi.com/signup), open the dashboard and create a key. Keys start with `ffk_` and are shown once; store them in an environment variable, never in client-side code. The free plan gives you ten requests per day, enough to explore the schema; paid plans raise that to 100 or 1,000 requests per day (see [pricing](https://fundfactsapi.com/pricing)).

## 2. Make your first request

```bash
curl --max-time 300 https://fundfactsapi.com/api/v1/funds/IE00B4L5Y983 \
  -H "Authorization: Bearer $FUNDFACTS_API_KEY"
```

Note `--max-time 300`. The first request for an ISIN nobody has asked for yet triggers a live refresh, which takes one to three minutes. Every request in the following 24 hours is served from cache in a few hundred milliseconds. If you cannot wait that long in a request cycle, pre-warm the ISINs you care about in a background job (more on that below).

## 3. Read the response

The envelope is the same for every fund:

```json
{
  "isin": "IE00B4L5Y983",
  "name": "iShares Core MSCI World ETF USD Acc",
  "cached": true,
  "generatedAt": "2026-09-02T13:41:07.112Z",
  "expiresAt": "2026-09-03T13:41:07.112Z",
  "plan": "pro",
  "quota": { "limit": 100, "remaining": 87, "resetAt": "2026-09-03T09:12:44.000Z" },
  "data": {
    "keyFacts": { "assetClass": "Equities", "currency": "USD", "aum": "USD 151.7bn", "inception": "Sep 25, 2009", "holdings": 1344 },
    "riskRating": 4,
    "topHoldings": [{ "name": "NVIDIA Corp", "weight": 5.43 }],
    "sector": [{ "label": "Technology", "weight": 30.5 }],
    "calendarReturns": { "years": ["2021", "2022"], "fund": [21.9, -18] },
    "headlineMetrics": { "ter": "0.20%", "volatility3y": "11.9%", "sharpe3y": "1.21" }
  }
}
```

Three things worth internalising:

- `data` has **the same keys for every fund**. Panels that do not apply (credit quality for an equity ETF, say) are empty arrays or empty strings, never missing. You can write one renderer.
- Breakdowns are arrays of `{ label, weight }` with weights in percent. Holdings use `{ name, weight }`.
- Formatted metrics stay strings with their unit (`"0.20%"`, `"USD 151.7bn"`) so you never lose the currency or the scale. Numbers you will chart (returns, weights) are numbers.

The full list of fields is in the [documentation](https://fundfactsapi.com/docs).

## 4. JavaScript / TypeScript

```typescript
const BASE = "https://fundfactsapi.com/api/v1";

export async function getFund(isin: string) {
  const res = await fetch(`${BASE}/funds/${isin}`, {
    headers: { Authorization: `Bearer ${process.env.FUNDFACTS_API_KEY}` },
    signal: AbortSignal.timeout(300_000),
  });

  if (res.status === 429) {
    const { error } = await res.json();
    throw new Error(`Quota exceeded, retry in ${error.retryAfter}s`);
  }
  if (!res.ok) {
    const { error } = await res.json();
    throw new Error(`${res.status} ${error.code}: ${error.message}`);
  }
  return res.json();
}

const fund = await getFund("IE00B4L5Y983");
console.log(fund.name, fund.data.headlineMetrics.ter, fund.quota.remaining);
```

## 5. Python

```python
import os
import requests

BASE = "https://fundfactsapi.com/api/v1"
HEADERS = {"Authorization": f"Bearer {os.environ['FUNDFACTS_API_KEY']}"}

def get_fund(isin: str) -> dict:
    r = requests.get(f"{BASE}/funds/{isin}", headers=HEADERS, timeout=300)
    if r.status_code == 429:
        retry = r.json()["error"]["retryAfter"]
        raise RuntimeError(f"Quota exceeded, retry in {retry}s")
    r.raise_for_status()
    return r.json()

fund = get_fund("IE00B4L5Y983")
top = fund["data"]["topHoldings"][:5]
for h in top:
    print(f"{h['name']:<30} {h['weight']:>6.2f}%")
```

## 6. Handle errors properly

Every error uses one envelope: `{ "error": { "code", "message", ... } }`.

| Status | Code | What to do |
| --- | --- | --- |
| 400 | `invalid_isin` | Show a validation message; the request was not counted |
| 401 | `missing_api_key` / `invalid_api_key` | Check the header and the key |
| 404 | `fund_not_found` | The ISIN is valid but is not a fund/ETF we can cover; `data` is an empty skeleton |
| 429 | `rate_limited` | Wait `retryAfter` seconds (also in the `Retry-After` header) |
| 502 | `upstream_error` | Temporary upstream issue; retry with back-off |

The `X-RateLimit-Limit`, `X-RateLimit-Remaining` and `X-RateLimit-Reset` headers let you throttle before you hit 429.

## 7. Pre-warm your universe

For a screener or a portfolio tool you know the ISINs in advance. Run a nightly job that requests each one; the first run is slow, every subsequent run and every user request is a cache hit. With a Scale plan (10,000 requests a month) that covers a universe of about two hundred funds with headroom for ad-hoc lookups.

```typescript
for (const isin of universe) {
  await getFund(isin);                 // populates the 24h cache
  await new Promise((r) => setTimeout(r, 500));
}
```

## Next steps

- Learn what each block contains: [ETF holdings and exposure](https://fundfactsapi.com/blog/etf-holdings-api), [performance series](https://fundfactsapi.com/blog/fund-performance-data-explained), [risk indicators](https://fundfactsapi.com/blog/srri-risk-indicator-explained).
- Put it together: [build a fund comparison tool](https://fundfactsapi.com/blog/build-fund-comparison-tool).
- Or just [grab a free key](https://fundfactsapi.com/signup) and paste your own ISIN into the dashboard's "Try it" box.

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