Guide · Python · Validation

DTI Validation in Python

Digital Token Identifiers are the ISO standard for naming crypto assets globally. Here's how to validate them in Python — and why a regex is nowhere near enough.

1. What is a DTI?

A Digital Token Identifier (DTI) is a 9-character alphanumeric code that uniquely identifies a digital asset — cryptocurrency, stablecoin, tokenised security, or any other token — at a global level. The standard is defined by ISO 24165, published in September 2021, and maintained by the Digital Token Identifier Foundation (DTIF).

Each DTI is assigned once, never reused, and unambiguously distinguishes one token from its forks and variants. Bitcoin, Bitcoin Cash, and Bitcoin SV each have their own DTI, even though they share a common history. This precision is exactly what regulators and institutional systems need.

TokenDTI
Bitcoin4H95J0R2X
ArweaveJW7SD9THP
ℹ️DTI and ISIN are two different identification systems for digital tokens. An XT-ISIN (using the XT country prefix) also identifies a digital token, but its 9-character NSIN is assigned independently by the numbering authority — it is not the same as the token's DTI.

2. DTI structure — the restricted alphabet

A DTI is exactly 9 characters long: an 8-character payload followed by a single check character. Both the payload and the check character are drawn from the same restricted 30-character alphabet.

PartLengthDescriptionBitcoin example
Payload8Randomly assigned base number4H95J0R2
Check character1Computed checksum over the full codeX

The restricted alphabet

The payload is randomly generated from a 30-character alphabet — the digits 0-9 plus 20 consonants: B C D F G H J K L M N P Q R S T V W X Z. The five vowels A, E, I, O, U and the letter Y are excluded to prevent strings that could form recognisable (and potentially offensive) words.

Two additional structural rules apply:

  • The first character must not be 0
  • All 9 characters — including the check character — must be from the restricted alphabet

In Python, checking whether a string uses only the allowed characters looks like this:

import re

# DTI alphabet: digits 0-9 plus consonants (no vowels, no Y)
DTI_RE = re.compile(r"^[0-9BCDFGHJKLMNPQRSTVWXZ]{9}$")


def has_dti_format(raw: str) -> bool:
    dti = raw.strip().upper()
    return len(dti) == 9 and DTI_RE.match(dti) is not None and dti[0] != "0"


print(has_dti_format("4H95J0R2X"))  # True (format only — not validated)
print(has_dti_format("4H95J0R2A"))  # False — 'A' is a vowel, not allowed
print(has_dti_format("0H95J0R2X"))  # False — starts with '0'
⚠️A string that passes this format check is not necessarily a valid DTI. It must also exist in the DTIF registry to be meaningful. Use the found field from the API to confirm. See sections 3 and 5.

3. The check character

The 9th character of a DTI is a check character whose computation is defined in the ISO 24165 standard — a paid document not publicly available. The check character is drawn from the same restricted 30-character alphabet as the payload, so it is always one of 0-9 B C D F G H J K L M N P Q R S T V W X Z.

In practice, the most reliable way to confirm a DTI is valid is to look it up in the DTIF registry — a DTI that appears there has been officially assigned and is by definition structurally correct. The IsValid API returns the found field for exactly this purpose.

ℹ️The ISO 24165 check character algorithm is not publicly documented. The IsValid API validates the format (alphabet, length, first character) and performs a DTIF registry lookup to confirm the DTI has been officially assigned. The found field is the authoritative signal for regulatory reporting.

4. Why DTIs matter — regulation and reporting

DTIs are not just an internal bookkeeping convention. They are becoming mandatory in regulatory reporting across multiple jurisdictions:

EMIR (EU derivatives reporting)

The European Securities and Markets Authority (ESMA) requires crypto-asset derivatives to be identified using DTIs when reporting under the European Market Infrastructure Regulation. Financial counterparties trading Bitcoin futures, Ethereum options, or stablecoin swaps need a valid DTI for the underlying asset.

MiCA (Markets in Crypto-Assets)

The EU's MiCA regulation identifies crypto-assets in part through DTIs. Crypto-asset service providers (CASPs) operating in the EU need correct DTIs for the assets they list and trade.

UPI (Unique Product Identifier)

