NPPES NPI Bulk Scraper — Every Provider by Specialty & State avatar

NPPES NPI Bulk Scraper — Every Provider by Specialty & State

Pricing

$2.00 / 1,000 per provider returneds

Go to Apify Store
NPPES NPI Bulk Scraper — Every Provider by Specialty & State

NPPES NPI Bulk Scraper — Every Provider by Specialty & State

Pull an ENTIRE medical specialty from the official NPPES NPI Registry. The CMS API caps every query at 1,200 records; this Actor auto-splits by state and ZIP until each slice fits — 22,189 NY dentists, not 1,200. Name, phone, fax, practice address, license. Or paste NPI numbers to verify a list.

Pricing

$2.00 / 1,000 per provider returneds

Rating

0.0

(0)

Developer

Scrapers Delight

Scrapers Delight

Maintained by Community

Actor stats

0

Bookmarked

2

Total users

1

Monthly active users

6 days ago

Last modified

Share

Export a whole medical specialty from the official NPPES NPI Registry — not the first 1,200 rows of it. One row per unique NPI: npi, providerName, credential, gender, organizationName, authorizedOfficialName, authorizedOfficialTitle, authorizedOfficialPhone, matchedTaxonomy, primaryTaxonomy, primaryTaxonomyCode, licenseNumber, licenseState, phone, fax, practiceAddress1, practiceCity, practiceState, practiceZip, mailingAddress, otherNames, enumerationDate, lastUpdated, profileUrl. Filter by specialty, state, ZIP prefix and provider type — or paste a list of NPI numbers and verify them instead. No login. No API key. No CAPTCHA solving. $2.00 per 1,000 providers, billed per unique NPI delivered.

The CMS API hard-caps every query at 1,200 records. This Actor returned 22,189.

That is not a slogan, it is a measured pair of numbers from the same search on the same day. state=NY + taxonomy_description=Dentist gives you 1,200 rows and then silently repeats them forever. This Actor split that one query into 146 smaller ones and returned 22,189 unique New York dentists in 278 API requests (measured 2026-08-13; the same run on 2026-08-12 returned 22,187 — the registry moves daily). There are 379,654 dentists in the United States. A single query gets you 0.3% of them.

Click Try for free and press Start. The Actor ships prefilled with New York dentists capped at 100 providers — that run costs $0.20, takes 6 API requests and 19 seconds on the Apify platform, and needs zero edits.


The 1,200 cap, and why it is the entire product

The free CMS NPPES API is generous in every way except one. limit is silently clamped to 200, skip is clamped at 1,000, and past that the API re-serves the same final page forever without ever telling you it truncated. The cap is per query, not per page:

QueryProviders the API will hand over
state=NY + Dentist1,200, then it repeats
ZIP 10001, no specialty at all1,200, then it repeats
ZIP 10016 + Student, individuals only1,200, then it repeats

This Actor treats 1,200 as a signal, not a limit. Any slice that comes back at exactly 1,200 is split and re-queried until every leaf fits underneath the cap:

specialty
└── state (your list, or all 56 states + territories)
└── ZIP-2 10* (measured prefix list, per state)
└── ZIP-3 100*
└── ZIP-4 1001*
└── ZIP-5 10016
└── provider type NPI-1 / NPI-2
└── surname prefix last_name=sm*

A slice under the cap is already complete and is never expanded, so narrow pulls stay cheap while dense ones walk the tree. Measured on live runs:

RunSingle-query ceilingThis ActorRequestsBucketsWall clock
All NY dentists (2026-08-12)1,20022,187 (21,499 practising in NY)2781461m 27s on Apify
All NY dentists (2026-08-13 re-run)1,20022,189 (21,501 practising in NY)278146
ZIP 10016 + Student1,2002,6536891m 15s
All WY dentists (2026-08-13)1,200615 raw / 599 practising in WY417s

Wyoming needs four requests because 615 is already under the cap — you are never charged for a split you did not need.

Why we believe the coverage is real. The New York pull was run down two different enumeration paths — once with the practice-address pin on and once off, which produces different cell boundaries, different split points and different raw totals (22,187 vs 23,467 rows). Both runs returned exactly 21,499 providers whose practice address is in New York. Two independent walks converging on the same number is the strongest completeness evidence available without a bulk file to diff against.


Quick start

  1. Press Try for free and then Start. Nothing to fill in — it ships prefilled with Dentist + NY + a 100-provider cap.
  2. Watch the log. Every slice prints its filters, its row count and whether it hit the 1,200 cap and had to split.
  3. Open the Dataset tab and export CSV / Excel / JSON, or read it from the API.
  4. Widen: pick more specialties from the dropdown, add states, and raise Max providers.

