Guide · Node.js · Compliance

MiFID II Reporting — Validating ISIN, LEI and CFI Together

Every MiFID II transaction report needs three validated identifiers: the instrument (ISIN), the counterparty (LEI), and the instrument classification (CFI). Here's how to validate all three before submission.

1. What MiFID II requires

MiFID II (Markets in Financial Instruments Directive II) is the EU regulation governing investment services. Article 26 requires investment firms to submit transaction reports to National Competent Authorities (NCAs) within T+1.

Each transaction report must include:

  • The financial instrument traded — identified by ISIN
  • The counterparties involved — identified by LEI
  • The instrument classification — encoded as CFI

NCAs reject reports with invalid, missing, or inconsistent identifiers. Pre-validation before submission prevents costly resubmissions and regulatory scrutiny.

RTS 25 — Instrument Reference Data

ESMA maintains the Financial Instruments Reference Data System (FIRDS). Every instrument admitted to trading on an EU venue receives an ISIN and CFI. The IsValid ISIN API cross-references FIRDS.

RTS 22 — Transaction Reporting

Each report must include the LEI of the executing entity, the LEI of the client (if a legal entity), and the ISIN + CFI of the instrument. All must pass format validation.

ESMA Validation Rules

ESMA publishes validation rules that NCAs enforce. Invalid ISIN checksums, LAPSED LEIs, and missing CFI codes are among the top rejection reasons.


2. The three identifiers in a transaction report

A single MiFID II transaction report references at least three ISO-standard identifiers. Each one serves a different purpose and uses a different validation algorithm:

IdentifierStandardField in reportWhat it identifiesAPI endpoint
ISINISO 6166Instrument IDThe financial instrument (stock, bond, derivative)GET /v0/isin
LEIISO 17442Buyer/Seller/ExecutingThe legal entity involved in the tradeGET /v0/lei
CFIISO 10962Instrument classThe category and attributes of the instrumentGET /v0/cfi
ℹ️All three identifiers are validated independently — but they are semantically linked. The CFI code should be consistent with the instrument type identified by the ISIN, and the LEI must belong to a legal entity that is authorised to transact in that instrument class.

3. ISIN — instrument identification

An International Securities Identification Number (ISIN) is a 12-character alphanumeric code defined by ISO 6166. It consists of a 2-letter country prefix (the issuing country or XS for Eurobonds), a 9-character National Securities Identifying Number (NSIN), and a single check digit computed using the Luhn algorithm applied to the alphanumeric-to-numeric conversion of the full code.

The IsValid ISIN API performs three operations in one call:

  • Validates the Luhn checksum
  • Looks up the instrument in the ESMA FIRDS database (primary) — returns instrument name (FISN), CFI, venue
  • Falls back to the OpenFIGI API (global fallback) — returns name, exchange, ticker

Example response — US equity via OpenFIGI

{
  "valid": true,
  "isin": "US0378331005",
  "countryCode": "US",
  "nsin": "037833100",
  "checkDigit": "5",
  "found": true,
  "dataSource": "openfigi",
  "instrument": {
    "name": "APPLE INC",
    "ticker": "AAPL",
    "exchangeCode": "US"
  }
}
⚠️For MiFID II, check both valid === true and found === true. A valid checksum alone does not confirm the instrument exists in a reference database — and NCAs require instruments to be registered in FIRDS for EU-venue trades.

4. LEI — counterparty identification

A Legal Entity Identifier (LEI) is a 20-character alphanumeric code defined by ISO 17442. The last two characters are check digits computed using the MOD-97 algorithm (ISO 7064) — the same algorithm used for IBAN validation.

The IsValid LEI API validates the checksum and looks up the entity in the GLEIF database (~2.3 million records), returning the legal name, country, entity status, registration status, and the issuing Local Operating Unit (LOU).

Compact response example