The DTIF has coordinated with ANNA and the derivatives UPI system so that DTIs can serve as underlying asset identifiers in swap data reports under UPI taxonomy — used globally for OTC derivative reporting.

Beyond regulation, DTIs are used by exchanges, data vendors, and risk systems to unambiguously link positions and trades to specific tokens — even when the same token trades under different tickers on different venues.


5. Why format checks alone are not enough

Format check alone is not enough

A 9-character string that uses only the allowed 30 characters and does not start with 0 satisfies the format rules — but there is no publicly available checksum algorithm to verify the 9th character. The ISO 24165 standard is a paid document. In practice, format validation alone allows a large number of strings that have never been registered with DTIF.

Even a valid DTI might not exist

A string can pass the format check and still not correspond to any registered token. The DTIF registry is the authoritative source — only DTIs that appear there have been officially assigned. Accepting an unregistered DTI is accepting a code that refers to nothing.

Implementing the check character is non-trivial

The ISO 24165 check character algorithm is not publicly documented in detail outside the paid standard. Rolling your own implementation requires access to the specification, careful testing against known values, and ongoing maintenance if the standard is revised. Most teams do not need to own this complexity.


6. The right solution: one API call

The IsValid DTI API handles the full validation stack in a single GET request:

1

Format check

9 characters, restricted alphabet, first character ≠ 0

2

Registry lookup

Checks the DTIF registry — returns name, short name, and type when found

Get your free API key at isvalid.dev. The free tier includes 100 calls per day with no credit card required.

Full parameter reference and response schema: DTI Validation API docs →


7. Python code example

Using the requests library — the de facto standard for HTTP in Python. Install it with pip install requests.

# dti_validator.py
import os
import requests

API_KEY = os.environ["ISVALID_API_KEY"]
BASE_URL = "https://api.isvalid.dev"


def validate_dti(dti: str) -> dict:
    """Validate a DTI using the IsValid API.

    Args:
        dti: 9-character Digital Token Identifier.

    Returns:
        Validation result as a dictionary.

    Raises:
        requests.HTTPError: If the API returns a non-2xx status.
    """
    response = requests.get(
        f"{BASE_URL}/v0/dti",
        params={"value": dti},
        headers={"Authorization": f"Bearer {API_KEY}"},
    )
    response.raise_for_status()
    return response.json()


# ── Example usage ────────────────────────────────────────────────────────────

result = validate_dti("4H95J0R2X")  # Bitcoin DTI

if not result["valid"]:
    print("Invalid DTI: failed format validation")
else:
    print("DTI is valid")
    print(f"Payload   : {result['payload']}")     # "4H95J0R2"
    print(f"Check char: {result['checkChar']}")    # "X"
    print(f"Found     : {result['found']}")        # True / False / None
    if result["found"]:
        print(f"Name      : {result['name']}")           # "Bitcoin"
        print(f"Short name: {result['shortName']}")      # "BTC"
        print(f"Type      : {result['identifierType']}") # "token"
        print(f"DTI type  : {result['dtiType']}")        # "protocol"

With error handling for production use:

def validate_dti_safe(dti: str) -> dict | None:
    """Validate a DTI, returning None on failure instead of raising."""
    try:
        return validate_dti(dti)
    except requests.Timeout:
        print(f"DTI validation timed out for {dti}")
        return None
    except requests.RequestException as exc:
        print(f"DTI validation failed: {exc}")
        return None


# Validate a batch of DTIs
dtis = ["4H95J0R2X", "JW7SD9THP", "INVALID00"]

for dti in dtis:
    result = validate_dti_safe(dti)
    if result is None:
        print(f"{dti} → validation unavailable")
    elif not result["valid"]:
        print(f"{dti} → INVALID")
    else:
        print(f"{dti} → valid, payload: {result['payload']}")

In a Flask endpoint, you might use it like this:

# app.py (Flask)
from flask import Flask, request, jsonify

app = Flask(__name__)


@app.post("/check-dti")
def check_dti():
    data = request.get_json()

    try:
        result = validate_dti(data["dti"])
    except requests.RequestException:
        return jsonify(error="DTI validation service unavailable"), 502

    if not result["valid"]:
        return jsonify(error="Invalid DTI format"), 400

    return jsonify(
        valid=True,
        found=result["found"],
        name=result.get("name"),
        shortName=result.get("shortName"),
        identifierType=result.get("identifierType"),
    )