Time to first rows: 6 API requests, 19 seconds on the platform, no code.

{
"specialties": ["Dentist"],
"states": ["NY"],
"maxItems": 100
}

From the API

# pip install apify-client
from apify_client import ApifyClient
client = ApifyClient("<YOUR_APIFY_TOKEN>")
run = client.actor("scrapersdelight/nppes-provider-scraper").call(run_input={
"specialties": ["Nurse Practitioner"],
"states": ["TX"],
"maxItems": 5000,
})
for provider in client.dataset(run["defaultDatasetId"]).iterate_items():
print(provider["npi"], provider["providerName"], provider["phone"])
// npm install apify-client
import { ApifyClient } from 'apify-client';
const client = new ApifyClient({ token: '<YOUR_APIFY_TOKEN>' });
const run = await client.actor('scrapersdelight/nppes-provider-scraper').call({
specialties: ['Chiropractor'],
states: ['FL'],
maxItems: 2000,
});
const { items } = await client.dataset(run.defaultDatasetId).listItems();
# Verify three NPIs and get the rows back in one call
curl -X POST "https://api.apify.com/v2/acts/scrapersdelight~nppes-provider-scraper/run-sync-get-dataset-items?token=<YOUR_APIFY_TOKEN>" \
-H 'Content-Type: application/json' \
-d '{"npiNumbers":["1679540124","1063386555","1245319599"]}'

Read this before you buy rows

1. The specialty is a PREFIX match, not a substring. CMS resolves your text against the start of a NUCC taxonomy classification or specialization. So Dentist works and returns every dental sub-specialty; Nurse, Counselor, Internal Medicine and Orthodontics all work; and Medicine, entist and dontic return nothing — the API answers "No taxonomy codes found with entered description" and this Actor stops with that explanation in the run's status message rather than pushing an empty dataset. The dropdown only contains stems verified live on 2026-08-12, which is also how we found that Psychiatrist is not a taxonomy and fails — the working stem is Psychiatry.

2. A state on its own is not a valid search. state=WY with no other criteria is rejected by CMS itself: {"description":"Field state requires additional search criteria","field":"state"}. You need a specialty, a ZIP, or a list of NPIs. A ZIP on its own is legal, which is how you pull a whole-ZIP roster of every provider of every kind (measured: ZIP 10016 returns 38 distinct primary taxonomies in the first 60 rows).

3. The state filter leaks across state lines even with the practice address pinned. A raw NY sweep returns some providers who merely collect post in New York: 688 of 22,189 rows, 3.1% on the full NY dentist pull (it is 8.4% without the pin). So "Practice address must be in the selected state" defaults to ON, which re-checks the parsed row and gives you a clean 21,501-row in-state list. Turn it off to keep every state-linked provider. Either way practiceState is on every row.

4. Slices are a true partition, not overlapping sets — because we pin the address. By default the CMS API matches city/state/ZIP against any address on a record, practice or mailing, so neighbouring ZIP cells overlap. Every request here pins address_purpose=LOCATION. Measured on Dentist, ZIP cells 770* and 752* share 5 records without it and exactly 0 with it. Nothing is lost by pinning: a practice address is mandatory in NPPES, and 0 of 1,200 sampled providers were missing one.

5. NPPES publishes no email addresses, for anyone. Not a scraping limitation — the registry does not collect them. Anyone selling you "NPI emails" generated them somewhere else. You get phone (100%), fax (50.3%), a full practice address (100%) and, on organizations, a named authorized official with a direct phone (100%).


What you get

One row per unique NPI, deduplicated across every slice of the split.

Identity

FieldTypeNotes
npistringThe 10-digit National Provider Identifier. The dedupe key.
npiTypestringIndividual or Organization
enumerationTypestringNPI-1 (individual) or NPI-2 (organization)
statusstringActive. See Honest limits — CMS does not serve deactivated NPIs at all.

Name

FieldTypeNotes
providerNamestringPerson's full name, or the organization name. Never null.
firstName, middleName, lastNamestringnull on organization rows by design
credentialstringDDS, MD, RN, DMD… as the provider self-reported it
genderstringM / F; null on organizations
soleProprietorstringYES / NO, individuals only
organizationNamestringLegal entity name
otherNamesarrayDBA / former / alternate names

Decision-maker (organizations)

FieldTypeNotes
authorizedOfficialNamestringThe named owner or practice manager
authorizedOfficialTitlestringOWNER, PRESIDENT, OFFICE MANAGER
authorizedOfficialPhonestringFormatted 646-593-9305

