---
title: "Compliance and suitability monitoring on daily fund data"
description: "Detect risk-rating drift, concentration changes and stale fund documents with a daily snapshot-and-diff loop over the funds your clients hold."
canonical: https://fundfactsapi.com/use-cases/compliance-monitoring
published: 2026-09-04
updated: 2026-09-04
publisher: FundFacts API
---

# Compliance and suitability monitoring on daily fund data

> Detect risk-rating drift, concentration changes and stale fund documents with a daily snapshot-and-diff loop over the funds your clients hold.

Suitability is not a one-off check at onboarding. Risk ratings drift, funds change objective, holdings concentrate and documents go stale. This page describes a monitoring loop that re-reads each fund's published facts daily, compares them with the last known state and raises alerts an operations team can act on.

## The problem

A client is placed in a fund with risk rating 4 that fits their profile. Eighteen months later the fund house republishes the KID with a rating of 5. Nothing in the portfolio system changes; the position looks the same. Unless someone re-reads the document, the mismatch goes unnoticed until an audit or a complaint.

The same applies to concentration, where a fund's top holding grows from 6 percent to 12 percent, and to document freshness, where the most recent factsheet available is months old. Compliance teams know these are the checks to run; the difficulty is running them across every fund, every day, without a data team.

## What you need from the data

A monitoring loop compares a small set of attributes against their previous values:

- Risk rating (1–7) and the profile risk band
- Top holding weight and the number of holdings, as concentration proxies
- Region focus and sector tilt, to detect mandate drift
- Investment objective text, to detect wording changes
- Asset allocation, for multi-asset funds that should stay within bands
- `dataAsOf`, to detect stale documents
- Whether the fund still exists (a 404 on a held fund)

For each, you need both the current value and a stored previous value, plus a rule that says what change is material.

## How it works with FundFacts API

Run a daily job over held ISINs. For each, load the fund, compute a snapshot of the monitored attributes, diff it against yesterday's snapshot and emit alerts.

```typescript
type Snapshot = {
  riskRating?: number;
  riskBand?: string;
  topWeight?: number;
  regionFocus?: string;
  objective?: string;
  dataAsOf?: string;
};

export async function snapshot(isin: string): Promise<Snapshot | "gone"> {
  const res = await fetch(`https://fundfactsapi.com/api/v1/funds/${isin}`, {
    headers: { Authorization: `Bearer ${process.env.FUNDFACTS_API_KEY}` },
  });
  if (res.status === 404) return "gone";
  if (!res.ok) throw new Error(`${res.status} loading ${isin}`);
  const { data } = await res.json();
  return {
    riskRating: data.riskRating,
    riskBand: data.profile?.riskBand,
    topWeight: data.topHoldings?.[0]?.weight,
    regionFocus: data.profile?.regionFocus,
    objective: data.investmentObjective,
    dataAsOf: data.dataAsOf,
  };
}

export function alerts(prev: Snapshot, next: Snapshot, staleDays = 120): string[] {
  const out: string[] = [];
  if (prev.riskRating !== next.riskRating)
    out.push(`risk rating ${prev.riskRating} -> ${next.riskRating}`);
  if ((next.topWeight ?? 0) > 10 && (prev.topWeight ?? 0) <= 10)
    out.push(`top holding above 10%: ${next.topWeight}%`);
  if (prev.regionFocus !== next.regionFocus)
    out.push(`region focus ${prev.regionFocus} -> ${next.regionFocus}`);
  if (prev.objective !== next.objective) out.push("investment objective text changed");
  if (next.dataAsOf) {
    const age = (Date.now() - Date.parse(next.dataAsOf)) / 86_400_000;
    if (age > staleDays) out.push(`documents ${Math.round(age)} days old`);
  }
  return out;
}
```

Route alerts by severity: a risk-rating change on a fund held by clients at the edge of their band is urgent; a stale document is a ticket. The [SRRI explainer](https://fundfactsapi.com/blog/srri-risk-indicator-explained) covers how the rating is derived and why it moves.

## Design notes

**Caching and cadence.** The API refreshes every 24 hours, so a daily job is the right cadence; running more often gains nothing. Store every snapshot with its `expiresAt` and `dataAsOf`. The history is your audit trail and lets you answer "when did we first see this rating".

**Quota.** One request per held ISIN per day. A firm holding a few hundred distinct funds fits a Pro or Scale plan; watch `X-RateLimit-Remaining` and finish the run over two days if needed rather than dropping funds. On `429`, pause until `X-RateLimit-Reset`.

**Errors.** Treat `502 upstream_error` as "no snapshot today", not as a change. Do not raise a false alert because the fund house's site was unavailable. A `404 fund_not_found` on a fund that resolved yesterday is itself an alert.

**Thresholds are yours.** The API supplies facts; what counts as material concentration or an acceptable document age is a policy decision that belongs in your compliance manual, not in code defaults.

**Text diffs.** Objective wording changes for trivial reasons (a typo fix, a reformatted PDF). Normalise whitespace and case before comparing, and show the diff to a reviewer rather than auto-escalating.

**Disclaimers.** Alerts describe changes in published facts and are not a judgement on the fund. Display `dataAsOf` on every alert, and keep the suitability decision with the adviser and the firm's process. Related reading: [MiFID II](https://fundfactsapi.com/glossary/mifid-ii) and [KID](https://fundfactsapi.com/glossary/kid).

## FAQ

### How quickly will a risk-rating change appear in the API?

Each fund's published documents are re-read on a 24-hour cycle, so a new KID is reflected within a day of the fund house publishing it. Your monitoring job then sees the new riskRating on its next run. The lag that matters is usually the fund house's own publication schedule, which is why storing dataAsOf with each snapshot is important for the audit trail.

### Can the API tell me directly when something changed?

No. The endpoint returns the current state of a fund; detecting change is done by comparing today's response with the snapshot you stored yesterday. This is deliberate: your firm decides which fields matter and what threshold is material. The pattern is a few dozen lines of code and gives you a complete history of every monitored attribute.

### Does the API return the SRRI or the SRI?

riskRating is the 1–7 indicator from the fund's current key information document. For UCITS KIIDs that is the SRRI; for PRIIPs KIDs it is the SRI. The two methodologies differ, so a fund that moved from one document format to the other may show a step change that is not a change in the fund itself. Note the document type when reviewing such alerts.

### What should happen when a held fund returns 404?

Treat it as an alert rather than a silent skip. A 404 fund_not_found on a fund that resolved the day before usually means the share class has been closed, merged or renamed, all of which are events a compliance team needs to review. Keep the last good snapshot for the record and assign the position to a human for follow-up.

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