{
  "valid": true,
  "lei": "784F5XWPLTWKTBV3E584",
  "found": true,
  "dataSource": "gleif-db",
  "entity": {
    "legalName": "GOLDMAN SACHS GROUP INC",
    "country": "US",
    "entityStatus": "ACTIVE",
    "registrationStatus": "ISSUED",
    "nextRenewal": "2025-08-14"
  }
}
⚠️LAPSED LEIs are the #1 rejection reason for MiFID II reports. LEIs must be renewed annually with the issuing LOU. Always check entity.registrationStatus === "ISSUED" — a LEI with status LAPSED will pass the MOD-97 checksum but will be rejected by the NCA.

5. CFI — instrument classification

A CFI code (Classification of Financial Instruments) is a 6-character alphabetic code defined by ISO 10962. Each position encodes a specific aspect of the instrument:

  • Position 1: Category — E=Equities, D=Debt, R=Entitlements/Rights, O=Options, F=Futures, S=Swaps, and more
  • Position 2: Group — the instrument type within the category
  • Positions 3-6: Attributes — context-dependent on category and group (voting rights, transfer restrictions, payment status, form, coupon type, etc.)

The IsValid CFI API decodes the full structure and validates each position against the ISO 10962:2021 definition.

Example response — common equity share

{
  "valid": true,
  "cfi": "ESNTFR",
  "category": "E",
  "categoryName": "Equities",
  "group": "S",
  "groupName": "Shares (common / ordinary)",
  "attributes": [
    { "position": 3, "code": "N", "name": "Voting right", "value": "Non-voting" },
    { "position": 4, "code": "T", "name": "Ownership / transfer restrictions", "value": "Restrictions" },
    { "position": 5, "code": "F", "name": "Payment status", "value": "Partly paid" },
    { "position": 6, "code": "R", "name": "Form", "value": "Registered" }
  ]
}
ℹ️For MiFID II, the CFI must match the instrument's actual characteristics. Mismatched CFI codes — such as reporting a bond with an equity CFI — trigger validation warnings and may result in NCA queries or rejections.

6. Validating all three in parallel

The three endpoints are independent — call them in parallel with Promise.all to minimise latency. Using the built-in fetch API available in Node.js 18+:

// mifid-validator.mjs
const API_KEY = process.env.ISVALID_API_KEY;
const BASE = 'https://api.isvalid.dev';
const headers = { Authorization: `Bearer ${API_KEY}` };

async function callApi(path, params) {
  const url = new URL(path, BASE);
  Object.entries(params).forEach(([k, v]) => url.searchParams.set(k, v));
  const res = await fetch(url, { headers });
  if (!res.ok) throw new Error(`${path}: ${res.status}`);
  return res.json();
}

async function validateTransaction({ isin, lei, cfi }) {
  const [isinResult, leiResult, cfiResult] = await Promise.all([
    callApi('/v0/isin', { value: isin }),
    callApi('/v0/lei', { value: lei }),
    callApi('/v0/cfi', { value: cfi }),
  ]);

  const errors = [];

  if (!isinResult.valid) errors.push('ISIN: invalid checksum');
  else if (!isinResult.found) errors.push('ISIN: not found in reference data');

  if (!leiResult.valid) errors.push('LEI: invalid checksum');
  else if (!leiResult.found) errors.push('LEI: entity not found in GLEIF');
  else if (leiResult.entity?.registrationStatus === 'LAPSED')
    errors.push('LEI: registration is LAPSED — renew before reporting');

  if (!cfiResult.valid) errors.push('CFI: invalid code');

  return {
    valid: errors.length === 0,
    errors,
    isin: isinResult,
    lei: leiResult,
    cfi: cfiResult,
  };
}

// ── Example: validate a trade report ──
const report = await validateTransaction({
  isin: 'US0378331005',   // Apple Inc
  lei: '784F5XWPLTWKTBV3E584', // Goldman Sachs (executing firm)
  cfi: 'ESNTFR',          // Common shares, non-voting, restricted, partly paid, registered
});