Specialty & licence

FieldTypeNotes
specialtyQueriedstringWhat you asked for. null in ZIP-roster and NPI-lookup runs.
matchedTaxonomystringThe taxonomy that actually matched — not always the primary one
primaryTaxonomy, primaryTaxonomyCodestringNUCC description + code, e.g. 1223G0001X
allTaxonomiesarrayEvery taxonomy on the record
licenseNumber, licenseStatestringState licence attached to the primary taxonomy

Contact & location

FieldTypeNotes
phone, faxstringNormalised to 212-688-7722 from the registry's raw 10/11-digit strings
practiceAddress1, practiceAddress2, practiceCity, practiceState, practiceZip, practiceCountrystringThe practice location, always — never the mailing address
practiceAddress, mailingAddressstringOne-line pre-joined versions for a CRM import
practiceLocationCountnumberHow many SECONDARY practice sites this provider publishes (0 on 93.8% of rows)
practiceLocationsarrayEvery secondary site in full: address1, address2, city, state, zip, country, phone, fax, address. A states/postalCodes filter matches these too, so this is where a row whose practiceZip looks "wrong" actually matched

Dates & provenance

FieldTypeNotes
enumerationDate, lastUpdatedstringISO dates as published by CMS
profileUrlstringThe public NPPES page for that NPI
scrapedAtstringISO-8601 UTC timestamp of the run

ZIPs are rendered 10022-4148 when the registry holds nine digits and 11572 when it holds five. null always means CMS published nothing there — it never means the scraper skipped it.

Key-value store artifact: in NPI-lookup mode the run writes NPI_NOT_FOUND{ notFound: [...], checked: n, found: n } — so a credentialing job can diff its own input list programmatically.


Field fill — measured on 21,501 New York dentists

Not a sample: the complete in-state result of a full New York Dentist enumeration at default settings, 2026-08-13. The identical measurement on 2026-08-12's 21,499 rows agreed to one decimal place on every line.

FieldFill
npi, providerName, matchedTaxonomy, primaryTaxonomy, primaryTaxonomyCode100.0%
phone100.0%
practiceAddress1 / practiceCity / practiceState / practiceZip / practiceCountry100.0%
mailingAddress, enumerationDate, lastUpdated, profileUrl100.0%
licenseState90.9%
licenseNumber90.6% (99.8% of individuals, 59.7% of organizations)
gender77.0% (individuals only, by definition)
credential69.7% (90.5% of individuals)
fax50.3%
middleName43.9%
practiceAddress224.7%
otherNames (DBA)7.8%
practiceLocations (secondary practice sites)6.2% carry at least one (n=80 random NPIs, 2026-08-19); practiceLocationCount is 0 on the rest

Phone and street address are 100%. That is unusual — most licence registries we scrape carry contact data on a tenth of rows — and it is why NPPES is the best cold list in US healthcare.

Split by record type on the same 21,501 rows: 77.0% individuals, 23.0% organizations. Every organization row carries an authorizedOfficialName, authorizedOfficialTitle and authorizedOfficialPhone (100.0%) — a named human and a direct line, which is who a DSO, a dental-supply rep or a practice-management SaaS actually needs to reach.

The headline that could mislead you: a licenseNumber fill of 90.6% is really 99.8% for individuals and 59.7% for organizations. If you are buying this data to reconcile against a state licensing board, filter to npiType = Individual first.

Secondary practice sites. practiceAddress*, phone and fax are the provider's PRIMARY practice location. NPPES also publishes SECONDARY sites, and the CMS API matches your states / postalCodes filter against those too — so a ZIP roster can legitimately return a provider whose practiceZip is a different ZIP, because the site that matched is one of the secondary ones. Every secondary site is emitted in full in practiceLocations[] (address, city, state, ZIP, phone, fax), with practiceLocationCount for filtering. Measured 2026-08-19: 6.2% of providers have at least one; NPI 1932975919 has 11 (Miami FL, Princeton NJ, Portland OR, Minneapolis MN, …). fax and practiceAddress2 are read from the practice location only — a MAILING-only fax or address line (~2 pp of rows each, measured on 60 sampled nulls per field) is deliberately not merged in, so the practice-vs-mailing semantics stay clean.


Sample rows

An individual, exactly as the Actor emitted it on 2026-08-13:

