Disclosure: I'm Ben, and I built the Apify Actor mentioned later in this post. I charge per result for it. Everything in the tutorial part works with free tools too.
If you sell to dentists, clinics, chiropractors or therapists, or you need to verify a list of providers, the best free source in the US is the NPI Registry (NPPES), run by CMS. Every HIPAA-covered provider has a National Provider Identifier, about 8 million of them, and the registry has a public API with no key.
Each record has the NPI, name, credential, taxonomies (specialties), license, practice and mailing addresses with phone and fax, the date the NPI was issued, and for organizations the authorized official (often the owner or CEO) with title and phone.
What it doesn't have: email addresses. Plan around that from the start.
If you need everything, CMS also publishes the full NPPES data dissemination file (monthly, plus weekly updates). It's free and complete, and it's a very large CSV with hundreds of columns. For targeted lists, the API is easier.
Step 1: Query the API
https://npiregistry.cms.hhs.gov/api/?version=2.1&taxonomy_description=Dentist&city=San Diego&state=CA&limit=200
limit is at most 200, and you page with skip. Here's a pager that also keeps only providers whose primary taxonomy is a dentist one. It ran as-is on 25 Oct 2026:
import requests
def npi(**q):
out, skip = [], 0
while True:
r = requests.get("https://npiregistry.cms.hhs.gov/api/", params={
"version": "2.1", "limit": 200, "skip": skip, **q}, timeout=30).json()
batch = r.get("results", [])
out += batch
if len(batch) < 200 or skip >= 1000: # skip is capped at 1000 -> max 1,200 per query
return out
skip += 200
rows = npi(taxonomy_description="Dentist", city="San Diego", state="CA")
print(len(rows))
primary = [r for r in rows if any(t["primary"] and "Dent" in t["desc"] for t in r["taxonomies"])]
print(len(primary), "with a dentist taxonomy as primary")
Output:
1200
1193 with a dentist taxonomy as primary
Exactly 1,200 is suspicious, and it should be.
Step 2: The pitfalls
-
Every query stops at 1,200 results.
skipis capped at 1000, and a page holds 200. Worse, it fails silently: I asked forskip=1000,skip=1200andskip=2000on a California dentist search, and all three returned the same page. If you just keep paging, you collect duplicates and think you're done. To get a whole city or state, split the search (by city, or by ZIP prefix, and for a state by both) until each piece is under 1,200, then dedupe on NPI. -
Specialty matches any taxonomy. A provider with Dentist as a secondary taxonomy still matches. Above, 7 of 1,200 didn't have a dentist taxonomy as primary. For strict lists, check the
primaryflag. -
Two addresses. Each record has a
MAILINGand aLOCATIONaddress, and in the record I checked the mailing address came first in the list. Useaddress_purpose, not position. City and state searches can match either one, so a "San Diego" search can include providers who only mail from there. -
"New practices" isn't a query parameter. The API can't filter on the NPI issue date (
enumeration_date). To find newly enumerated providers, you pull and filter client-side. -
Self-reported data. Phones and fax numbers are what providers entered, and some records haven't been updated in years.
last_updatedtells you how stale a record is.
Step 3: Or use the packaged version
I packaged this as an Apify Actor, NPI Registry Scraper – NPPES Healthcare Provider Leads. The main thing it adds is auto-splitting: large searches are split by state and ZIP prefix, so you can pull a whole state past the 1,200 cap. When it splits by state, it keeps providers whose practice location is in that state.
Run-ready link: open it in Apify Console with an example input already filled in ({"specialties": ["Dentist"], "states": ["CA"], "cities": ["San Diego, CA"], "maxItems": 50}). Click Start, or change the input first.
{
"specialties": ["Dentist"],
"states": ["CA"],
"cities": ["San Diego, CA"],
"maxItems": 50
}
Other inputs: individual vs organization, ZIPs, names, a list of NPIs to verify, and filters for NPI issued after, record updated after, only with phone, and primary specialty must match. Output is deduplicated, with formatted phones and ZIPs and separate practice and mailing addresses.
That input ran on 18 Oct 2026 in about 18 seconds and returned 50 providers. Some rows:
| NPI | Name | Type | Primary specialty | NPI issued |
|---|---|---|---|---|
| 1326171653 | Dr. WILLIAM JOSEPH DI ZINNO, DMD | Individual | Dentist, General Practice | 2007-03-14 |
| 1699214478 | GLENN C. ASHMORE DDS, INC. | Organization | Dentist | 2017-02-14 |
| 1730639188 | A.S. KIM DDS, INC. | Organization | Dentist, General Practice | 2016-10-10 |
| 1487251294 | Dr. ZEKI ABDULAALI, DDS | Individual | Dentist, General Practice | 2020-10-05 |
Organization rows are the useful ones for B2B. For A.S. KIM DDS, INC., the row has the authorized official (a DDS, title "CEO") with a phone, the practice phone, license number, other names (DBA "VILLAGE HILLCREST DENTAL"), and the count of additional practice locations.
A bigger real example from my testing: all individual chiropractors in California with a phone came to 3,000 providers in about 3 minutes.
A monthly "new practices" list
Set NPI issued after (enumeratedAfter) to 90 days ago, pick your specialties and states, and schedule it monthly. New clinicians and new group practices show up as they get their NPIs.
Limits, honestly
- No emails. NPPES doesn't publish them. You get practice phone and fax, the mailing address and, for organizations, the authorized official. If you need an email, you'll need the practice's website.
- Self-reported and sometimes stale.
- NPI holders only. It covers HIPAA-covered providers, not every wellness or cosmetic business.
- Not affiliated with CMS or HHS.
What it costs
$0.001 per provider ($1 per 1,000), no start fee. Duplicates and filtered-out records are free, and runs stop at your "maximum cost per run". The 50-provider test was 5 cents. The API and the full CMS file are free.
Play fair
Practice phones are business lines, but authorized officials are people. TCPA rules apply to calls and texts, CAN-SPAM to email, and some providers will rightly be annoyed by cold outreach. Be relevant and honour opt-outs.
Do you work with NPPES data? I'd like to hear how you deal with stale records, so tell me in the comments.
Top comments (0)