DEV Community

easysolutions906
easysolutions906

Posted on

How to Look Up NDC Drug Codes Programmatically with Node.js

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:

  1. Labeler -- the manufacturer, repackager, or distributor
  2. Product -- the specific drug formulation (strength, dosage form)
  3. 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"
Enter fullscreen mode Exit fullscreen mode
{
  "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"
}
Enter fullscreen mode Exit fullscreen mode

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"
Enter fullscreen mode Exit fullscreen mode
{
  "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"
    }
  ]
}
Enter fullscreen mode Exit fullscreen mode

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"
Enter fullscreen mode Exit fullscreen mode

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"
Enter fullscreen mode Exit fullscreen mode

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;
};
Enter fullscreen mode Exit fullscreen mode

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', ... },
// ]
Enter fullscreen mode Exit fullscreen mode

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);
};
Enter fullscreen mode Exit fullscreen mode

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,
  };
};
Enter fullscreen mode Exit fullscreen mode

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"]
    }
  }
}
Enter fullscreen mode Exit fullscreen mode

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"
Enter fullscreen mode Exit fullscreen mode

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"
Enter fullscreen mode Exit fullscreen mode
{
  "source": "FDA NDC Directory",
  "url": "https://www.accessdata.fda.gov/cder/ndctext.zip",
  "recordCount": 137000,
  "builtAt": "2026-03-15T00:00:00.000Z",
  "updateFrequency": "weekly"
}
Enter fullscreen mode Exit fullscreen mode

Getting started

  1. Look up by NDC: GET /lookup?ndc=0002-1433-80
  2. Search by name: GET /search?q=metformin&limit=10
  3. Search by ingredient: GET /search?ingredient=acetaminophen&limit=10
  4. Search by manufacturer: GET /search?manufacturer=pfizer&limit=10
  5. Controlled substances: GET /schedule/CII?limit=25
  6. Batch lookup: POST /lookup/batch with {"ndcs": [...]}
  7. 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)