{
"npi": "1679540124",
"npiType": "Individual",
"enumerationType": "NPI-1",
"status": "Active",
"providerName": "THEODORE MARK AARONSON",
"firstName": "THEODORE",
"middleName": "MARK",
"lastName": "AARONSON",
"credential": "DDS",
"gender": "M",
"soleProprietor": "NO",
"organizationName": null,
"authorizedOfficialName": null,
"authorizedOfficialTitle": null,
"authorizedOfficialPhone": null,
"specialtyQueried": "Dentist",
"matchedTaxonomy": "Dentist, General Practice",
"primaryTaxonomy": "Dentist, General Practice",
"primaryTaxonomyCode": "1223G0001X",
"licenseNumber": "028432-1",
"licenseState": "NY",
"allTaxonomies": ["Dentist, General Practice"],
"phone": "212-688-7722",
"fax": "212-355-4701",
"practiceAddress1": "305 E 55TH ST",
"practiceAddress2": "SUITE 204",
"practiceCity": "NEW YORK",
"practiceState": "NY",
"practiceZip": "10022-4148",
"practiceCountry": "United States",
"practiceAddress": "305 E 55TH ST, SUITE 204, NEW YORK, NY, 10022-4148",
"mailingAddress": "305 E 55TH ST, SUITE 204, NEW YORK, NY, 10022-4148",
"practiceLocationCount": 0,
"practiceLocations": [],
"otherNames": [],
"enumerationDate": "2006-03-03",
"lastUpdated": "2026-07-13",
"profileUrl": "https://npiregistry.cms.hhs.gov/provider-view/1679540124",
"scrapedAt": "2026-08-13T02:03:25.446Z"
}

An organization from the same run — note the personal-name fields are null by design and the contactable human is the authorized official:

{
"npi": "1063386555",
"npiType": "Organization",
"enumerationType": "NPI-2",
"status": "Active",
"providerName": "1 DAVISON AVE DENTISTRY PLLC",
"firstName": null,
"lastName": null,
"credential": null,
"gender": null,
"organizationName": "1 DAVISON AVE DENTISTRY PLLC",
"authorizedOfficialName": "GAVRIEL GULAMOV",
"authorizedOfficialTitle": "OWNER",
"authorizedOfficialPhone": "646-593-9305",
"specialtyQueried": "Dentist",
"matchedTaxonomy": "Dentist, General Practice",
"primaryTaxonomy": "Dentist, General Practice",
"primaryTaxonomyCode": "1223G0001X",
"licenseNumber": null,
"licenseState": null,
"phone": "646-593-9305",
"fax": null,
"practiceAddress1": "1 DAVISON AVE W",
"practiceCity": "OCEANSIDE",
"practiceState": "NY",
"practiceZip": "11572-2114",
"practiceAddress": "1 DAVISON AVE W, OCEANSIDE, NY, 11572-2114",
"otherNames": ["ELIDENT"],
"enumerationDate": "2025-09-30",
"lastUpdated": "2025-10-09",
"profileUrl": "https://npiregistry.cms.hhs.gov/provider-view/1063386555"
}

Three fields people misread:

  • matchedTaxonomy vs primaryTaxonomy — they differ whenever the specialty you searched is a secondary taxonomy on that provider. matchedTaxonomy is why the row is in your dataset; primaryTaxonomy is what the provider calls themselves first.
  • licenseNumber: null on an organization is normal (59.7% fill). Practices are not licensed the way practitioners are.
  • specialtyQueried: null is not a bug — it means the run was a ZIP roster or an NPI lookup, where no specialty was asked for.

Recipes

Recipe 1 — the whole state, cheap first, then everything. Run it capped, look at the rows, then set maxItems: 0.

{ "specialties": ["Dentist"], "states": ["NY"], "maxItems": 0 }

Recipe 2 — practice owners only, with a named decision-maker on every row. 100% of these carry authorizedOfficialName + authorizedOfficialPhone.

{ "specialties": ["Dentist"], "states": ["TX", "OK", "NM"], "npiType": "NPI-2", "maxItems": 0 }

Recipe 3 — a metro, not a state. ZIP prefixes beat state filters when your territory is a city.

{ "specialties": ["Physical Therapist"], "postalCodes": ["100", "101", "112"], "maxItems": 2000 }

Recipe 4 — a whole-ZIP roster with no specialty at all. Every provider of every kind in that ZIP; the only search shape besides an NPI that CMS accepts without a taxonomy.

{ "postalCodes": ["10016"], "maxItems": 1000 }

Recipe 5 — credentialing / NPI verification. Every other filter is ignored; unfound NPIs land in NPI_NOT_FOUND.

{ "npiNumbers": ["1679540124", "1063386555", "1245319599"] }

