How fresh is fund data? As-of dates, publication lags and why a 24-hour cache is enough
Daily NAV, monthly holdings, yearly KID: what 'as of' means for each factsheet block, how to store it, and why a daily refresh matches how funds publish.Published 21 September 2026 · 6 min read · by FundFacts APIFund data is not one dataset with one update frequency. A fund's NAV is struck daily, its holdings and exposures are published monthly (daily for some ETF issuers), its factsheet is monthly, its KID is reviewed at least annually, and its fund size moves with every dealing day but is reported at the factsheet's date. "Fresh" therefore means "no older than the fund's own publication cadence", and for factsheet-type data that cadence is measured in days to months. A store that refreshes each fund every 24 hours is at least as current as the documents it is built from, and the important thing is not the refresh rate but carrying the as-of date with every figure.
What "as of" means on a factsheet
The date printed near the top of a factsheet is the reporting date: the day the portfolio snapshot was taken, usually the last business day of the month. But it is a snapshot of several things that are not equally fresh:
| Block | Underlying cadence | Typical lag behind the reporting date |
|---|---|---|
| NAV and performance | Daily NAV, published the next business day | None at month end; the factsheet itself appears days to weeks later |
| Fund size (AUM) | Daily | Reported at the factsheet date |
| Top holdings, sector, country | Monthly for most funds; daily holdings files for many ETF issuers | Some issuers lag holdings one month behind performance |
| Number of holdings | Same as holdings | Same |
| TER / ongoing charges | From the KID, reviewed at least annually and when costs change | Can be up to a year old relative to today's actual costs |
| Risk indicator (SRI) | Recalculated per KID rules, updated when it changes class | Same |
| Calendar-year returns | Annual, fixed once the year closes | None |
So a factsheet dated 31 August can carry August performance, July holdings and a February KID's fee. This is normal and not an error. The practical consequence is that a single "last updated" timestamp on a fund record is not enough; you want the date each figure refers to.
The two dates you should store
FundFacts API returns two dates for every fund, and they answer different questions:
data.dataAsOfis the date the figures refer to: the latest NAV observation when the issuer publishes a NAV history, otherwise the factsheet's reporting date. This is the date to show next to the numbers ("data as of 31 August 2026").generatedAt(in the envelope and indata) is when the payload was produced from the documents.expiresAtis when the store will refresh it on the next request, 24 hours later.
The distinction removes a common confusion. A payload generated this morning can legitimately carry a dataAsOf from the end of last month, because the fund has not published anything newer. Conversely, a payload with yesterday's dataAsOf was built from a daily NAV file. Both are fresh in the sense that matters: they reflect what the fund has disclosed.
bashcurl https://fundfactsapi.com/api/v1/funds/IE00B4L5Y983 \-H "Authorization: Bearer ffk_live_..."
json{"isin": "IE00B4L5Y983","cached": true,"generatedAt": "2026-09-21T06:02:11.408Z","expiresAt": "2026-09-22T06:02:11.408Z","data": {"dataAsOf": "2026-09-18","generatedAt": "2026-09-21T06:02:11.408Z","keyFacts": { "aum": "USD 105.4 bn" },"headlineMetrics": { "ter": "0.20%" }}}
Why a 24-hour cycle matches how funds publish
Consider what could change between two requests an hour apart. The NAV, if the issuer publishes it daily, changes once per business day, after the close. Holdings files from issuers who publish daily are updated once a day. Factsheets change once a month. KIDs change a few times a year at most. Nothing in a factsheet payload changes intraday, so refreshing more often than daily produces identical payloads and costs the issuer's servers and yours.
A 24-hour store also gives the behaviour products need: the first request for an ISIN loads the fund's documents (a cold load takes roughly one to three minutes for a fund nobody has asked for yet), and every later request within 24 hours returns in well under a second with cached: true. After 24 hours the next request rebuilds the payload and serves the new one. Concurrent requests for the same cold fund trigger one load. Funds in the pre-warmed universe are refreshed on a schedule, so the popular ETFs are always warm. The freshness and sourcing docs describe the mechanics.
For the same reason, caching on your side until expiresAt is safe. The ISIN lookup post shows a client that does this, and the pattern cuts usage to one request per fund per day regardless of how many users ask.
When you do need something faster
Two kinds of data are genuinely intraday, and neither is factsheet data:
- Exchange prices of ETF shares. Market price, bid and ask, and the premium or discount to NAV move all day and belong to a market-data feed. The NAV vs market price post explains why published performance uses NAV instead.
- Order and settlement data for your own transactions, which comes from your broker or custodian.
If your product shows a factsheet with a live price next to it, keep the two visibly separate: "price 15:42" and "data as of 31 August". Mixing a live price into a NAV-based series makes the last point incomparable with the rest.
Detecting when something actually changed
Daily refreshes produce many identical payloads. What most products want to know is when a tracked figure moved: the TER changed, the risk indicator moved a class, the manager changed, the fund size crossed a threshold, or the top-five holdings changed. Diffing payloads yourself works; the GET /api/v1/changes feed (Scale and Enterprise) does it server-side and returns the tracked fields that moved between refreshes, plus a fund.refreshed event whenever dataAsOf advances. Webhooks on the same plans push those events. The compliance monitoring use case is built on this.
bashcurl "https://fundfactsapi.com/api/v1/changes?since=2026-09-01T00:00:00Z&isins=IE00B4L5Y983,IE00B3RBWM25" \-H "Authorization: Bearer ffk_live_..."
Displaying freshness honestly
- Show `dataAsOf` next to every block that carries figures, and in the same format everywhere. Users rarely object to a month-old holdings date; they object to not being told.
- Do not show `generatedAt` as "updated" without explanation. It says when the payload was rebuilt, not when the fund published.
- Expect block-level skew. If you have both a NAV-derived
dataAsOfand a factsheet-derived holdings snapshot, the holdings can be older; say so in a footnote. - Keep history. Store each day's
dataAsOfand the tracked fields, so you can answer "when did the fee change" later. The change feed gives you this for free on Scale. - Treat empty as unknown, not zero. A blank
dataAsOfor an empty metric string means the document does not state it.
For AI assistants the same rule applies in prose: every figure the model quotes should carry its date. The LLM grounding post turns that into a system-prompt rule, and the MCP tool descriptions ask the model to do it by default.
The short version: fund data is slow-moving by construction, a daily store is faster than the documents it reads, and the as-of date is the field that makes the whole thing trustworthy. Try it on a fund you follow with a free key and compare dataAsOf with the date on the issuer's factsheet.
FAQ
How often is ETF holdings data updated?
It depends on the issuer. Many large ETF issuers publish a full holdings file every business day; most traditional funds publish holdings monthly, sometimes with a one-month lag, and some only disclose their top ten. The dataAsOf field reports the date the returned figures refer to.
Is fund data available in real time?
Factsheet data is not real-time by nature: NAV is struck once a day and holdings, fees and risk indicators change monthly or yearly. Only the exchange price of an ETF's shares moves intraday, and that is market data rather than fund data.
What is the difference between dataAsOf and generatedAt?
dataAsOf is the date the fund's figures refer to (the latest NAV observation or the factsheet's reporting date). generatedAt is when the payload was built from the documents. A payload can be generated today with figures as of last month if the fund has published nothing newer.
Is caching fund data for 24 hours acceptable?
Yes. Nothing in a factsheet changes intraday, so a daily refresh is at least as current as the documents. FundFacts stores each fund for 24 hours, returns expiresAt so clients can cache until then, and offers a change feed for detecting when tracked fields actually moved.