---
title: "Build a fund screener on structured factsheet data"
description: "Filter thousands of UCITS share classes by asset class, region, risk rating, TER and size using one JSON endpoint per ISIN. Data model, code and caching."
canonical: https://fundfactsapi.com/use-cases/fund-screener
published: 2026-09-04
updated: 2026-09-09
publisher: FundFacts API
---

# Build a fund screener on structured factsheet data

> Filter thousands of UCITS share classes by asset class, region, risk rating, TER and size using one JSON endpoint per ISIN. Data model, code and caching.

A fund screener lets users narrow thousands of share classes to a shortlist by asset class, region, risk rating, fees and size. This page covers what a screener needs from the underlying data, how to build the universe with FundFacts API and how to keep it fresh without burning quota.

## The problem

A screener is a filter over a universe. The hard part is not the UI; it is having consistent, comparable attributes for every share class in the universe. Factsheets from different fund houses put the asset class in different places, express fees as TER or ongoing charges, quote fund size in fund currency or share-class currency, and publish risk indicators on the SRRI or SRI scale depending on the document.

If you build the attribute table by hand, you end up with a spreadsheet that is out of date the week it is finished. If you build it from raw PDFs, you spend your time on extraction rather than on the product.

## What you need from the data

A useful screener typically filters on a handful of dimensions and sorts on a few more:

- Asset class and sub-asset (equity, fixed income, multi-asset; large cap, high yield, and so on)
- Region focus and sector tilt
- Risk rating on the 1–7 scale used by the [KID](https://fundfactsapi.com/glossary/kid)
- Fees, usually the [TER](https://fundfactsapi.com/glossary/ter) or ongoing charges figure
- Size, as assets under management
- Distribution policy (accumulating or distributing) and share-class currency
- Inception date, so users can exclude funds without a track record

Every one of these must be normalised the same way across issuers, or the filter silently excludes funds that were described differently.

## How it works with FundFacts API

Each call to `GET /api/v1/funds/{isin}` returns the normalised attributes under `data`. For a screener you seed the universe with the ISINs you want to cover, load each one once, and store the fields you filter on in your own database. The API is the source of truth; your table is a read model.

```typescript
type ScreenerRow = {
  isin: string;
  name: string;
  assetClass?: string;
  region?: string;
  riskRating?: number;
  ter?: number;
  aum?: number;
  distribution?: string;
  expiresAt: string;
};

export async function loadRow(isin: string): Promise<ScreenerRow> {
  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} for ${isin}`);
  const json = await res.json();
  const d = json.data;
  return {
    isin: json.isin,
    name: json.name,
    assetClass: d.keyFacts?.assetClass,
    region: d.profile?.regionFocus,
    riskRating: d.riskRating,
    ter: d.headlineMetrics?.ter,
    aum: d.headlineMetrics?.aum,
    distribution: d.keyFacts?.distribution,
    expiresAt: json.expiresAt,
  };
}
```

Filtering then becomes a plain SQL query or an in-memory filter over your rows. The `profile` block is designed for exactly this: `profile.kind`, `profile.category`, `profile.riskBand`, `profile.regionFocus` and `profile.sectorTilt` are short, stable labels intended for facets, while `keyFacts` holds the raw values you display.

A cold ISIN takes one to three minutes to load the first time because the documents have to be read from the fund house; subsequent calls return from cache in milliseconds. Seed the universe in a background job, not in a request handler.

## Design notes

**Caching.** Data is refreshed every 24 hours. Store `expiresAt` next to each row and re-load only rows whose `expiresAt` has passed. A universe of a few hundred ISINs refreshed daily fits the Scale plan's 10,000 requests a month (about 330 a day) if you spread the refresh across the day and prioritise the funds users actually open.

**Quota.** Read `X-RateLimit-Remaining` on every response and pause the refresh job when it drops below a floor you choose. A `429 rate_limited` response includes `X-RateLimit-Reset`; sleep until then rather than retrying immediately.

**Errors.** Treat `404 fund_not_found` as a permanent skip for that ISIN (log it, review weekly) and `502 upstream_error` as a transient failure to retry on the next cycle. Validate ISINs client-side before spending a request; see [what is an ISIN](https://fundfactsapi.com/blog/what-is-an-isin).

**Missing values.** Not every document publishes every field. Filter with null-safe comparisons and show "n/a" rather than treating a missing TER as zero, which would wrongly rank the fund as the cheapest.

**Disclaimers.** A screener presents facts, not recommendations. Show `dataAsOf` beside the figures, label the page "not investment advice", and avoid ranking language ("best", "top") that implies a judgement about fund quality.

For a deeper walkthrough of comparing funds side by side, read [build a fund comparison tool](https://fundfactsapi.com/blog/build-fund-comparison-tool).

## FAQ

### Can I query the API with filters instead of one ISIN at a time?

No. The endpoint takes a single ISIN and returns the full structured factsheet for that share class. A screener is built by loading the ISINs in your universe once, storing the attributes you filter on in your own database and querying that table. This keeps filtering fast, lets you add your own fields and means the API is only called when a record has expired.

### How many ISINs can I keep fresh on each plan?

Data refreshes every 24 hours, so a full daily refresh needs one request per ISIN per day. Free covers 5 lookups a month, Starter 60, Pro 1,000, Scale 10,000 and Enterprise is metered per request. In practice you can cover more than that by refreshing popular funds daily and the long tail less often, since expiresAt tells you exactly which rows are stale.

### Why does a fund show no TER or no fund size?

The values are read from the documents each fund house publishes. If a factsheet or KID does not state a figure, the field is absent rather than guessed. Treat missing values as unknown in your filters: exclude them from a cheapest-first sort, and display n/a rather than zero so users are not misled about cost or size.

### Are the risk ratings comparable across fund houses?

The riskRating field is the 1–7 indicator published in the fund's KID, so it follows the same regulatory methodology across issuers. Note that UCITS KIIDs use the SRRI and PRIIPs KIDs use the SRI, which can differ by a notch for the same fund. The profile.riskBand label gives a coarser grouping that is convenient for a facet filter.

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