Recipe 6 — a national specialty sweep. Leave states empty and it walks all 56 states and territories. Size it first.

{ "specialties": ["Behavior Analyst"], "states": [], "maxItems": 20000 }

Recipe 7 — a taxonomy that is not in the dropdown. The free-text box is additive and validated on the first request.

{ "specialtiesCustom": ["Nurse Anesthetist, Certified Registered"], "states": ["WY"], "maxItems": 500 }

Input

FieldTypeDefaultWhat it does
specialtiesmulti-select["Dentist"]56 verified NUCC taxonomy stems. Each is enumerated separately, so two specialties is roughly twice the work and twice the rows.
specialtiesCustomstring listEscape hatch for any taxonomy not in the dropdown; added to whatever you picked. Use the opening words of the taxonomy name.
statesmulti-select["NY"]56 states and territories with full names. Empty = the whole country for that specialty. Ignored when ZIPs or NPIs are supplied.
postalCodesstring listFull ZIPs (10016) or prefixes (100). Overrides states. Legal with no specialty at all.
npiNumbersstring listPaste 10-digit NPIs to verify a list instead of enumerating. Every other scope filter is ignored.
npiTypeselectBothNPI-1 practitioners / NPI-2 practices. A full NY dentist pull is 77.0% / 23.0%.
practiceStateOnlycheckboxtrueDrops the 3.1% whose practice address is out of state. Ignored in NPI-lookup mode.
activeOnlycheckboxtrueBelt-and-braces: 23,467 of 23,467 rows on an unfiltered pull were already Active.
withPhoneOnlycheckboxfalsePhone fill is 100.0%, so this normally removes nothing.
maxItemsnumber100 (prefilled 100)Your cost ceiling. 100 = $0.20, 10,000 = $20. 0 = no cap. Duplicates never count against it. Defaults to 100 so an API/MCP call that omits it can never bill a surprise — raise it for a bulk pull.
maxRequestsnumber20000Compute ceiling, not a billing one. The full NY pull used 278; a dense single ZIP used 689.
concurrencynumber41–8 slices in flight. 4 is the measured balance.
requestDelayMsnumber150Pause between pages inside one slice.
proxyConfigurationproxyApify datacenter, onSee the transport ladder below. Residential is not needed.

The 56 verified specialty stems

Dentist · Dental Hygienist · Pediatric Dentistry · Orthodontics and Dentofacial Orthopedics · Endodontics · Periodontics · Prosthodontics · Oral & Maxillofacial Surgery · Family Medicine · Internal Medicine · Pediatrics · Obstetrics & Gynecology · Emergency Medicine · Anesthesiology · Dermatology · Ophthalmology · Optometrist · Radiology · Surgery · Orthopaedic Surgery · Urology · Cardiovascular Disease · Gastroenterology · Nephrology · Neurology · Psychiatry · Psychologist · Counselor · Social Worker · Marriage & Family Therapist · Behavior Analyst · Nurse Practitioner · Registered Nurse · Physician Assistant · Chiropractor · Podiatrist · Physical Therapist · Occupational Therapist · Speech-Language Pathologist · Audiologist · Acupuncturist · Massage Therapist · Dietitian, Registered · Nutritionist · Pharmacy · Pharmacist · Clinic/Center · General Acute Care Hospital · Skilled Nursing Facility · Assisted Living Facility · Home Health · Hospice Care, Community Based · Clinical Medical Laboratory · Durable Medical Equipment & Medical Supplies · Ambulance · Veterinarian

Each of these was fired at the live API on 2026-08-12 and returned rows. Note the traps the same test exposed: Psychiatrist fails (use Psychiatry), and so do Medicine, entist and dontic.


Pricing

$0.002 per provider returned — $2.00 per 1,000. Charged on the provider-scraped event, once per unique NPI, and there is no monthly platform fee from this Actor.

What you are not charged for:

  • Never twice for the same provider. The split tree deliberately re-reads a parent cell's rows when it splits, and every one of those repeats is dropped on the NPI before anything is pushed. Measured on the full New York pull: 22,189 rows, 22,189 unique NPIs, 0 duplicates.
  • Never for a row your filters removed. practiceStateOnly, activeOnly and withPhoneOnly are applied before push. The 688 out-of-state New Yorkers were fetched and discarded free.
  • Never for an NPI that is not in the registry. Lookup mode bills the ones it finds and lists the rest in NPI_NOT_FOUND.
  • Never for our retries or our proxy budget. A CloudFront 403 retried on a fresh session costs you nothing.

