DTI Validation in Node.js
Digital Token Identifiers are the ISO standard for naming crypto assets globally. Here's how to validate them correctly — and why a regex is nowhere near enough.
In this guide
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.
| Token | DTI |
|---|---|
| Bitcoin | 4H95J0R2X |
| Arweave | JW7SD9THP |
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.
| Part | Length | Description | Bitcoin example |
|---|---|---|---|
| Payload | 8 | Randomly assigned base number | 4H95J0R2 |
| Check character | 1 | Computed checksum over the full code | X |
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 Node.js, checking whether a string uses only the allowed characters looks like this:
// DTI alphabet: digits 0-9 plus consonants (no vowels, no Y) const DTI_RE = /^[0-9BCDFGHJKLMNPQRSTVWXZ]{9}$/; function hasDtiFormat(raw) { const dti = raw.trim().toUpperCase(); return dti.length === 9 && DTI_RE.test(dti) && dti[0] !== '0'; } console.log(hasDtiFormat('4H95J0R2X')); // true (format only — not validated) console.log(hasDtiFormat('4H95J0R2A')); // false — 'A' is a vowel, not allowed console.log(hasDtiFormat('0H95J0R2X')); // false — starts with '0'
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.
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:
Format check
9 characters, restricted alphabet, first character ≠ 0
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. Node.js code example
Using the @isvalid-dev/sdk package or the built-in fetch API (Node.js 18+):
// dti-validator.mjs import { createClient } from '@isvalid-dev/sdk'; const iv = createClient({ apiKey: process.env.ISVALID_API_KEY }); // ── Example usage ──────────────────────────────────────────────────────────── const result = await iv.dti('4H95J0R2X'); // Bitcoin DTI if (!result.valid) { console.log('Invalid DTI: failed format validation'); } else { console.log('DTI is valid'); console.log('Payload :', result.payload); // "4H95J0R2" console.log('Check char:', result.checkChar); // "X" console.log('Found :', result.found); // true / false / null if (result.found) { console.log('Name :', result.name); // "Bitcoin" console.log('Short name:', result.shortName); // "BTC" console.log('Type :', result.identifierType); // "token" console.log('DTI type :', result.dtiType); // "protocol" } }
With error handling for production use:
async function validateDtiSafe(dti) { try { return await validateDti(dti); } catch (err) { if (err.name === 'AbortError') { console.warn('DTI validation timed out for', dti); } else { console.error('DTI validation failed:', err.message); } return null; } } // Validate a batch of DTIs const dtis = ['4H95J0R2X', 'JW7SD9THP', 'INVALID00']; const results = await Promise.all(dtis.map(validateDtiSafe)); results.forEach((result, i) => { const dti = dtis[i]; if (!result) { console.log(dti, '→ validation unavailable'); } else if (!result.valid) { console.log(dti, '→ INVALID'); } else { console.log(dti, '→ valid, payload:', result.payload); } });
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 }
| Field | Type | Description |
|---|---|---|
| valid | boolean | Whether the string passed format validation |
| normalized | string | Uppercased, whitespace-stripped DTI (only present when valid: true) |
| payload | string | First 8 characters — the randomly assigned base number (only present when valid: true) |
| checkChar | string | The 9th character — the check character from the restricted alphabet (only present when valid: true) |
| found | boolean | null | Whether the DTI exists in the DTIF registry. null if the registry is unavailable (only present when valid: true) |
| identifierType | string | null | Identifier type: "token" (digital token) or "ledger" (distributed ledger/blockchain). Only present when found: true |
| name | string | null | Full token name from the DTIF registry (only present when found: true) |
| shortName | string | null | Short name / ticker symbol (only present when found: true) |
| dtiType | string | null | DTIF 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 await validateDti('4h95j0r2x'); // valid → Bitcoin await validateDti('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 await validateDti('4H95J0R2A'); // { valid: false } // 'Y' is excluded — invalid await validateDti('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.
const result = await validateDti('4H95J0R2X'); if (result.valid && result.found === false) { // Structurally correct DTI but not in the DTIF registry // Reject for regulatory reporting } if (result.valid && result.found === null) { // Registry unavailable — decide whether to accept or retry }
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 await validateDti('4H95J0R2X'); // Bitcoin DTI // XT-ISIN — 12 chars, use /v0/isin await validateIsin('XTV15WLZJMF0'); // Bitcoin XT-ISIN
Summary
Node.js integration notes
When handling DTI Code in a TypeScript codebase, define a branded type to prevent accidental mixing of financial identifier strings at compile time:type DtiCode = string & { readonly __brand: 'DtiCode' }. The IsValid SDK ships with full TypeScript definitions covering all response fields, including country-specific and instrument-specific data, so your editor provides autocomplete on the parsed result without manual type declarations.
In financial data pipelines — payment processors, reconciliation engines, or KYC workflows — DTI Code validation sits at the ingestion boundary. Pair the IsValid SDK with decimal.js orbig.js for any monetary amounts tied to the identifier, and usepino for structured logging that attaches the validation result to the transaction reference in every log line, making audit trails straightforward.
Express.js and Fastify middleware
Centralise DTI Code validation in a request middleware rather than repeating it in every route handler. The middleware calls the IsValid API, attaches the parsed result toreq.validated, and callsnext() on success. Layer in a Redis cache keyed by the normalised identifier with a 24-hour TTL to avoid redundant API calls for the same value across multiple requests in the same session.
Error handling should distinguish between a 422 response from IsValid (the DTI Code is structurally invalid — return this to the caller immediately) and 5xx or network errors (transient failures — retry once after a short delay before surfacing a service-unavailable error). Never swallow validation failures silently; they indicate bad data that could propagate into financial records downstream.
- Assert
process.env.ISVALID_API_KEYis present at server startup, not lazily at first request - Use
Promise.allSettled()for batch validation — it collects all results without aborting on the first failure - Mock the IsValid client with
jest.mock()in unit tests; keep CI pipelines free of real API calls - Store the full parsed API response alongside the raw DTI Code in your database — country code, institution data, and check-digit status are useful for downstream logic
When making HTTP calls to the IsValid API directly (without the SDK), the choice between fetch and axios is largely a matter of preference. The native fetch API is available in Node.js 18+ without any additional dependency and is sufficient for simple request/response flows. axios adds automatic JSON parsing, request/response interceptors, and a cleaner timeout API (axios.create({ timeout: 5000 })), which makes it easier to centralise the Authorization header and retry logic in one place. For high-throughput services that make many concurrent API calls, consider undici — the HTTP client underlying Node.js fetch — used directly for its connection pooling and lower overhead.
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.