CFI Code Validation in Python — ISO 10962 Decoded
CFI codes appear in ISIN lookups, MiFID II filings, and financial data feeds — but their 6-letter structure is rarely explained. Here's what each position encodes and how to validate and decode them in your Python application.
In this guide
- 1. What is a CFI code?
- 2. The 6-position structure
- 3. Position 1: the 13 asset categories
- 4. Position 2 and 3–6: groups and attributes
- 5. Decoding ESVUFR — equity share step by step
- 6. Decoding DBFAXR — bond step by step
- 7. Where CFI codes appear in practice
- 8. The production-ready solution
- 9. Python code example
- 10. cURL example and response
1. What is a CFI code?
A CFI code (Classification of Financial Instruments) is a standardised 6-character alphabetic identifier defined by ISO 10962:2021. It encodes what a financial instrument is — its asset class, instrument type, and key structural attributes — in a machine-readable format that is consistent across markets, countries, and regulatory regimes.
CFI codes are maintained by the Association of National Numbering Agencies (ANNA) and are included in ISIN records, MiFID II trade reporting, ESMA FIRDS regulatory filings, and most institutional-grade financial data feeds.
2. The 6-position structure
Every CFI code is exactly 6 uppercase letters. Each position has a fixed role:
| Position | Name | Meaning | Example (ESVUFR) |
|---|---|---|---|
| 1 | Category | Asset class — the top-level instrument type | ESVUFR → Equities |
| 2 | Group | Instrument type within the category | ESVUFR → Shares (common) |
| 3 | Attribute 1 | First characteristic — depends on category/group | ESVUFR → Voting right |
| 4 | Attribute 2 | Second characteristic | ESVUFR → Transfer: Free |
| 5 | Attribute 3 | Third characteristic | ESVUFR → Partly paid |
| 6 | Attribute 4 | Fourth characteristic | ESVUFR → Registered form |
F at position 3 means Fixed rate for a bond but Partly paid for an equity share. Always resolve attributes in the context of positions 1 and 2.3. Position 1: the 13 asset categories
The first letter narrows the instrument down to one of 13 top-level asset classes defined by ISO 10962:2021. Any 6-letter string starting with an unrecognised letter is invalid.
| Code | Category | Typical instruments |
|---|---|---|
| E | Equities | Common shares, preferred shares, ETF units, convertibles, ADRs |
| D | Debt instruments | Bonds, convertible bonds, MTNs, money market, ABS, MBS |
| R | Entitlements (rights) | Subscription rights, allotment rights, warrants, mini-futures |
| O | Listed options | Exchange-traded call and put options |
| F | Futures | Financial futures (rates, FX, equity) and commodity futures |
| S | Swaps | Interest rate, FX, credit, equity, commodity swaps |
| H | Non-listed & complex options | OTC options, exotic options, swaptions |
| I | Spot | FX spot, commodity spot |
| J | Forwards | FRAs, FX forwards, forward equity, forward commodity |
| K | Strategies | Multi-leg combinations — straddles, spreads, mixed strategies |
| L | Financing | Repo agreements, securities lending |
| T | Referential instruments | Currencies, indices, interest rates used as underlyings |
| M | Others | Combined and miscellaneous instruments not covered above |
4. Position 2 and 3–6: groups and attributes
Position 2 selects a group within the category — for example, within E (Equities) the group letter distinguishes common shares (S), preferred shares (P), convertible shares (C), ETF units (U), and so on.
Once category and group are fixed, each of the four attribute positions maps to a named characteristic. For ES (Equities — Shares common) the four attributes are:
| Position | Attribute name | Possible values |
|---|---|---|
| 3 | Voting right | V Voting · N Non-voting · R Restricted · E Enhanced · X N/A |
| 4 | Ownership / transfer restrictions | T Restrictions · U Free · X N/A |
| 5 | Payment status | O Fully paid · P Nil paid · F Partly paid · X N/A |
| 6 | Form | B Bearer · R Registered · N Bearer & registered · M Others · X N/A |
X to mean "not applicable or undefined" regardless of attribute type. When you see ESVUXR, position 5 is not meaningful for that particular share.5. Decoding ESVUFR — equity share step by step
ESVUFR is the CFI code returned by the IsValid ISIN API for PKN ORLEN SA, Poland's largest energy company. Here is what each position means:
Equities
Asset class — this is an equity instrument
Shares (common / ordinary)
Regular voting shares, not preferred or convertible
Voting
Each share carries a voting right at shareholders' meetings
Free
No restrictions — shares can be freely bought and sold
Partly paid
Shares have not been fully paid up by shareholders
Registered
Owner's name is recorded in the issuer's share register
# What ESVUFR tells you about the instrument: result = { "category": "E", "categoryName": "Equities", "group": "S", "groupName": "Shares (common / ordinary)", "attributes": [ {"position": 3, "code": "V", "name": "Voting right", "value": "Voting"}, {"position": 4, "code": "U", "name": "Ownership / transfer restrictions","value": "Free"}, {"position": 5, "code": "F", "name": "Payment status", "value": "Partly paid"}, {"position": 6, "code": "R", "name": "Form", "value": "Registered"}, ], }
6. Decoding DBFAXR — bond step by step
DBFAXR is a typical CFI code for a plain-vanilla government or corporate bond:
Debt instruments
This is a fixed-income instrument
Bonds
Classic bond — not a convertible, ABS, or money-market instrument
Fixed rate
Coupon is a fixed percentage of face value — predictable cash flows
Annual
Coupon is paid once per year
Not applicable
No external guarantor — backed solely by the issuer
Registered
Holder is recorded in the issuer's register
F means Fixed rate in the bond context (DB), but means Partly paid in the equity context (ES). This is why you cannot decode positions 3–6 without first knowing positions 1 and 2.7. Where CFI codes appear in practice
ISIN lookup responses
Financial data APIs that resolve ISIN codes typically include the CFI code in the response. The IsValid ISIN API returns a cfiCode field — you can feed it directly into the CFI endpoint to get a structured breakdown.
# Example: enrich an ISIN lookup with CFI decoding isin_resp = requests.get( f"{BASE_URL}/v0/isin", params={"value": "PL0000503135"}, headers={"Authorization": f"Bearer {API_KEY}"}, ) cfi_code = isin_resp.json()["cfiCode"] # -> "ESVUFR" cfi_resp = requests.get( f"{BASE_URL}/v0/cfi", params={"value": cfi_code}, headers={"Authorization": f"Bearer {API_KEY}"}, ) cfi = cfi_resp.json() print(cfi["categoryName"]) # -> "Equities" print(cfi["groupName"]) # -> "Shares (common / ordinary)" print(cfi["attributes"][0]["value"]) # -> "Voting"
MiFID II trade reporting (ESMA FIRDS)
Under MiFID II, investment firms must report trades with instrument reference data including the CFI code. The ESMA FIRDS database — which the IsValid ISIN endpoint queries — stores CFI codes for all ~700 000 instruments registered for EU trading.
Portfolio analytics and risk systems
Risk engines and portfolio management systems use CFI codes to determine instrument type for margin calculations, asset allocation bucketing, and regulatory capital treatment. Parsing the category letter alone is often enough to route instruments through the correct calculation path.
Data validation at ingestion time
When importing financial instrument data from third-party feeds, validating the CFI code catches malformed or truncated records before they corrupt your database. A valid CFI must be exactly 6 characters, all uppercase letters, and start with a recognised category letter.
import re # Quick structural check before sending to the API def is_cfi_candidate(value: str) -> bool: return isinstance(value, str) and bool(re.fullmatch(r"[A-Z]{6}", value)) # Filter out obviously invalid entries in a batch valid_candidates = [i for i in instruments if is_cfi_candidate(i["cfi"])]
8. The production-ready solution
The IsValid CFI API validates a CFI code against the full ISO 10962:2021 definition and returns a structured breakdown of all 6 positions — category name, group name, and the label and decoded value of each attribute. Because attribute mappings vary by category and group, maintaining the full lookup table yourself is error-prone; the API handles it for you.
Full parameter reference and response schema: CFI Validation API docs →
9. Python code example
Using the requests library — the de facto standard for HTTP in Python. Install it with pip install requests.
# cfi_validator.py import os import requests API_KEY = os.environ["ISVALID_API_KEY"] BASE_URL = "https://api.isvalid.dev" def validate_cfi(cfi: str) -> dict: """Validate and decode a CFI code using the IsValid API. Args: cfi: 6-character CFI code (case-insensitive). Returns: Structured breakdown of all 6 positions. Raises: requests.HTTPError: If the API returns a non-2xx status. """ response = requests.get( f"{BASE_URL}/v0/cfi", params={"value": cfi}, headers={"Authorization": f"Bearer {API_KEY}"}, ) response.raise_for_status() return response.json() # ── Example usage ──────────────────────────────────────────────────────────── result = validate_cfi("ESVUFR") if not result["valid"]: print("Invalid CFI code") else: print(f"Category: {result['categoryName']}") # -> "Equities" print(f"Group: {result['groupName']}") # -> "Shares (common / ordinary)" for attr in result["attributes"]: print(f"Position {attr['position']} ({attr['name']}): {attr['value']}") # Position 3 (Voting right): Voting # Position 4 (Ownership / transfer restrictions): Free # Position 5 (Payment status): Partly paid # Position 6 (Form): Registered
In a portfolio analytics pipeline — routing instruments to the correct calculation path based on asset class:
# Classify a batch of instruments by CFI category def classify_instruments(instruments: list[dict]) -> list[dict]: asset_class_map = { "E": "equity", "D": "fixed-income", "O": "option", "F": "future", "S": "swap", "J": "forward", } results = [] for inst in instruments: if not inst.get("cfi"): results.append({**inst, "asset_class": "unknown"}) continue cfi = validate_cfi(inst["cfi"]) if not cfi["valid"]: results.append({**inst, "asset_class": "invalid"}) continue # Route by category for downstream processing asset_class = asset_class_map.get(cfi["category"], "other") results.append({ **inst, "asset_class": asset_class, "instrument_type": cfi["groupName"], "cfi_attributes": cfi["attributes"], }) return results
10. cURL example and response
Validate and decode an equity share CFI:
curl -H "Authorization: Bearer YOUR_API_KEY" \ "https://api.isvalid.dev/v0/cfi?value=ESVUFR"
Validate a bond CFI:
curl -H "Authorization: Bearer YOUR_API_KEY" \ "https://api.isvalid.dev/v0/cfi?value=DBFAXR"
Valid CFI response (ESVUFR):
{ "valid": true, "cfi": "ESVUFR", "category": "E", "categoryName": "Equities", "group": "S", "groupName": "Shares (common / ordinary)", "attributes": [ { "position": 3, "code": "V", "name": "Voting right", "value": "Voting" }, { "position": 4, "code": "U", "name": "Ownership / transfer restrictions","value": "Free" }, { "position": 5, "code": "F", "name": "Payment status", "value": "Partly paid" }, { "position": 6, "code": "R", "name": "Form", "value": "Registered" } ] }
Invalid CFI response:
{ "valid": false }
| Field | Type | Description |
|---|---|---|
| valid | boolean | 6 uppercase letters, recognised category letter |
| cfi | string | Normalised (uppercase) input |
| categoryName | string | Human-readable asset class (e.g. Equities) |
| groupName | string | null | Instrument type within the category, or null if group letter is unrecognised |
| attributes | array[4] | Decoded positions 3–6 — each object has position, code, name, value |
Summary
See also
Validate CFI codes instantly
Free tier includes 100 API calls per day. No credit card required. Full ISO 10962:2021 attribute decoding for all 13 asset categories.