Rows are charged as they are pushed (Actor.pushData(items, 'provider-scraped')), so if you hit a budget cap you get whole rows and a clean stop, not a half-billed dataset.

What you pullProvidersCost
The prefilled first click100$0.20
Every dentist in Wyoming599$1.20
Every dentist in New York21,501$43.00
Every dentist in the United States379,654~$759

In Apify plan terms: the $5 free credit is about 2,500 providers, and the $29 Starter credit is about 14,500. maxItems is the dial — it is prefilled at 100 on purpose.


Honest limits

  • No email addresses. None, for anybody. NPPES does not collect them, so no scraper can return them from this source. If you need email you need a separate enrichment step against the practice's own website — this Actor does not do that and does not pretend to.
  • No DEA numbers, no NPPES-published specialties beyond the NUCC taxonomy, no patient or claims data. The registry does not publish them.
  • A state on its own cannot start a run. The API rejects it (Field state requires additional search criteria). You need a specialty, a ZIP list, or NPIs. This is a CMS rule, not a design choice here.
  • fax is 50.3% and otherNames is 7.8%. A blank means CMS published nothing, not that the scrape missed it. licenseNumber is 59.7% on organizations.
  • The finest split rung is a two-letter name prefix, because the API rejects one-letter wildcards ("Wildcards require at least two leading characters"). If a bucket is still at exactly 1,200 after aazz, some of its providers are unreachable through the public API — and the run names that bucket in a warning rather than quietly shipping short. On the full New York pull this never happened: 0 unresolved buckets out of 146.
  • activeOnly is nearly a no-op. CMS does not serve deactivated NPIs through the search API at all: 23,467 of 23,467 rows on an unfiltered pull were Active, 0 deactivated. Do not use this Actor to find lapsed providers — it cannot see them.
  • Two specialties cost roughly twice as much as one. Each is enumerated separately from the root. There is no shared work between them.
  • Cross-run dedupe does not exist. Deduplication is within a run. Two runs over the same scope return the same providers and bill twice; dedupe on npi on your side, or slice by state/ZIP so runs do not overlap.
  • Registry data moves daily. The same New York search returned 22,187 on 2026-08-12 and 22,189 on 2026-08-13. Treat any total on this page as a measurement with a date on it, not a constant.

How it works, and the transport ladder

There is no browser, no login, no API key and no CAPTCHA solving. Every request is a plain GET to the public CMS endpoint:

https://npiregistry.cms.hhs.gov/api/?version=2.1&address_purpose=LOCATION
&taxonomy_description=&state=&postal_code=&enumeration_type=&limit=200&skip=

address_purpose=LOCATION is pinned on every call — that is what turns overlapping ZIP cells into a true partition (see gotcha 4). Pagination walks skip=0…1000 in 200-row pages, and a bucket that comes back at exactly 1,200 unique NPIs is handed to the splitter.

The API is free and unauthenticated, but it is not unlimited — it sits behind CloudFront with a rate rule. Measured in this order:

RungResult
Direct, un-proxied, home line403 HTML Request blocked once the IP is rate-flagged — this build's own ZIP-mapping sweep tripped it after ~5,600 calls and it stayed blocked. Reported and then ignored: no Apify container has this IP.
Apify datacenter proxy (the default)200 OK. Sustained load 38/38 consecutive calls (100%) at 1.04 req/s; a full 278-request New York enumeration completed with 0 CloudFront 403s.
Apify RESIDENTIAL200 OK, roughly 2× slower, unnecessary

So the cheap datacenter rung is the default, and a CloudFront HTML 403 is retried on a fresh proxy session rather than being mistaken for an empty result. That the site is not walled is both a trust win and a cost win — you are not paying for residential bandwidth you do not need.


Uniqueness and deduplication

The split tree is designed to overlap: when a bucket caps out, its children re-read rows the parent already returned. That is by design, and it is why deduplication happens before billing.

  • Dedupe key: npi, held in a run-wide set.
  • Measured on a contiguous full enumeration (146 buckets, 278 requests, every leaf of the New York dentist tree): 22,189 rows pushed, 22,189 distinct NPIs, 0 duplicates.
  • With address_purpose=LOCATION pinned, neighbouring ZIP cells 770* and 752* share exactly 0 records. Without it they share 5.
  • Across runs there is no dedupe. Slice by state or ZIP so runs do not overlap, or dedupe on npi downstream.

When a run stops early

