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 isvalid-sdk package or the requests library. Install with pip install isvalid-sdk or pip install requests.
# cfi_validator.py import os from isvalid_sdk import IsValidConfig, create_client iv = create_client(IsValidConfig(api_key=os.environ["ISVALID_API_KEY"])) result = iv.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
Python integration notes
Pydantic V2 makes it straightforward to validate CFI Code as part of a data model. Use @field_validator or the Annotated pattern with AfterValidator to call the IsValid API inside the validator and raise aValueError on failure. The validator runs automatically whenever the model is instantiated — in a FastAPI request body, a SQLModel ORM model, or a standalone Pydantic parse. Initialise the API client as a module-level singleton so it is not re-created on every request.
FastAPI and Django integration
In FastAPI, inject the IsValid client viaDepends() so the samehttpx.AsyncClient connection pool is shared across all concurrent requests. Pair it with async route handlers to keep the event loop non-blocking during CFI Code validation. In Django, implement aclean() method on your model or form field to call the synchronous SDK client; wrap it insync_to_async() if you are using Django Channels or async views.
When processing a batch of CFI Code values — during a CSV import or a nightly reconciliation job — useasyncio.gather() with a shared httpx.AsyncClientand an asyncio.Semaphoreto cap concurrency at the API rate limit. This brings total validation time from O(n) sequential to O(1) bounded by the pool size.
Handle httpx.HTTPStatusError andhttpx.RequestErrorseparately. A 422 from IsValid means the CFI Code is structurally invalid — surface this as a validation error to the user. A 503 or network error means the API is temporarily unavailable — retry with exponential backoff usingtenacity before falling back gracefully.
- Store
ISVALID_API_KEYin a.envfile and load it withpython-dotenvat startup - Use
pytest-asynciowithasyncio_mode = "auto"for testing async validation paths - Type-annotate validated fields with
NewType('CfiCode', str)for clarity in function signatures - Apply
.strip()and.upper()before validation — CFI Code values often arrive with stray whitespace or mixed case from user input
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.