Guide · Python · Validation

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.

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.

ℹ️CFI codes describe the instrument type, not the issuer or the market. Two shares from different companies can have the same CFI code if they have the same voting rights, transfer restrictions, payment status, and form.

2. The 6-position structure

Every CFI code is exactly 6 uppercase letters. Each position has a fixed role:

PositionNameMeaningExample (ESVUFR)
1CategoryAsset class — the top-level instrument typeESVUFR → Equities
2GroupInstrument type within the categoryESVUFR → Shares (common)
3Attribute 1First characteristic — depends on category/groupESVUFR → Voting right
4Attribute 2Second characteristicESVUFR → Transfer: Free
5Attribute 3Third characteristicESVUFR → Partly paid
6Attribute 4Fourth characteristicESVUFR → Registered form
⚠️The attribute letters at positions 3–6 are not universal — the same letter can mean completely different things depending on the category and group. For example, 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.

CodeCategoryTypical instruments
EEquitiesCommon shares, preferred shares, ETF units, convertibles, ADRs
DDebt instrumentsBonds, convertible bonds, MTNs, money market, ABS, MBS
REntitlements (rights)Subscription rights, allotment rights, warrants, mini-futures
OListed optionsExchange-traded call and put options
FFuturesFinancial futures (rates, FX, equity) and commodity futures
SSwapsInterest rate, FX, credit, equity, commodity swaps
HNon-listed & complex optionsOTC options, exotic options, swaptions
ISpotFX spot, commodity spot
JForwardsFRAs, FX forwards, forward equity, forward commodity
KStrategiesMulti-leg combinations — straddles, spreads, mixed strategies
LFinancingRepo agreements, securities lending
TReferential instrumentsCurrencies, indices, interest rates used as underlyings
MOthersCombined 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:

PositionAttribute namePossible values
3Voting rightV Voting · N Non-voting · R Restricted · E Enhanced · X N/A
4Ownership / transfer restrictionsT Restrictions · U Free · X N/A
5Payment statusO Fully paid · P Nil paid · F Partly paid · X N/A
6FormB Bearer · R Registered · N Bearer & registered · M Others · X N/A
Positions 3–6 always use 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:

E
Position 1 · Category

Equities

Asset class — this is an equity instrument

S
Position 2 · Group

Shares (common / ordinary)

Regular voting shares, not preferred or convertible

V
Position 3 · Voting right

Voting

Each share carries a voting right at shareholders' meetings

U
Position 4 · Transfer restrictions

Free

No restrictions — shares can be freely bought and sold

F
Position 5 · Payment status

Partly paid

Shares have not been fully paid up by shareholders

R
Position 6 · Form

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:

D
Position 1 · Category

Debt instruments

This is a fixed-income instrument

B
Position 2 · Group

Bonds

Classic bond — not a convertible, ABS, or money-market instrument

F
Position 3 · Interest / coupon type

Fixed rate

Coupon is a fixed percentage of face value — predictable cash flows

A
Position 4 · Coupon payment frequency

Annual

Coupon is paid once per year

X
Position 5 · Guarantor

Not applicable

No external guarantor — backed solely by the issuer

R
Position 6 · Form

Registered

Holder is recorded in the issuer's register

ℹ️Notice that position 3 letter 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.

ISO 10962:2021
Standard
full attribute decoding
<10ms
Response time
pure in-memory lookup
100/day
Free tier
no credit card

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
If you only need the category for routing (equity vs debt vs derivative), you can read just the first character of the CFI string without calling the API. Reserve the full API call for when you need the structured attribute breakdown or want to validate that the entire 6-character code is well-formed.

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
}
FieldTypeDescription
validboolean6 uppercase letters, recognised category letter
cfistringNormalised (uppercase) input
categoryNamestringHuman-readable asset class (e.g. Equities)
groupNamestring | nullInstrument type within the category, or null if group letter is unrecognised
attributesarray[4]Decoded positions 3–6 — each object has position, code, name, value

Summary

Do not decode positions 3–6 independently — their meaning depends on positions 1 and 2
Do not treat CFI as a static lookup — the ISO standard evolves; use an up-to-date implementation
Use position 1 alone for high-level asset class routing (equity, debt, derivative)
Use X at positions 3–6 for "not applicable" — it is a valid value, not an error
Validate CFI at data ingestion time to catch malformed or truncated instrument records
Combine with ISIN lookup to get a fully enriched instrument record from a single code

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.