Why does the first request for a fund take so long?
A fund never loaded before is read live from the fund house's documents, which takes 15 seconds to a few minutes. Later calls are instant, even past 24 hours. Timeouts, batches and pre-warming.Published September 12, 2026 · Updated October 25, 2026 · by FundFacts APIShort answer
Because the data is read from the fund house at that moment. A cold ISIN (never loaded, or not in the last 7 days) triggers a read of the product page, factsheet PDF, KID and holdings / NAV files, which takes 15 seconds to about 3 minutes. The server allows 300 seconds; set that timeout and do not send a second request for the same ISIN while the first runs. Every later call returns from the store in well under a second with cached: true; past 24 hours it is flagged stale: true and refreshed in the background.
What happens on a cold load
- The issuer adapter resolves the ISIN to the fund house's product page.
- The page, the monthly factsheet, the KID and (where published) the holdings and NAV files are fetched and parsed.
- Figures are normalised into the
datashape, the profile is derived, series are computed. - The payload is stored and returned; it is due for a refresh 24 hours later.
PDF parsing and per-issuer sites set the pace. Two callers asking for the same cold ISIN at once share one load.
Design rules
- Timeout 300 s on
GET /funds/{isin}andPOST /funds. Show "loading fund data, first load can take a couple of minutes" in a UI. - Never retry in flight. A second request for the same ISIN waits on the same load and, once it completes, is counted too.
- Batch and let pending work for you.
POST /fundswithwait: truereturns what fits the time budget and marks the restpending; they finish in the background and the next call finds them warm. - Pre-warm in a job, not a request handler. Seed your universe overnight, a few ISINs in parallel at most, and your users only ever hit warm payloads.
- Cache locally until `expiresAt`. Then even the warm call is saved.
The demo endpoint never loads
GET /demo/funds/{isin} only returns funds already in the store (404 not_cached otherwise), which is why it is instant and keyless.
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]}'