This Actor explains itself instead of crashing. Every stop condition below ends the run Succeeded, with the reason in the run's status message and the full detail in the log, and with everything already collected pushed to the dataset. You are only ever billed for providers that were actually returned.

  • No specialty, ZIP code or NPI number supplied → the documented sample runs: Dentist + NY, capped at 100 providers ($0.20). A warning names exactly which input to set. This is what an API, MCP or agent caller sending {} gets — a working example, not an error.
  • Zero rows is reported, not thrown: the status message carries the request count, the bucket count and the two most likely causes (a mid-word specialty, or a genuinely empty scope).
  • An unrecognised specialty is reported on the first request with the exact CMS message and the prefix-matching rule — it never burns your budget walking a tree that cannot match.
  • A state with nothing else is not a search (the CMS API rejects it outright), so it falls back to the sample rather than erroring.
  • An unrecognised state code or provider type is ignored with a warning, and the rest of the scope still runs.
  • A bucket still capped at 1,200 after the finest split is named in a warning, so an incomplete slice is visible instead of silent.
  • CloudFront 403s are retried on a fresh proxy session and counted in the final log line; they are never treated as "no results".
  • In lookup mode, NPIs not in the registry are listed in the log and written to NPI_NOT_FOUND, so a credentialing run can diff its input list programmatically.
  • Hitting maxItems, maxRequests or the run timeout is a warning, not a failure: the run stops taking new work, pushes everything it already has, and the log tells you which limit stopped you.

Who buys this

  • Dental and medical supply / DSO acquisition teamsnpiType: NPI-2 gives one row per practice with authorizedOfficialName, authorizedOfficialTitle and a direct authorizedOfficialPhone at 100% fill. That is an owner list, not a switchboard list.
  • Practice-management and EHR SaaS (Dentrix, Weave, Tebra, Jane class) — territory lists by specialty × ZIP prefix with 100% phone and 100% street address, sized before you hire the rep.
  • Medical device and pharma field salesmatchedTaxonomy plus primaryTaxonomyCode lets you build sub-specialty target lists (Endodontics, Cardiovascular Disease, Behavior Analyst) rather than blunt "dentists".
  • Credentialing, payer enrollment and primary-source verification vendors — paste npiNumbers and get the registry's own record back, plus an explicit not-found list to work.
  • Healthcare data and analytics teamsenumerationDate and lastUpdated on every row make provider-churn and new-entrant analysis possible; licenseNumber + licenseState is the join key to state board data.
  • Staffing and locum agenciesRegistered Nurse, Nurse Practitioner and Physician Assistant by state, with a phone number on every single row.

How this differs from our other actors

ActorWhat it isWhy you would use it instead
npi-registry-scraperThe same registry, one query, no split — stops at the 1,200 ceiling by designCheaper at $0.0005 per provider. Use it for a named provider, one city, one NPI, or any search you already know is under 1,200. No amount of paging it will get you a whole specialty.
nppes-provider-scraper (this one)The bulk enumerator. Splits the query tree until an entire specialty comes outYou need all 22,189 New York dentists, not the first 1,200.
healthgrades-scraperHealthgrades provider profilesYou want star ratings, review counts, awards, "accepting new patients" and per-office GPS — consumer-facing signals NPPES does not carry. $0.0015/provider.
florida-medical-license-scraperFlorida DOH MQA primary sourceYou need licence status, expiry dates and full disciplinary history for Florida. NPPES carries a licence number, never a licence status.

The honest one-liner: NPPES is where you go for complete coverage and a phone number on every row; a state board is where you go to find out whether that licence is actually in good standing.


Integrations

The dataset is a normal Apify dataset, so everything downstream already works: CSV / Excel / JSON / XML export, the Dataset API, webhooks on run completion, and the official Zapier, Make, n8n, Slack and Google Sheets integrations. A typical pipeline is Schedule → this Actor → webhook → your CRM, with npi as the upsert key and lastUpdated as the change detector.


FAQ

Does this need an account, a login or an API key for NPPES? No. The CMS NPPES API is public and unauthenticated. This Actor logs into nothing and solves no challenges.

Why did other NPI scrapers give me only 1,200 rows? Because that is the API's hard cap per query, and most tools page until the API stops giving them new rows and call it done. This one detects the cap and splits the query — 1,200 → 22,189 on a single New York dentist search.

Can I get the entire registry in one run? Not sensibly. Enumerate one specialty at a time — that is the dimension the splitter is built around. All 379,654 US dentists is a real, costed run (~$759); "all 8 million NPIs" is not what this Actor is for.

How much does it cost? $2.00 per 1,000 providers, billed per unique NPI delivered, no per-run fee. The $5 free Apify credit is about 2,500 providers.

