CRM & wealth platform enrichment

Enrich CRM and wealth platform positions with fund facts

Advisers spend their day inside a CRM or portfolio management system, and every position in it is just an ISIN and a quantity. Enriching those positions with fund facts turns a list of codes into something an adviser can talk about with a client. This page shows how to do it and keep it fresh.

The problem

A CRM or PMS knows what a client holds, but rarely what those holdings are. The position record says LU0106235293, 1,240 units. To prepare for a review meeting, the adviser opens a browser, finds the fund house's product page, downloads the factsheet and reads off the risk rating and ongoing charges, for every position, for every client.

Enrichment moves that lookup into the system. When a position appears, the platform attaches the fund's key facts to it and refreshes them on a schedule, so the adviser sees the risk rating and fees next to the holding without leaving the record.

What you need from the data

Enrichment fields should be short, stable and safe to display. The typical set:

  • Fund name and share class, to confirm the position matches the intended class
  • Asset class, sub-asset and region focus, for grouping a client's book
  • Risk rating 1–7, for a quick suitability sanity check
  • TER or ongoing charges, for cost conversations
  • Distribution policy and currency, for income and FX questions
  • Fund manager and benchmark name
  • The as-of date of the source documents

Deep data such as full holdings and performance series belongs in a drill-down, not the position list. See portfolio look-through for that layer.

How it works with FundFacts API

Most platforms expose a webhook or a scheduled job hook. The pattern is: on position create, and nightly for positions whose data has expired, call the API and write a compact enrichment record back to the platform's custom fields.

typescript
type Enrichment = {
isin: string;
name: string;
assetClass?: string;
regionFocus?: string;
riskRating?: number;
ter?: number;
distribution?: string;
currency?: string;
manager?: string;
benchmark?: string;
dataAsOf?: string;
expiresAt: string;
};
export async function enrich(isin: string): Promise<Enrichment | null> {
const res = await fetch(`https://fundfactsapi.com/api/v1/funds/${isin}`, {
headers: { Authorization: `Bearer ${process.env.FUNDFACTS_API_KEY}` },
});
if (res.status === 404) return null; // not a fund we can describe
if (!res.ok) throw new Error(`${res.status} loading ${isin}`);
const { name, data, expiresAt } = await res.json();
return {
isin,
name,
assetClass: data.keyFacts?.assetClass,
regionFocus: data.profile?.regionFocus,
riskRating: data.riskRating,
ter: data.headlineMetrics?.ter,
distribution: data.keyFacts?.distribution,
currency: data.keyFacts?.currency,
manager: data.keyFacts?.manager,
benchmark: data.benchmarkName,
dataAsOf: data.dataAsOf,
expiresAt,
};
}

Write the result to the platform's custom fields (most CRMs allow a dozen or so per object) and store expiresAt in one of them so the refresh job knows what to re-load.

Design notes

Not every ISIN is a fund. Client books contain single stocks, bonds and structured products. The API returns 404 fund_not_found for those; treat it as "no enrichment", not as an error, and do not retry it nightly.

Caching. Data refreshes every 24 hours. Enrich once per ISIN, not once per position: a popular fund held by two hundred clients is one request. Re-load only when expiresAt has passed, and only for ISINs still held by at least one client.

Quota. A wealth firm's distinct-ISIN count is usually in the hundreds, which fits a Pro or Scale plan on a daily refresh. Check X-RateLimit-Remaining in the job and spread the work across the day if you are close to the limit. On 429, wait for X-RateLimit-Reset.

Field mapping. CRMs often restrict custom-field types. Store riskRating as a number, ter as a number in percent and dataAsOf as a date, so advisers can filter and sort on them.

Disclaimers. Enriched facts are read from the documents each fund house publishes and shown for information. Display dataAsOf next to the figures, and keep any suitability judgement in the adviser's hands and the firm's process. The enrichment is input, not advice.

For the identifiers you will meet in position data, see what is an ISIN, and for the fee figure, ongoing charges.

Frequently asked questions

What happens when a position's ISIN is a stock or a bond rather than a fund?

The API describes funds and ETFs only, so a single stock, government bond or structured note returns 404 fund_not_found. Record that outcome against the ISIN so your job does not retry it every night and consume quota. Showing the position without enrichment is the correct result; it is not a data error.

How often should the enrichment refresh?

Once a day is enough. The API re-reads each fund's published documents on a 24-hour cycle and tells you the exact moment via expiresAt, so a nightly job that reloads only expired ISINs keeps every position current. Risk ratings, fees and objectives change infrequently, and a daily cadence catches those changes within a day of publication.

Can I use the same enrichment for the client-facing portal?

Yes, the fields are suitable for display, with two additions. Show dataAsOf beside the figures so clients know the effective date, and include a not-investment-advice statement. The investmentObjective text is written by the fund house and can be shown verbatim; avoid adding any commentary that reads as a recommendation.

Which identifier should the CRM store?

The share-class ISIN. Fund names are ambiguous because one fund can have many share classes with different currencies, distribution policies and fee levels, and tickers vary by exchange. If your custodian feed supplies SEDOL or WKN instead, map them to ISINs before enrichment so the API returns the exact class the client holds.

Build your crm & wealth platform enrichment 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.