The API strips whitespace and converts to uppercase automatically. Pass raw user input directly — you do not need to pre-process it.

8. cURL examples

Validate Bitcoin's DTI:

curl -H "Authorization: Bearer YOUR_API_KEY" \
  "https://api.isvalid.dev/v0/dti?value=4H95J0R2X"

Validate Arweave's DTI:

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

Test an invalid DTI format (contains a vowel):

curl -H "Authorization: Bearer YOUR_API_KEY" \
  "https://api.isvalid.dev/v0/dti?value=4H95J0R2A"

9. Understanding the response

Valid DTI — Bitcoin (found in registry)

{
  "valid": true,
  "normalized": "4H95J0R2X",
  "payload": "4H95J0R2",
  "checkChar": "X",
  "found": true,
  "identifierType": "token",
  "name": "Bitcoin",
  "shortName": "BTC",
  "dtiType": "protocol"
}

Invalid DTI — forbidden character

{
  "valid": false
}
FieldTypeDescription
validbooleanWhether the string passed format validation
normalizedstringUppercased, whitespace-stripped DTI (only present when valid: true)
payloadstringFirst 8 characters — the randomly assigned base number (only present when valid: true)
checkCharstringThe 9th character — the check character from the restricted alphabet (only present when valid: true)
foundboolean | nullWhether the DTI exists in the DTIF registry. null if the registry is unavailable (only present when valid: true)
identifierTypestring | nullIdentifier type: "token" (digital token) or "ledger" (distributed ledger/blockchain). Only present when found: true
namestring | nullFull token name from the DTIF registry (only present when found: true)
shortNamestring | nullShort name / ticker symbol (only present when found: true)
dtiTypestring | nullDTIF type classification. For tokens: "protocol" or "auxiliary". For ledgers: "blockchain" or "other". Only present when found: true

10. Edge cases to handle

Lowercase input

The API normalises input to uppercase automatically, so lowercase DTIs are handled correctly. Your own pre-validation code should do the same before any client-side display or storage.

# Both of these work — the API normalises automatically
validate_dti("4h95j0r2x")  # valid → Bitcoin
validate_dti("4H95J0R2X")  # valid → Bitcoin

Strings containing vowels or Y

Vowels (A, E, I, O, U) and Y are not part of the DTI alphabet. Strings containing them are rejected immediately without checking the rest of the structure.

# 'A' is a vowel — invalid
validate_dti("4H95J0R2A")  # {"valid": False}

# 'Y' is excluded — invalid
validate_dti("4H95J0R2Y")  # {"valid": False}

Structurally valid but unregistered DTIs

A 9-character string can pass format checks without corresponding to any token in the DTIF registry. The IsValid API both validates the structure and checks the DTIF registry — the found field tells you whether the DTI has been officially assigned. Always check found in regulated reporting contexts.

result = validate_dti("4H95J0R2X")

if result["valid"] and result["found"] is False:
    # Structurally correct DTI but not in the DTIF registry
    # Reject for regulatory reporting
    pass

if result["valid"] and result["found"] is None:
    # Registry unavailable — decide whether to accept or retry
    pass

DTI vs XT-ISIN — do not confuse them

Both DTI and XT-ISIN identify the same underlying digital token, but they are different codes with different structures. Bitcoin's DTI is 4H95J0R2X while its XT-ISIN is XTV15WLZJMF0. They are not interchangeable and are maintained by different numbering authorities.

# DTI — 9 chars, use /v0/dti
validate_dti("4H95J0R2X")       # Bitcoin DTI

# XT-ISIN — 12 chars, use /v0/isin
validate_isin("XTV15WLZJMF0")  # Bitcoin XT-ISIN

Summary

Do not rely on a regex alone — also check the found field against the DTIF registry
Do not assume a structurally valid DTI has been registered with DTIF
Do not confuse DTI with XT-ISIN — they are different codes for the same token
Normalise to uppercase before validation or display
Validate the format — 9 characters, restricted alphabet, first character ≠ 0
Check the found field — for regulatory reporting, only accept DTIs registered with DTIF

See also

Try DTI validation instantly

Free tier includes 100 API calls per day. No credit card required. Format check and DTIF registry lookup included for every DTI.