DEV Community

Perufitlife
Perufitlife

Posted on • Originally published at apify.com

Build a Healthcare Lead List From the Public NPI Registry (NPPES API)

If you run a marketing agency that serves dentists, dermatologists, med spas, or any medical vertical, you've probably paid way too much for a lead list — and half the rows were stale. Here's the thing almost nobody outside the data world knows: there is a free, official, public registry of every healthcare provider in the United States, and it has a clean API.

It's called NPPES — the National Plan and Provider Enumeration System — and it backs the NPI Registry that CMS (Medicare/Medicaid) maintains. Every provider who bills insurance has an NPI (National Provider Identifier), and the registry is public record.

The free NPPES API

No key, no signup. You hit one endpoint:

https://npiregistry.cms.hhs.gov/api/?version=2.1
Enter fullscreen mode Exit fullscreen mode

It supports filtering by taxonomy (specialty), city, state, and more. Here's a Node example pulling every dentist in Austin, TX:

import got from 'got';

async function searchProviders({ taxonomy, city, state, limit = 200 }) {
  const results = [];
  // API caps each call at 200 results; page with skip
  for (let skip = 0; skip < limit; skip += 200) {
    const { results: page } = await got('https://npiregistry.cms.hhs.gov/api/', {
      searchParams: {
        version: '2.1',
        taxonomy_description: taxonomy, // e.g. "Dentist"
        city,
        state,
        limit: 200,
        skip,
      },
    }).json();

    if (!page || page.length === 0) break;

    for (const p of page) {
      const addr = (p.addresses || []).find(a => a.address_purpose === 'LOCATION') || {};
      results.push({
        npi: p.number,
        providerName: p.basic?.organization_name ||
          `${p.basic?.first_name ?? ''} ${p.basic?.last_name ?? ''}`.trim(),
        specialty: (p.taxonomies?.find(t => t.primary) || {}).desc,
        phone: addr.telephone_number,
        fullAddress: `${addr.address_1 ?? ''}, ${addr.city ?? ''}, ${addr.state ?? ''} ${addr.postal_code ?? ''}`,
      });
    }
  }
  return results;
}

const leads = await searchProviders({ taxonomy: 'Dentist', city: 'Austin', state: 'TX' });
console.log(leads.length, 'providers');
Enter fullscreen mode Exit fullscreen mode

In a few seconds you have name, NPI, specialty, phone, and full address for every dentist in a city — straight from the source of truth, updated as providers re-enumerate with CMS.

The missing piece: email enrichment

The registry gives you a phone and address but no email — that's the gap that makes the raw API only half useful for cold outreach. The fix is a second pass: for each provider, find their practice website (Google/Bing the name + city, or use the listed org URL), then crawl the site's contact and about pages to extract emails, additional phone numbers, social links, and even the tech stack.

That enrichment loop is where the value is. A practice's info@ or front-desk email plus a verified website turns a registry row into an actual lead.

What a finished lead looks like

After the registry pull + website enrichment, each row carries:

  • npi, providerName, providerType (individual vs organization)
  • specialty, credential, licenseNumber, licenseState
  • fullAddress, phone, fax
  • website, email / emails (all discovered), websitePhones
  • socialLinks (Facebook, Instagram), techStack, contactPageUrl

The techStack field is a sneaky-good qualifier for agencies: a dentist still on a 2014 template website is a far warmer lead for a web-redesign or paid-ads pitch than one already running a modern stack.

A note on doing this responsibly

The NPI registry is public business-contact data — practice addresses and front-desk lines, not patient data, and nothing HIPAA-covered. Still: respect CAN-SPAM, honor unsubscribes, and target the practice, not individuals' personal info. This is B2B outreach to businesses, full stop.

If you'd rather not build and maintain the registry pagination + website-enrichment crawler yourself, I packaged the whole pipeline into a Healthcare Provider Leads actor — you give it a specialty, city, and state, toggle enrichEmails, optionally set onlyWithEmail, and it returns the enriched rows above, one per provider. But the NPPES API is free and public, so even the DIY version above will get an agency a clean, current vertical list today.

Top comments (0)