How to Validate DEA Numbers Programmatically: The Check Digit Algorithm Explained
Every prescriber of controlled substances in the United States must hold a Drug Enforcement Administration (DEA) registration number. This number appears on every controlled substance prescription, in every pharmacy dispensing system, in credentialing databases, and in electronic prescribing of controlled substances (EPCS) workflows. If you are building software that touches prescribing, pharmacy operations, or provider credentialing, you need to validate DEA numbers.
The good news: DEA numbers contain a built-in check digit that lets you verify format correctness without calling any external service. This article breaks down the DEA number format, walks through the check digit algorithm step by step, and shows how to implement validation in JavaScript.
What a DEA number looks like
A DEA number is exactly 9 characters: 2 letters followed by 7 digits.
AB1234563
^^-------
|| | |
|| | └── check digit (1 digit)
|| └────── registration number (6 digits)
|└───────── last name initial of the registrant
└────────── registrant type code
The first letter: registrant type
The first letter identifies the type of DEA registrant:
| Letter | Registrant Type |
|---|---|
| A, B | Dispenser (hospitals, pharmacies, practitioners) |
| C, D, E | Manufacturer / Schedule I researcher |
| F | Manufacturer |
| G, H, J, K | Distributor |
| L | Reverse distributor |
| M | Mid-level practitioner (NP, PA, optometrist) |
| P, R, S, T | Narcotic treatment program |
| X | Suboxone/Subutex prescriber (DATA 2000 waiver) |
Most DEA numbers you encounter in clinical software start with A, B, or M. If you see an F or G prefix, that is a manufacturer or distributor, not a prescriber.
The second letter: last name initial
The second character is the first letter of the registrant's last name. For a physician named Dr. Smith, the DEA number starts with AS, BS, or MS. This gives you a quick sanity check: if a provider's last name starts with "J" but their DEA number's second letter is "R", something is wrong.
The last 7 digits: registration number + check digit
The first 6 digits are the registration number assigned by the DEA. The 7th digit is a check digit computed from the preceding 6 digits using a Luhn-variant algorithm.
The check digit algorithm
The DEA check digit algorithm is a weighted sum modulo 10. Here is how it works, step by step.
Given a DEA number AB1234563:
Step 1: Extract the 7 digits: 1 2 3 4 5 6 3
Step 2: Sum the digits in odd positions (1st, 3rd, 5th):
1 + 3 + 5 = 9
Step 3: Sum the digits in even positions (2nd, 4th, 6th):
2 + 4 + 6 = 12
Step 4: Multiply the even sum by 2:
12 * 2 = 24
Step 5: Add the odd sum and the doubled even sum:
9 + 24 = 33
Step 6: The check digit is the last digit of the total:
33 % 10 = 3
Step 7: Compare. The 7th digit of the DEA number is 3, and the computed check digit is 3. They match, so the number passes the check digit validation.
If they do not match, the DEA number is structurally invalid. It was either mistyped, fabricated, or corrupted during data entry.
Implementing validation from scratch
Here is a complete DEA validator in JavaScript. It checks format, extracts the registrant type, validates the check digit, and optionally verifies the last name initial.
const REGISTRANT_TYPES = {
A: 'Dispenser (Hospitals, Pharmacies, Practitioners)',
B: 'Dispenser (Hospitals, Pharmacies, Practitioners)',
C: 'Manufacturer (Schedule I Researcher)',
D: 'Manufacturer (Schedule I Researcher)',
E: 'Manufacturer (Schedule I Researcher)',
F: 'Manufacturer',
G: 'Distributor',
H: 'Distributor',
J: 'Distributor',
K: 'Distributor',
L: 'Reverse Distributor',
M: 'Mid-Level Practitioner',
P: 'Narcotic Treatment Program',
R: 'Narcotic Treatment Program',
S: 'Narcotic Treatment Program',
T: 'Narcotic Treatment Program',
X: 'Suboxone/Subutex Prescriber',
};
const computeCheckDigit = (digits) => {
const oddSum = digits[0] + digits[2] + digits[4];
const evenSum = digits[1] + digits[3] + digits[5];
return (oddSum + evenSum * 2) % 10;
};
const validateDea = (raw, lastName = null) => {
const dea = (raw || '').trim().toUpperCase();
const errors = [];
if (dea.length !== 9) {
errors.push(`DEA number must be exactly 9 characters, got ${dea.length}`);
}
const letters = dea.slice(0, 2);
const digitPart = dea.slice(2);
if (!/^[A-Z]{2}$/.test(letters)) {
errors.push('First two characters must be letters');
}
if (!/^\d{7}$/.test(digitPart)) {
errors.push('Last seven characters must be digits');
}
// If format is wrong, return early
if (errors.length > 0) {
return { valid: false, dea, errors };
}
const typeCode = letters[0];
const nameInitial = letters[1];
const registrantType = REGISTRANT_TYPES[typeCode] || 'Unknown';
// Check digit validation
const digits = digitPart.split('').map(Number);
const expected = computeCheckDigit(digits.slice(0, 6));
const actual = digits[6];
if (expected !== actual) {
errors.push(`Check digit mismatch: expected ${expected}, got ${actual}`);
}
// Optional last name cross-check
if (lastName) {
const lastNameInitial = lastName.trim()[0]?.toUpperCase();
if (lastNameInitial && lastNameInitial !== nameInitial) {
errors.push(
`Name initial mismatch: DEA has '${nameInitial}', last name starts with '${lastNameInitial}'`
);
}
}
return {
valid: errors.length === 0,
dea,
registrantType,
nameInitial,
checkDigit: actual,
errors,
};
};
// Valid DEA number
console.log(validateDea('AB1234563'));
// { valid: true, dea: 'AB1234563', registrantType: 'Dispenser ...', ... }
// Invalid check digit
console.log(validateDea('AB1234567'));
// { valid: false, ..., errors: ['Check digit mismatch: expected 3, got 7'] }
// Cross-check with provider last name
console.log(validateDea('AB1234563', 'Baker'));
// { valid: true, ... } — second letter 'B' matches last name 'Baker'
console.log(validateDea('AB1234563', 'Smith'));
// { valid: false, ..., errors: ["Name initial mismatch: DEA has 'B', last name starts with 'S'"] }
The computeCheckDigit function is the core. It is three lines. Everything else is format validation and error reporting.
Using the MCP Healthcare server
If you do not want to maintain your own validation code, the @easysolutions906/mcp-healthcare npm package includes DEA validation as one of its 10 tools, alongside NPI lookup, ICD-10 search, and NDC drug lookup.
In Claude Desktop or Cursor
Add the server to your claude_desktop_config.json:
{
"mcpServers": {
"healthcare": {
"command": "npx",
"args": ["-y", "@easysolutions906/mcp-healthcare"]
}
}
}
Then ask Claude: "Validate DEA number AB1234563" and it calls the dea_validate tool, returning the validity, registrant type, check digit, and any errors.
You can also ask "Generate a test DEA number for a provider named Smith" and it calls the dea_generate_test tool to produce a structurally valid DEA number for development use.
Programmatic usage
You can also use the MCP server as a library in Node.js. The validation tool accepts a DEA number and returns a structured result:
import { validate, generateTest } from '@easysolutions906/mcp-healthcare/src/tools/dea.js';
// Validate a real DEA number
const result = validate('AB1234563');
console.log(result);
// {
// valid: true,
// dea: 'AB1234563',
// checkDigit: 3,
// registrantType: 'Dispenser (Hospitals, Pharmacies, Practitioners)',
// errors: []
// }
// Generate a test DEA number for development
const testDea = generateTest('Johnson');
console.log(testDea);
// {
// dea: 'AJ7382910',
// disclaimer: 'This is a randomly generated test DEA number ...',
// registrantType: 'Dispenser (Hospitals, Pharmacies, Practitioners)'
// }
The generateTest function is useful when you need structurally valid DEA numbers for unit tests, seed data, or staging environments without using real registrations.
When you need DEA validation
E-prescribing (EPCS)
The DEA requires that electronic prescribing systems for controlled substances validate the prescriber's DEA number before transmitting a prescription. EPCS certification under 21 CFR Part 1311 mandates identity proofing and two-factor authentication, but the first layer of defense is structural validation: reject obviously malformed DEA numbers before they enter the workflow.
Pharmacy dispensing
When a pharmacy receives a controlled substance prescription, the dispensing system checks the prescriber's DEA number. A failed check digit means the prescription cannot be filled until the number is verified. Catching this at data entry saves a phone call to the prescriber's office.
Provider credentialing
Credentialing systems verify that a provider's DEA registration is active and matches their identity. The second-letter check against the provider's last name is a quick cross-reference that catches data entry errors and mismatched records. It is not definitive (multiple providers share initials), but it filters out obvious mismatches.
Claims and billing
Controlled substance claims require the prescriber's DEA number. Payers reject claims with invalid DEA numbers. Validating the format before submission avoids denials and resubmission delays.
Clinical data integration
If you are ingesting provider data from multiple sources (EHR, credentialing vendor, state PDMP), DEA number validation helps you identify dirty records. A DEA number that fails the check digit test is a data quality signal: either the source record is wrong, or the data was corrupted during integration.
Limitations of check digit validation
The check digit algorithm tells you whether a DEA number is structurally valid. It does not tell you whether it is:
- Currently active. DEA registrations expire and can be revoked. Check digit validation cannot detect an expired or surrendered registration.
- Assigned to the claimed provider. A structurally valid DEA number could belong to a different provider. Cross-checking the second letter against the last name helps, but is not conclusive.
- Authorized for the relevant schedules. A DEA registration may be limited to specific controlled substance schedules. The number format does not encode this information.
For production systems that need to verify active status, the DEA's NTIS database or state Prescription Drug Monitoring Programs (PDMPs) provide authoritative lookups. But check digit validation is the first gate: fast, offline, and catches the majority of data entry errors.
Get started
-
Roll your own: The algorithm is 3 lines of code. Copy the
computeCheckDigitfunction above and build validation into your existing stack. -
MCP server:
npx @easysolutions906/mcp-healthcareadds DEA validation (plus ICD-10, NPI, and NDC tools) to Claude Desktop or Cursor. -
npm package:
npm install @easysolutions906/mcp-healthcare
DEA validation is one of those rare problems where the algorithm is simple enough to implement from scratch in minutes, but important enough that getting it wrong causes real downstream failures. Whether you embed the check digit logic directly or use a tool that handles it for you, validate early and validate always.
Top comments (0)