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.
In this guide
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:
| Identifier | Standard | Field in report | What it identifies | API endpoint |
|---|---|---|---|---|
| ISIN | ISO 6166 | Instrument ID | The financial instrument (stock, bond, derivative) | GET /v0/isin |
| LEI | ISO 17442 | Buyer/Seller/Executing | The legal entity involved in the trade | GET /v0/lei |
| CFI | ISO 10962 | Instrument class | The category and attributes of the instrument | GET /v0/cfi |
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" } }
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" } }
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" } ] }
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)); }
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
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.