How to Look Up NDC Drug Codes Programmatically with Node.js
Every drug product sold in the United States has a National Drug Code. The NDC is the FDA's universal identifier for human drugs -- it appears on every prescription bottle, pharmacy claim, insurance EOB, and formulary file. If you are building a pharmacy system, an EHR, a claims adjudication engine, or a drug interaction checker, you need programmatic access to the NDC directory.
The FDA publishes the full NDC directory as a downloadable zip file. Parsing it, normalizing the formats, and indexing it for fast search is a chore you should not have to repeat. This article covers the NDC format, how to search the directory programmatically, and how to integrate NDC lookups into a Node.js application using both a REST API and an MCP server.
What NDC codes are
A National Drug Code is a 10-digit or 11-digit numeric identifier assigned by the FDA to every drug product listed under the Federal Food, Drug, and Cosmetic Act. The code uniquely identifies three things:
- Labeler -- the manufacturer, repackager, or distributor
- Product -- the specific drug formulation (strength, dosage form)
- Package -- the package size and type
These three segments are separated by dashes, like 0002-1433-80. The labeler is Eli Lilly (0002), the product is a specific formulation (1433), and the package is an 80-count bottle.
The NDC format problem
The NDC format is one of the most frustrating quirks in healthcare data. The FDA defines NDCs as 10 digits, but the three segments can be split in different configurations:
| Format | Example | Segments |
|---|---|---|
| 4-4-2 | 0002-1433-80 |
4-digit labeler, 4-digit product, 2-digit package |
| 5-3-2 | 12345-678-90 |
5-digit labeler, 3-digit product, 2-digit package |
| 5-4-1 | 12345-6789-0 |
5-digit labeler, 4-digit product, 1-digit package |
To make things worse, HIPAA transactions require an 11-digit NDC, which means zero-padding one of the segments. A 4-4-2 NDC like 0002-1433-80 becomes 00002-1433-80 in an 11-digit format. Pharmacies, PBMs, and clearinghouses all handle this differently. Some strip dashes. Some zero-pad the wrong segment.
The NDC API normalizes all of this for you. Pass in any format -- with dashes, without dashes, 10-digit, 11-digit -- and it finds the right product.
Looking up a drug by NDC code
When you have an NDC from a claim or prescription and need the drug details:
curl "https://ndc-api-production.up.railway.app/lookup?ndc=0002-1433-80"
{
"ndc": "0002-1433-80",
"name": "Prozac",
"genericName": "Fluoxetine Hydrochloride",
"ingredients": "FLUOXETINE HYDROCHLORIDE",
"manufacturer": "Eli Lilly and Company",
"dosageForm": "CAPSULE",
"route": "ORAL",
"strength": "20mg",
"deaSchedule": null,
"marketingCategory": "NDA",
"productType": "HUMAN PRESCRIPTION DRUG"
}
The response includes everything you need for claims processing: brand name, generic name, active ingredients, manufacturer, dosage form, route of administration, strength, and DEA schedule (if it is a controlled substance).
Searching by drug name
Most of the time, you do not have the NDC -- you have a drug name from a prescription or a patient record. Search by name:
curl "https://ndc-api-production.up.railway.app/search?q=metformin&limit=5"
{
"query": "metformin",
"count": 5,
"results": [
{
"ndc": "0002-1433-80",
"name": "Metformin Hydrochloride",
"genericName": "Metformin Hydrochloride",
"manufacturer": "Aurobindo Pharma Limited",
"dosageForm": "TABLET",
"strength": "500mg",
"route": "ORAL"
}
]
}
The search is scored by relevance. Exact name matches and generic name matches rank higher than hits on manufacturer or ingredient fields.
Searching by active ingredient
For drug interaction checking, you need to find all products containing a specific compound, regardless of brand name or manufacturer:
curl "https://ndc-api-production.up.railway.app/search?ingredient=acetaminophen&limit=5"
This returns every product in the FDA directory that lists acetaminophen as an active ingredient -- Tylenol, generic acetaminophen, combination products like Percocet, cold medicines, and more. This is essential for interaction screening and duplicate therapy detection.
Searching by manufacturer
For formulary management and supply chain work, search by manufacturer:
curl "https://ndc-api-production.up.railway.app/search?manufacturer=pfizer&limit=10"
Building an NDC lookup into a Node.js application
Here is a practical module for a pharmacy claims system that validates NDC codes and enriches claim records with drug details:
const NDC_BASE = 'https://ndc-api-production.up.railway.app';
const lookupNdc = async (ndc) => {
const res = await fetch(`${NDC_BASE}/lookup?ndc=${encodeURIComponent(ndc)}`);
if (!res.ok) {
const err = await res.json();
return { valid: false, error: err.error, ndc };
}
const drug = await res.json();
return { valid: true, ...drug };
};
const searchDrugs = async (query, { limit = 25 } = {}) => {
const params = new URLSearchParams({ q: query, limit: String(limit) });
const res = await fetch(`${NDC_BASE}/search?${params}`);
const data = await res.json();
return data.results;
};
const searchByIngredient = async (ingredient, { limit = 25 } = {}) => {
const params = new URLSearchParams({ ingredient, limit: String(limit) });
const res = await fetch(`${NDC_BASE}/search?${params}`);
const data = await res.json();
return data.results;
};
Validating NDCs on incoming claims
When pharmacy claims arrive, validate that every NDC maps to a real product before adjudication:
const validateClaimNdcs = async (claims) => {
const ndcs = claims.map((c) => c.ndc);
const res = await fetch(`${NDC_BASE}/lookup/batch`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ ndcs }),
});
const { results } = await res.json();
return claims.map((claim, i) => {
const drug = results[i];
const hasError = Boolean(drug.error);
return {
claimId: claim.id,
ndc: claim.ndc,
valid: !hasError,
drugName: hasError ? null : drug.name,
genericName: hasError ? null : drug.genericName,
error: hasError ? drug.error : null,
};
});
};
// Validate a batch of incoming claims
const claims = [
{ id: 'CLM-001', ndc: '0002-1433-80' },
{ id: 'CLM-002', ndc: '9999-9999-99' },
{ id: 'CLM-003', ndc: '0069-3150-83' },
];
const validated = await validateClaimNdcs(claims);
// [
// { claimId: 'CLM-001', valid: true, drugName: 'Prozac', ... },
// { claimId: 'CLM-002', valid: false, error: 'Not found', ... },
// { claimId: 'CLM-003', valid: true, drugName: 'Zoloft', ... },
// ]
Building a drug autocomplete for a prescriber UI
let timeout;
const onDrugSearch = (inputValue, renderResults) => {
clearTimeout(timeout);
timeout = setTimeout(async () => {
if (inputValue.length < 2) {
renderResults([]);
return;
}
const drugs = await searchDrugs(inputValue, { limit: 10 });
const options = drugs.map((d) => ({
ndc: d.ndc,
label: `${d.name} ${d.strength} (${d.dosageForm})`,
generic: d.genericName,
manufacturer: d.manufacturer,
}));
renderResults(options);
}, 300);
};
Checking for duplicate ingredients
Before filling a new prescription, check whether the patient is already taking a drug with the same active ingredient:
const checkDuplicateTherapy = async (newNdc, currentMedications) => {
const newDrug = await lookupNdc(newNdc);
if (!newDrug.valid) {
return { error: `Unknown NDC: ${newNdc}` };
}
const newIngredients = newDrug.ingredients.toLowerCase().split(';').map((s) => s.trim());
const duplicates = currentMedications.filter((med) => {
const medIngredients = med.ingredients.toLowerCase().split(';').map((s) => s.trim());
return newIngredients.some((ni) => medIngredients.includes(ni));
});
return {
newDrug: newDrug.name,
ingredients: newIngredients,
duplicates: duplicates.map((d) => ({
name: d.name,
ndc: d.ndc,
sharedIngredient: newIngredients.find((ni) =>
d.ingredients.toLowerCase().includes(ni)
),
})),
hasDuplicate: duplicates.length > 0,
};
};
Using the MCP server for NDC lookups
The @easysolutions906/mcp-healthcare MCP server includes three NDC tools alongside ICD-10, NPI, and DEA tools -- 10 tools total. The NDC tools are:
-
ndc_lookup-- Look up a drug by its NDC code -
ndc_search-- Search drugs by name, generic name, or manufacturer -
ndc_search_ingredient-- Search drugs by active ingredient
Add the server to your Claude Desktop configuration:
{
"mcpServers": {
"healthcare": {
"command": "npx",
"args": ["-y", "@easysolutions906/mcp-healthcare"]
}
}
}
Then ask Claude questions like:
- "What drug has NDC 0002-1433-80?"
- "Find all NDC codes for metformin 500mg tablets"
- "What products contain acetaminophen and hydrocodone?"
- "Look up the manufacturer for lisinopril NDC 0069-3150-83"
The MCP server searches the same full FDA dataset. This is useful during development when you need to verify test NDCs, explore the data model, or check whether a specific formulation is in the directory -- without leaving your editor.
Common use cases
Claims adjudication
Every pharmacy claim includes an NDC. Before paying the claim, validate that the NDC exists, check the drug's generic equivalent for MAC pricing, and verify it is on the plan's formulary. The batch lookup endpoint handles up to 50 NDCs per request.
Drug interaction checking
Search by ingredient to find all formulations containing a compound. Cross-reference a patient's active medications to flag duplicate therapy, contraindicated combinations, or therapeutic overlaps. The ingredient search is the foundation of this workflow.
Formulary management
PBMs and health plans maintain drug formularies -- lists of covered medications organized by tier. The manufacturer search helps identify all products from a specific labeler when negotiating rebate contracts or updating formulary tiers after a generic launch.
Inventory and supply chain
Hospital pharmacies and mail-order pharmacies track inventory by NDC. The lookup endpoint maps an NDC to its full product details for label printing, barcode scanning, and automated dispensing cabinet integration.
Controlled substance tracking
The /schedule/:schedule endpoint lists drugs by DEA schedule (CII through CV). This supports prescription drug monitoring program (PDMP) integrations and controlled substance inventory audits:
curl "https://ndc-api-production.up.railway.app/schedule/CII?limit=10"
FDA data source and update frequency
The NDC directory is sourced directly from the FDA's NDC text file download at https://www.accessdata.fda.gov/cder/ndctext.zip. The FDA updates this file weekly as new drug products are approved, reformulated, or discontinued.
The API embeds the full dataset locally for fast searches -- no upstream API dependency, no rate limits from the FDA. Hit the /data-info endpoint to check the current data build date and record count:
curl "https://ndc-api-production.up.railway.app/data-info"
{
"source": "FDA NDC Directory",
"url": "https://www.accessdata.fda.gov/cder/ndctext.zip",
"recordCount": 137000,
"builtAt": "2026-03-15T00:00:00.000Z",
"updateFrequency": "weekly"
}
Getting started
-
Look up by NDC:
GET /lookup?ndc=0002-1433-80 -
Search by name:
GET /search?q=metformin&limit=10 -
Search by ingredient:
GET /search?ingredient=acetaminophen&limit=10 -
Search by manufacturer:
GET /search?manufacturer=pfizer&limit=10 -
Controlled substances:
GET /schedule/CII?limit=25 -
Batch lookup:
POST /lookup/batchwith{"ndcs": [...]} -
MCP server:
npx @easysolutions906/mcp-healthcare
No API key required for the REST endpoints. The dataset is public FDA data, and the API is free to use.
Top comments (0)