Do I get charged for rows that get filtered out, or for duplicates? No to both. Filters run before push, and duplicates are dropped on the NPI before push. Measured 0 duplicates across a 22,189-row enumeration.

Two runs over the same state — will I get duplicates? Yes. Dedupe is per run. Dedupe on npi downstream, or slice by state / ZIP so runs do not overlap.

Can I look up specific NPI numbers? Yes — paste them into npiNumbers and every other filter is ignored. Ones that do not exist are reported in the log and in the NPI_NOT_FOUND key-value record.

Can I get email addresses? No. NPPES does not collect them. You get phone at 100%, fax at 50.3%, a full practice address at 100%, and a named authorized official with a direct phone on 100% of organization rows.

Does it need a proxy? Yes, and it is on by default. The API sits behind CloudFront with a rate rule; a single unproxied IP got blocked after roughly 5,600 calls during development. The cheap Apify datacenter pool is enough — residential is about 2× slower and buys nothing.

Why does my specialty return nothing? CMS prefix-matches taxonomy names. Medicine, entist and dontic match nothing; Internal Medicine, Dentist and Orthodontics do. Psychiatrist also fails — the taxonomy is Psychiatry. Pick from the dropdown and this cannot happen.

Why is providerName filled but firstName empty on some rows? Those are organizations. NPPES stores no personal name for a business — the human is in authorizedOfficialName, which is filled on 100% of organization rows.

Can I pull a whole city rather than a state? Use postalCodes with prefixes — ["100","101","112"] is most of New York City. It is also the only way to pull rows with no specialty filter at all.

Will a run ever succeed with zero rows? Only when the registry genuinely has nothing for your scope. The run ends Succeeded with a status message naming the scope, the request count and the two usual causes (a mid-word specialty, or a filter combination that matches nothing), and nothing is pushed and nothing is billed. It does not crash the run — a failed run is useless to an agent or a scheduled task.

How fresh is the data? It is read live from CMS on every run, and CMS updates NPPES daily. Every row carries lastUpdated so you can see the registry's own freshness per provider.

Can I schedule it? Yes — use Apify Schedules. lastUpdated and enumerationDate make weekly new-provider detection straightforward on your side.


This Actor reads the public NPPES NPI Registry API published by the Centers for Medicare & Medicaid Services. NPI data is a US public record, released by CMS under its own data-dissemination notice; there is no login, no paywall and no authentication involved, and this Actor collects nothing that is behind any authentication.

NPI records contain personal data about named individuals — practitioner names, practice addresses and phone numbers, and the names and direct phone numbers of authorized officials. You are responsible for complying with the NPPES data-dissemination notice and with how you use the data, including TCPA, CAN-SPAM, state telemarketing and do-not-call rules, and GDPR/CCPA where they apply to you. Nothing here is medical, legal or compliance advice.

NPPES and the NPI Registry are operated by CMS. This Actor is an independent tool and is not affiliated with, endorsed by, or sponsored by the Centers for Medicare & Medicaid Services or the US Department of Health and Human Services.


Changelog

BuildDateChange
0.1.4+2026-08-13NPI-lookup mode (npiNumbers + NPI_NOT_FOUND), specialty-free ZIP rosters, 56-stem specialty dropdown + free-text escape hatch, 56-state multi-select, maxItems prefill dropped 2,000 → 100. All coverage and fill numbers re-measured against the live API.
0.1.1 – 0.1.32026-08-12First release: cap-aware split enumeration, address_purpose=LOCATION partition pinning, datacenter-proxy transport.

Last verified against the live NPPES API: 2026-08-13.


Support

Found a missing field, a taxonomy stem that should be in the dropdown, or a filter you need? Open an issue on the Issues tab of this Actor and it goes straight to the developer.

If this saved you a week of paging a capped API, a ⭐⭐⭐⭐⭐ rating genuinely helps other people find it.


Keywords: NPI registry scraper · NPPES scraper · NPI number lookup · NPI database export · CMS provider registry · healthcare provider list · doctor list by specialty · dentist email list alternative · dentist phone list · medical practice owner list · DSO acquisition targets · NUCC taxonomy · provider taxonomy code · NPI-1 NPI-2 · authorized official contact · practice manager phone list · nurse practitioner list by state · registered nurse database · chiropractor leads · physical therapist leads · optometrist list · pharmacy directory · home health agency list · skilled nursing facility list · hospice provider list · DME supplier list · clinical laboratory list · credentialing data · primary source verification · payer enrollment · provider directory · healthcare lead generation · medical device sales leads · pharma territory list · provider data enrichment · NPI bulk download · past the 1200 record limit