---
title: "Client reporting and branded factsheets from structured fund data"
description: "Generate branded PDF and HTML fund pages, quarterly packs and factsheets from one JSON payload per ISIN, with as-of dates and disclaimers handled right."
canonical: https://fundfactsapi.com/use-cases/client-reporting
published: 2026-09-04
updated: 2026-09-04
publisher: FundFacts API
---

# Client reporting and branded factsheets from structured fund data

> Generate branded PDF and HTML fund pages, quarterly packs and factsheets from one JSON payload per ISIN, with as-of dates and disclaimers handled right.

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](https://fundfactsapi.com/blog/fund-performance-data-explained) 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](https://fundfactsapi.com/blog/fund-factsheet-automation) and the [factsheet](https://fundfactsapi.com/glossary/factsheet) glossary entry.

## FAQ

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

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