if (report.valid) {
  console.log('All identifiers valid — ready to submit');
} else {
  console.log('Validation errors:');
  report.errors.forEach(e => console.log(' ', e));
}
Run this validation before submitting to the ARM (Approved Reporting Mechanism). Catching errors before submission avoids T+1 deadline pressure and costly resubmissions.

7. cURL examples

Validate an ISIN (Apple Inc):

curl -H "Authorization: Bearer YOUR_API_KEY" \
  "https://api.isvalid.dev/v0/isin?value=US0378331005"

Validate a LEI (Goldman Sachs):

curl -H "Authorization: Bearer YOUR_API_KEY" \
  "https://api.isvalid.dev/v0/lei?value=784F5XWPLTWKTBV3E584"

Validate a CFI code (common equity share):

curl -H "Authorization: Bearer YOUR_API_KEY" \
  "https://api.isvalid.dev/v0/cfi?value=ESNTFR"

8. Common rejection reasons

These are the most frequent reasons NCAs reject MiFID II transaction reports — and how to prevent them with pre-validation:

Invalid ISIN checksum

Transposition errors in the ISIN are the most basic failure. The Luhn algorithm catches single-digit errors and most transpositions. Pre-validate every ISIN before it enters your reporting pipeline.

const result = await callApi('/v0/isin', { value: isin });

if (!result.valid) {
  throw new Error(`ISIN ${isin} failed checksum — check for typos`);
}

LAPSED LEI

The most common rejection reason. LEIs must be renewed annually with the issuing LOU. A LAPSED LEI passes the MOD-97 checksum and is found in GLEIF — but NCAs will reject the report. Check registrationStatus and alert the counterparty to renew.

const result = await callApi('/v0/lei', { value: lei });

if (result.valid && result.found) {
  if (result.entity.registrationStatus === 'LAPSED') {
    // Do not submit — contact the entity to renew their LEI
    throw new Error(
      `LEI ${lei} (${result.entity.legalName}) is LAPSED. ` +
      `Next renewal was due ${result.entity.nextRenewal}. ` +
      `Contact the entity to renew with their LOU before reporting.`
    );
  }
}

Unknown instrument

An ISIN that passes the checksum but is not found in FIRDS or OpenFIGI. This can happen with newly issued instruments or OTC products. For MiFID II, the instrument should exist in ESMA FIRDS — if it does not, the trade may not be reportable via the standard flow.

const result = await callApi('/v0/isin', { value: isin });

if (result.valid && !result.found) {
  // Valid checksum but not in any reference database
  // Flag for manual review — may be a new issue or OTC product
  console.warn(`ISIN ${isin} passes checksum but is not in FIRDS or OpenFIGI`);
}

CFI mismatch

The CFI code does not match the instrument's actual characteristics — for example, reporting a bond with an equity CFI code. Use the decoded attributes from the CFI API to verify consistency with the instrument type returned by the ISIN lookup.

const [isinResult, cfiResult] = await Promise.all([
  callApi('/v0/isin', { value: isin }),
  callApi('/v0/cfi', { value: cfi }),
]);

// Cross-check: if ISIN data includes a CFI from FIRDS, compare it
if (isinResult.cfiCode && isinResult.cfiCode !== cfi) {
  console.warn(
    `CFI mismatch: report says ${cfi} but FIRDS says ${isinResult.cfiCode}`
  );
}

9. Summary checklist

Do not submit reports without pre-validating all three identifiers
Do not accept LAPSED LEIs — they cause NCA rejections
Do not skip the ISIN lookup — a valid checksum does not guarantee the instrument exists in FIRDS
Validate ISIN, LEI, and CFI in parallel before submission
Check registrationStatus for LEI — only "ISSUED" is acceptable
Use the ISIN dataSource field to confirm FIRDS or OpenFIGI coverage

See also

Start validating for MiFID II

Free tier includes 100 API calls per day. No credit card required. ISIN, LEI, and CFI validation all included.