Client reporting & factsheets

Client reporting and branded factsheets from structured fund data

Quarterly packs, one-page fund summaries and branded factsheets all need the same thing: current, sourced fund data laid out consistently. This page shows how to generate PDF and HTML reports from FundFacts API responses, how to handle as-of dates and how to keep a reporting run within quota.

The problem

Client reporting is repetitive by design. Every quarter, for every client, for every fund held, someone gathers the latest factsheet, copies the key figures into a template, checks the risk indicator has not moved and re-exports the pack. The work is not intellectually hard, but it is error-prone and it scales with the number of clients.

The failure modes are familiar: a stale TER because the factsheet used was last quarter's, a mismatched share class because the template was cloned, or a performance chart whose end date is not printed anywhere.

What you need from the data

A fund page in a client pack usually shows:

  • Name, ISIN, share class, currency and distribution policy
  • Investment objective in the fund house's own words
  • Risk rating on the 1–7 scale
  • Ongoing charges or TER
  • Calendar-year returns for the fund and its benchmark
  • Cumulative returns for standard periods
  • An indexed NAV chart
  • Top ten holdings and sector or country breakdowns
  • The as-of date for every figure

Consistency across funds matters more than richness. If one page has a five-year chart and another has three years because the source document differed, the client notices.

How it works with FundFacts API

Build a template once, then feed it the data object for each ISIN. The example below renders a single fund section as HTML; a PDF engine such as a headless browser takes it from there.

typescript
export async function fundSection(isin: string): Promise<string> {
const res = await fetch(`https://fundfactsapi.com/api/v1/funds/${isin}`, {
headers: { Authorization: `Bearer ${process.env.FUNDFACTS_API_KEY}` },
});
if (!res.ok) throw new Error(`${res.status} loading ${isin}`);
const { name, data } = await res.json();
const years = (data.calendarReturns?.years ?? []) as number[];
const fund = (data.calendarReturns?.fund ?? []) as (number | null)[];
const rows = years
.map((y, i) => `<tr><td>${y}</td><td>${fund[i] ?? "n/a"}</td></tr>`)
.join("");
return `
<section>
<h2>${name} <small>${isin}</small></h2>
<p>${data.investmentObjective ?? ""}</p>
<dl>
<dt>Risk rating</dt><dd>${data.riskRating ?? "n/a"} / 7</dd>
<dt>Ongoing charges</dt><dd>${data.headlineMetrics?.ter ?? "n/a"}%</dd>
<dt>Fund size</dt><dd>${data.headlineMetrics?.aum ?? "n/a"}</dd>
</dl>
<table><tbody>${rows}</tbody></table>
<footer>Data as of ${data.dataAsOf ?? "unknown"}. Not investment advice.</footer>
</section>`;
}

For the NAV chart, indexedPerformance.points is an array of { date, value } rebased to 100, ready for any charting library. The fund performance data guide explains how the calendar, cumulative and indexed series relate to one another.

Design notes

As-of dates. Print dataAsOf on every fund page and, at the pack level, the range of as-of dates across all funds. If a fund's dataAsOf is older than your reporting cut-off, flag it in the pack rather than silently using it.

Caching. Reporting runs are bursty: hundreds of funds in a night, then nothing for a quarter. Load the distinct ISINs across all clients once, store each response with expiresAt, and render every pack from that store. Data refreshes every 24 hours, so a run that spans two days should re-check expiresAt before rendering.

Quota. The number of requests you need is the number of distinct ISINs, not the number of clients. Count them before the run, compare with X-RateLimit-Limit, and schedule across days if needed. A 429 should pause the job until X-RateLimit-Reset.

Errors. 404 fund_not_found in a live client portfolio is a data-quality event; route it to a human. 502 upstream_error is transient: retry the ISIN at the end of the run.

Branding and disclaimers. The response contains no logos or images, which is deliberate: you apply your own branding. Include the standard wording that past performance is not a reliable indicator of future results and that figures are not investment advice, and state that fund data was read from the documents each fund house publishes.

For the end-to-end approach, see fund factsheet automation and the factsheet glossary entry.

Frequently asked questions

Does the API generate the PDF for me?

No. The API returns structured JSON and leaves layout, branding and file generation to you. Most teams render an HTML template from the data and convert it with a headless browser or a server-side PDF library, which keeps fonts, logos and disclaimers entirely under their control. The response deliberately contains no images so nothing from the fund house is reproduced.

Which date should I print on a fund page?

Print data.dataAsOf, the publication date of the documents the figures were read from. It is the date a reader needs to interpret returns and holdings. The top-level generatedAt tells you when the API last read those documents and is useful for your own audit log, but it is not the effective date of the figures and should not be shown as such.

How do I keep charts consistent when funds publish different history lengths?

Decide a standard window, for example five calendar years, and truncate longer series to it. Where a fund has less history because of a recent inception, show the shorter series and state the inception date on the page rather than padding or extrapolating. Reading the length of calendarReturns.years before rendering lets the template choose the right layout.

Can I include the benchmark in the performance table?

Yes, where the fund house publishes it. calendarReturns.benchmark is an array aligned with calendarReturns.years and benchmarkName carries the index name for the column header. Some active funds do not publish a benchmark comparison, in which case the array is absent and the template should drop the column rather than show empty cells.

Build your client reporting & factsheets on FundFacts API

Free plan with 10 requests a day to prototype; paid plans add the AI companion kit that scaffolds the integration for you.