# NPPES NPI Registry Scraper (US Providers) (`knotty_mistveil/nppes-npi-registry`) Actor

US health care providers from the official CMS NPPES NPI Registry API: NPI, taxonomy, licence, practice address, phone, fax and authorized official.

- **URL**: https://apify.com/knotty\_mistveil/nppes-npi-registry.md
- **Developed by:** [Jason](https://apify.com/knotty_mistveil) (community)
- **Categories:** Business, Lead generation
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

$3.00 / 1,000 provider returneds

This Actor is paid per event. You are not charged for the Apify platform usage, but only a fixed price for specific events.

Learn more: https://docs.apify.com/actors/running/actors-in-store.md#pay-per-event

## What's an Apify Actor?

Actors are web data automations that power AI and operations. They run on the Apify platform to scrape websites, process data, connect APIs, and automate workflows.
In Batch mode, an Actor accepts a well-defined JSON input, performs an action which can take anything from a few seconds to a few hours,
and optionally produces a well-defined JSON output, datasets with results, or files in key-value store.
In Standby mode, an Actor provides a web server which can be used as a website, API, or an MCP server.
Actors are written with capital "A".

## How to integrate an Actor?

If asked about integration, you help developers integrate Actors into their projects.
You adapt to their stack and deliver integrations that are safe, well-documented, and production-ready.
The best way to integrate Actors is as follows.

- **AI agents and MCP clients** — the [Apify MCP server](https://docs.apify.com/integrations/mcp.md) at `https://mcp.apify.com` (remote, streamable HTTP, OAuth on first use).
- **Agentic workflows and local Actor development** — [Agent Skills](https://apify.com/.well-known/agent-skills/index.json) with the [Apify CLI](https://docs.apify.com/cli/docs.md): `npm install -g apify-cli`, then `apify login`.
- **JavaScript/TypeScript projects** — the official [JS/TS client](https://docs.apify.com/api/client/js/docs.md): `npm install apify-client`.
- **Python projects** — the official [Python client](https://docs.apify.com/api/client/python/docs.md): `pip install apify-client`.
- **Any other language** — the [REST API](https://docs.apify.com/api/v2.md).

For usage examples, see the [API](#api) section below.

For more details, see Apify documentation as [Markdown index](https://docs.apify.com/llms.txt) and [Markdown full-text](https://docs.apify.com/llms-full.txt).

# README

## NPPES NPI Registry Scraper (US Providers)

Pulls US health care providers from the **NPPES NPI Registry**, the national directory every doctor, dentist, pharmacy, clinic and hospital in the United States must be listed in to bill insurance, through the official CMS Read API v2.1. No API key, no account, no browser automation. You get one flat JSON record per provider with the 10-digit NPI, the provider or organization name, credentials, the Healthcare Provider Taxonomy code and description, the state licence number, the full practice address with phone and fax, the mailing address, the organization's authorized official with their title and direct phone, and the enumeration and last-updated dates - filtered by taxonomy, state, city, ZIP prefix, NPI type, name, or a list of NPIs to look up directly.

**The one thing the API cannot do, and this Actor can.** Any single NPI Registry query returns at most **1,200 records** - 200 rows a request and a `skip` that stops at 1,000 - however many providers actually match. Ask it for dentists in California and you get 1,200 of the 52,265 there are, with no warning that the other 51,065 exist. This Actor detects a query that has filled that ceiling and automatically partitions it - first by NPI type, then down the ZIP-code prefix tree - re-running the narrower queries until every record is reachable, de-duplicating across them, and writing a `complete` flag to the run summary so you know whether the answer you have is the whole answer.

### What you get

Every record contains:

| Field | Description |
| --- | --- |
| `id` | The NPI, e.g. `1760813802` - same as `npi`, present because every Actor in this series has an `id` |
| `source` | `nppes`, constant |
| `url` | `https://npiregistry.cms.hhs.gov/provider-view/{npi}` - the public NPI Registry page |
| `npi` | The unique 10-digit National Provider Identifier |
| `enumerationType` | `NPI-1` (an individual provider) or `NPI-2` (an organization) |
| `providerName` | The organization's legal business name for `NPI-2`; prefix + first + middle + last + suffix for `NPI-1` |
| `firstName` / `lastName` | Individual provider's names (`null` for organizations) |
| `credential` | Free text exactly as the provider typed it - `DDS`, `D.D.S.`, `dds`, `DDS, MS` are all real values (see *Limits and freshness*) |
| `gender` | `M` or `F` as NPPES publishes it in `basic.sex`; `null` for organizations |
| `organizationName` | Legal business name (`null` for individuals) |
| `authorizedOfficialName` / `authorizedOfficialTitle` / `authorizedOfficialPhone` | The named person an organization registered as its authorized official, their job title and their direct line. `null` for individuals |
| `primaryTaxonomyCode` / `primaryTaxonomyDescription` | The Healthcare Provider Taxonomy the provider flagged primary, e.g. `1223G0001X` / `Dentist, General Practice` |
| `licenseNumber` / `licenseState` | The state licence attached to that primary taxonomy |
| `allTaxonomies` | Every taxonomy on the record as `{code, description, group, license, state, primary}` - a provider may hold up to 15 |
| `practiceAddress` | The practice location on one line: `7259 OWENSMOUTH AVE, CANOGA PARK, CA 91303-1530` |
| `practiceCity` / `practiceState` / `practiceZip` | The same address split up; `practiceZip` is ZIP+4 when NPPES holds nine digits, and its first five characters are always the 5-digit ZIP |
| `practicePhone` / `practiceFax` | Phone and fax at the practice location |
| `mailingAddress` | The mailing address on one line - often, but not always, the same as the practice address |
| `enumerationDate` | Date the NPI was issued, `YYYY-MM-DD` |
| `lastUpdated` | Date the provider last revised the record, `YYYY-MM-DD` |
| `status` | `Active` or `Deactivated` |
| `fetchedAt` | ISO timestamp of the run that produced the record |

The dataset has three views: **Overview** (NPI, name, taxonomy, city, phone, link), **Contacts** (credential, authorized official, phone, fax, both addresses) and **Taxonomy and licence** (codes, licence number and state, dates, status).

### Input

| Input | Type | Default | Description |
| --- | --- | --- | --- |
| `taxonomies` | string\[] | `["Dentist"]` | Taxonomy descriptions: a classification (`Dentist`, `Pharmacy`, `Chiropractor`, `Optometrist`), a full description (`Dentist, General Practice`), or a prefix with `*` and at least two leading characters (`Nurse*`). Each is queried separately and the results merged. |
| `states` | string\[] | `["CA"]` | Two-letter codes, including territories (`PR`, `GU`), military (`AE`) and the foreign-address codes `XX`/`ZZ`. Each is queried separately. |
| `cities` | string\[] | none | City names as NPPES stores them, usually upper case. `SAN*` is a prefix search. |
| `postalCodes` | string\[] | none | A 5-digit ZIP (`90210`, which also matches the stored ZIP+4), a full ZIP+4, or a prefix of at least two digits (`902` / `902*`). |
| `enumerationType` | string | `both` | `both`, `NPI-1` (individuals) or `NPI-2` (organizations). Picking one also doubles how deep a single query reaches. |
| `addressPurpose` | string | `LOCATION` | Which address the city/state/ZIP filters match: `LOCATION` (any practice location), `ANY` (the API's own default - mailing address too), `MAILING`, `PRIMARY`, `SECONDARY`. |
| `organizationName` | string | none | Type 2 legal business name; `Kaiser*` is a prefix search. NPPES also searches "doing business as" and former names. |
| `lastName` / `firstName` | string | none | Type 1 provider names; `Smith*` is a prefix search. First names also match common aliases (Robert for Bob). |
| `npiNumbers` | string\[] | none | Ten-digit NPIs to look up directly, one request and one record each. Ignores the search filters above (the API rejects `number` combined with anything else); the date filters still apply. |
| `enumeratedFrom` / `enumeratedTo` | string | none | Keep only NPIs issued in this `YYYY-MM-DD` window. Applied after fetching - the API has no date parameter (see *Limits and freshness*). |
| `updatedWithinDays` | integer | none | Keep only records revised in the last N days. Applied after fetching. |
| `maxItems` | integer | `500` | Stop after this many providers. You are charged per provider, so this is also the cost cap. |
| `maxRequests` | integer | `1000` | Budget on API calls, which also bounds the run time. One call returns up to 200 providers; each level of partitioning costs up to 100 probe calls. |

The API insists on at least one of `taxonomies`, `cities`, `postalCodes`, `organizationName`, `lastName`, `firstName` or `npiNumbers`: a state, an NPI type or an address purpose on their own are refused. This Actor checks that, the state codes, the ZIP shapes, the two-character wildcard rule and the date formats **before the first request**, and stops the run with the reason as its status, so a typo costs nothing.

Example - every dental practice (organizations only) in the Los Angeles 902 ZIP area, with the authorized official's phone:

```json
{
  "taxonomies": ["Dentist"],
  "postalCodes": ["902*"],
  "enumerationType": "NPI-2",
  "addressPurpose": "LOCATION",
  "maxItems": 2000
}
```

Example - newly enumerated providers to reach before anyone else, run weekly:

```json
{
  "taxonomies": ["Nurse Practitioner"],
  "states": ["TX"],
  "enumeratedFrom": "2026-08-01",
  "maxItems": 500
}
```

Example - a complete state download that partitions itself past the API ceiling:

```json
{ "taxonomies": ["Dentist"], "states": ["CA"], "maxItems": 100000, "maxRequests": 3000 }
```

That input with `maxRequests` at 900 returned 52,265 providers in 8 minutes on 8 September 2026 - see *Limits and freshness*.

Example - re-check a list of NPIs you already hold, for licence and address changes:

```json
{ "npiNumbers": ["1760813802", "1497431100"], "maxItems": 100 }
```

### Output example

A real record from a live run on 8 September 2026 (`{"taxonomies": ["Dentist"], "states": ["CA"], "enumerationType": "NPI-1", "maxItems": 3}`), unedited:

```json
{
  "id": "1497431100",
  "source": "nppes",
  "url": "https://npiregistry.cms.hhs.gov/provider-view/1497431100",
  "npi": "1497431100",
  "enumerationType": "NPI-1",
  "providerName": "Dr. SOHRAB AALAM",
  "firstName": "SOHRAB",
  "lastName": "AALAM",
  "credential": "DMD",
  "gender": "M",
  "organizationName": null,
  "authorizedOfficialName": null,
  "authorizedOfficialTitle": null,
  "authorizedOfficialPhone": null,
  "primaryTaxonomyCode": "122300000X",
  "primaryTaxonomyDescription": "Dentist",
  "licenseNumber": "108817",
  "licenseState": "CA",
  "allTaxonomies": [
    { "code": "122300000X", "description": "Dentist", "group": null, "license": "108817", "state": "CA", "primary": true }
  ],
  "practiceAddress": "17010 RED HILL AVE STE D, IRVINE, CA 92614-5626",
  "practiceCity": "IRVINE",
  "practiceState": "CA",
  "practiceZip": "92614-5626",
  "practicePhone": "949-975-0150",
  "practiceFax": null,
  "mailingAddress": "17010 RED HILL AVE STE D, IRVINE, CA 92614-5626",
  "enumerationDate": "2023-06-26",
  "lastUpdated": "2023-06-26",
  "status": "Active",
  "fetchedAt": "2026-09-08T05:15:04.509Z"
}
```

An organization (`NPI-2`) from the same registry looks the same but fills the other half of the record - NPI `1760813802`, `"providerName": "101 DENTAL GROUP"`, `"organizationName": "101 DENTAL GROUP"`, `"authorizedOfficialName": "AMIR H CHOROOMI"`, `"authorizedOfficialTitle": "dentist"`, `"authorizedOfficialPhone": "818-735-3800"`, `"practicePhone": "818-999-9900"`, `"practiceFax": "818-999-9978"`, and `firstName`, `lastName`, `credential` and `gender` all `null`.

Every run also writes a `SUMMARY` record to the key-value store with the filters used, how many query cells were read, how many hit the 1,200-record ceiling and were partitioned, how many duplicates the partitioning produced, how many records the date filters dropped, the request count, and the `complete` flag described below.

### Pricing

Pay per result: **US$0.003 per provider delivered** (US$3 per 1,000 providers). You are charged only for records pushed to the dataset; a run that returns nothing, or that fails validation before its first request, costs nothing. Runs on the Apify free plan are limited by the platform's free-tier usage allowance.

### Data source and licence

- **Source.** `GET https://npiregistry.cms.hhs.gov/api/?version=2.1`, the NPPES Read API operated by the Centers for Medicare and Medicaid Services. It is public and needs no key, no account and no registration; CMS states plainly that "There is no charge to use the NPI Registry". Documentation: <https://npiregistry.cms.hhs.gov/api-page> (read 8 September 2026). Versions 1.0 and 2.0 are retired; this Actor uses 2.1.
- **Licence.** NPPES provider data is produced by CMS, an agency of the US Department of Health and Human Services, as part of its official duties, which makes it a US Government work: not subject to domestic copyright under 17 U.S.C. § 105, and free to reuse commercially. CMS publishes it under the **NPPES Data Dissemination Notice** (CMS-6060-N, 72 FR 30011, 30 May 2007), which lists the data elements that are required to be disclosed under the Freedom of Information Act; the registry's own help pages state that "All information produced by the NPPES Read API is provided in accordance with the NPPES Data Dissemination Notice". CMS's own dissemination page confirms the position: "The information disclosed on the NPI Registry and in the downloadable files are FOIA-disclosable and are required to be disclosed under the FOIA" (<https://www.cms.gov/medicare/regulations-guidance/administrative-simplification/data-dissemination>, read 8 September 2026).
- **Attribution.** No attribution wording is mandated. Cite it as "Source: CMS NPPES NPI Registry" and link to the provider page. Do not use the data in a way that implies endorsement by CMS, HHS or any federal official, and do not reuse federal agency logos or trademarks.
- **Not a licence check.** CMS's own warning, shown on every NPI Registry page: issuance of an NPI does not ensure or validate that the provider is licensed or credentialed. `licenseNumber` is what the provider self-reported to NPPES; verify it with the state board before relying on it.
- **Personal data.** This dataset is about identified people. `providerName`, `firstName`, `lastName`, `credential`, `gender`, `practiceAddress`, `practicePhone`, `practiceFax` and the `authorizedOfficial*` fields are the professional details of named individuals. They are lawfully public - CMS is *required* by FOIA to disclose them, and says explicitly that there is "no way to 'opt out' or 'suppress' the NPPES record data for health care providers with active NPIs" - but public is not the same as unrestricted. If you use these records to make contact, the applicable regime is yours to satisfy: **TCPA** and the FCC rules govern calls, texts and unsolicited faxes to those numbers (the fax provisions in particular, since `practiceFax` is a fax number and the TCPA's junk-fax rules carry statutory damages); **CAN-SPAM** governs commercial email if you enrich these records with addresses; state telemarketing and do-not-call registers apply on top; and the GDPR applies if you are processing from, or contacting into, the EU/UK. A sole proprietor's practice address is frequently their home address. Nothing here is legal advice.

### Limits and freshness

- **The 1,200-record ceiling is the API's, not this Actor's.** CMS documents it: "An API query will return a maximum of 200 results per request. The Skip field in the API will let you skip up to 1000 records ... you can get up to a maximum of 1,200 records over six requests." Verified 8 September 2026: `limit=201` silently returns 200, and `skip=1001` returns byte-for-byte the same page as `skip=1000`. There is no total-match count in the response, so a query that matches 50,000 providers looks exactly like one that matches 1,200.
- **How the partitioning works.** When the deepest page a query allows (`skip=1000`) comes back full, the Actor knows the query is capped and splits it. First by `enumeration_type`, giving individuals and organizations a 1,200 ceiling each. Then by ZIP-code prefix: no ZIP filter becomes `00*` … `99*`, an N-digit prefix becomes its ten N+1-digit children, down to a single 5-digit ZIP. Records are de-duplicated by NPI across every cell. **Measured 8 September 2026:** `{"taxonomies": ["Dentist"], "states": ["CA"], "addressPurpose": "LOCATION", "maxItems": 100000, "maxRequests": 900}` delivered **52,265 unique dentists** - 43.6 times what a single query can reach - in 8 min 7 s over 746 requests and 433 query cells (26 of which had filled the ceiling and were split, 194 of which were empty ZIP prefixes), dropping 33,167 records already seen in a wider cell and finishing with `complete: true` and not one repeated NPI in the dataset. The same input without partitioning is the 1,200 records the API hands everybody.
- **Where partitioning stops, and what happens then.** A single 5-digit ZIP is the floor: `postal_code=90210` also matches records stored as `902101234`, so a six-digit prefix would silently drop every provider whose address carries only the five-digit ZIP. If one 5-digit ZIP plus one NPI type plus your other filters *still* holds more than 1,200 providers, the Actor cannot reach the rest - it logs a warning naming that cell, lists it in `SUMMARY.cellsUnsplittable`, and sets `complete: false`. It never reports a truncated result as complete. ZIP partitioning only works on numeric US ZIP codes, so a cell that had to be split can miss a provider whose matched address is foreign (`postal_code` "L1T5A4"); sweep those separately with `states: ["XX"]` or `["ZZ"]`, the codes NPPES puts on foreign addresses.
- **`SUMMARY.complete` is the completeness guarantee.** It is `true` only when every queued cell was read to its end, no cell was left at the ceiling unsplit, the request budget held, and every record the run kept was actually delivered. `maxItems`, `maxRequests` and the Apify spend limit each set it to `false`, and the run log says which one and what to raise.
- **Volumes and speed, measured 8 September 2026** (`addressPurpose: "LOCATION"`): every dentist practising in **Wyoming** is 615 records, 4 requests, 5.1 s; **Rhode Island** 1,068 records, 6 requests, 6.9 s; **California** 52,265 records (38,074 individuals and 14,191 organizations), 746 requests, 8 min 7 s. The default 500-record run takes 3 requests and about 4 s. A full page of 200 arrives in about 0.65 s, so throughput is roughly **150 records a second** while pages are full and **107 a second** averaged over the whole Californian run - the difference is the empty ZIP prefixes a deep run has to rule out at one request each.
- **Rate limits.** CMS documents none. Twenty consecutive requests with no pacing all returned HTTP 200 (measured 8 September 2026, about 1.4 requests a second end to end). The Actor still paces itself at no more than 5 requests a second, and `fetchWithRetry` retries 429 and 5xx with exponential back-off, honouring `Retry-After`.
- **The API has no date parameter of any kind.** `enumeratedFrom`, `enumeratedTo` and `updatedWithinDays` are therefore applied by the Actor to records it has already fetched, and results are ordered by name, not by date - so a narrow date window over a broad query reads a great many records to keep a few. It lowers your bill (you pay only for kept records) but not the run time. `SUMMARY.scanned` versus `SUMMARY.pushed` shows the ratio; add a state, city or ZIP filter to keep it sane.
- **`addressPurpose` matters more than it looks.** With the API's own default, city, state and ZIP match the mailing address as readily as the practice location, and the two can be in different states: of 50 records returned for `state=CA` with no address purpose, **2 had their practice location outside California** and matched only on the mailing address. This Actor therefore defaults to `LOCATION`, and every `practice*` field in the output is that location. Set `addressPurpose: "ANY"` to get the API's behaviour back.
- **An unknown state code returns nothing rather than an error.** `state=QQ` answers HTTP 200 with `result_count: 0` (verified for QQ, JJ, BB, EE on 8 September 2026), so a typo would hand you an empty dataset and no explanation. The Actor validates the code against the 65 two-letter codes NPPES uses - the 50 states, DC, the territories and freely associated states, the three military codes and the `XX`/`ZZ` foreign placeholders - and refuses to start on anything else.
- **`credential` is free text and is published unchanged.** Of the 52,265 Californian dentists, 34,044 supplied a credential and wrote it **686 different ways**: `DDS` (15,875), `D.D.S.` (8,271), `DMD` (3,216), `D.D.S` (1,337), `D.M.D.` (1,217), `dds` (1,018), `DDS, MS` (356) and a long tail. Match it case-insensitively with the dots stripped; the Actor does not collapse them to a canonical form, because that would throw away the second qualification in `DDS, MS`.
- **NPPES filler is removed.** The enumerator writes a literal `--` into `name_prefix`, `name_suffix` and their authorized-official twins when nothing was supplied: 883 of those four fields across a 1,200-record sample held one. Every text field is trimmed, whitespace-collapsed, and published as `null` rather than as `--`, `.` or `N/A`, so `providerName` never comes out as `RONALD AANERUD --` - not one `--` survived into the 52,265 delivered Californian records. Nine-digit ZIPs become `91303-1530`; ten-digit bare phone numbers become `818-735-3800`; foreign postal codes and numbers are left exactly as published.
- **Addresses are picked by purpose, never by position.** The CMS help page says `addresses[0]` is the practice location and `addresses[1]` the mailing address. Live responses put `MAILING` first on the records sampled, so this Actor selects on `address_purpose`. All 1,200 sampled records carried exactly one `LOCATION` and one `MAILING` entry; 78 also had secondary practice locations, which the API returns in a separate `practiceLocations` array.
- **`LOCATION` includes secondary practice locations; the output reports the primary one.** The `practice*` fields are the provider's *primary* practice location, but `addressPurpose: "LOCATION"` matches any practice location they filed - so a dentist whose main surgery is in Las Vegas and who also practises in Burbank matches a California search and is published with a Nevada practice address. In the 52,265-record Californian run, **395 records (0.76%)** had a primary practice location outside California for exactly that reason. Set `addressPurpose: "PRIMARY"` when the primary location itself has to be inside the filter.
- **Missing values are the provider's, not the Actor's.** Across the 52,265-record Californian run: 52,261 had a practice phone (99.99%), 26,185 a fax (50.1%), 46,093 a licence number (88.2%), 34,044 a credential (65.1%), and 38,038 of the 38,074 individuals a `gender`. All 14,191 organizations carried an authorized official's name. What the provider never filed stays `null` rather than being invented, and `taxonomy_group` - empty on 966 of 1,419 sampled taxonomy entries - becomes `null` rather than an empty string.
- **Freshness.** CMS states the API "retrieves data from NPPES daily". Measured 8 September 2026 across 3,600 records in six taxonomies: the newest `lastUpdated` was **2026-09-07** (the previous day) and the newest `enumerationDate` **2026-09-04**. The 52,265-record Californian run agreed - newest `lastUpdated` and `enumerationDate` both 2026-09-07, oldest `enumerationDate` 2005-05-23, the month NPIs were first issued. Records are revised in place, so re-run with an overlapping window and use `lastUpdated` to spot changes. Search results in every sample were `Active`; CMS advises that only the NPI and deactivation date be shown for deactivated NPIs, so treat a `Deactivated` status as the end of the record's usefulness rather than as a lead.

### Use cases

- **Territory lists for medical-device, dental and pharma sales:** one run per taxonomy and state gives every practice in the patch with its address, phone, fax and - for organizations - the authorized official's name, title and direct line, ready for a CRM import.
- **Provider directories, credentialing and network build-out:** NPI, taxonomy code, self-reported state licence and current practice address for every provider in a region, re-run weekly against `lastUpdated` to catch moves, closures and new enumerations before your directory goes stale.
- **Market and health-policy analysis:** count providers by taxonomy, ZIP or state to map specialty density and care deserts, or track new enumerations over time with `enumeratedFrom` - the partitioning is what makes a whole-state or whole-specialty census possible at all.

### Support

Open an issue on the Actor's Issues tab on Apify Store. Replies within 14 days. Bug reports that name the run id and the input used are fixed fastest.

# Actor input Schema

## `taxonomies` (type: `array`):

Healthcare Provider Taxonomy descriptions, matched as the registry stores them: a classification such as Dentist, Pharmacy, Chiropractor, Optometrist or Physician Assistant, or a full description such as "Dentist, General Practice" or "Internal Medicine, Cardiovascular Disease". A trailing \* is a prefix search and needs at least two characters before it (Nurse\*). Every entry is queried separately and the results are merged and de-duplicated. An unknown description stops the run with the API's own message.

## `states` (type: `array`):

Two-letter state codes (CA, NY, TX), territories (PR, GU, VI, AS, MP), military codes (AA, AE, AP) or the codes NPPES puts on foreign addresses (XX, ZZ). Every entry is queried separately. A state cannot be the only filter - the API rejects it - and an unknown code silently returns nothing there, so this Actor validates the code before the first request.

## `cities` (type: `array`):

City names as NPPES stores them, usually upper case (SAN DIEGO). A trailing \* is a prefix search and needs at least two characters (SAN\*). Enter APO or FPO for military addresses. Every entry is queried separately.

## `postalCodes` (type: `array`):

A full 5-digit ZIP (90210, which also matches the stored ZIP+4), a full ZIP+4, or a prefix of at least two digits (902 or 902\*) to cover a whole area. Every entry is queried separately.

## `enumerationType` (type: `string`):

NPI-1 is an individual provider (a person), NPI-2 an organization (a practice, clinic, pharmacy or hospital). "Both" returns each type in the same dataset. Picking one type also doubles how deep a single query can go, because the API's 1,200-record ceiling then applies to that type alone.

## `addressPurpose` (type: `string`):

LOCATION matches any practice location, MAILING the mailing address, PRIMARY the primary practice location only, SECONDARY the secondary ones. ANY is the API's own default and matches the mailing address as well - which is how a provider whose surgery is in Arizona turns up in a California search (2 of 50 sampled records on 8 September 2026). Leave it on LOCATION unless you specifically want mailing addresses.

## `organizationName` (type: `string`):

Legal business name of a Type 2 provider. A trailing \* is a prefix search and needs at least two characters (Kaiser\*). NPPES also searches the "doing business as" and former names, so a result may show a different organization name from the one you typed.

## `lastName` (type: `string`):

Last name of a Type 1 individual provider. A trailing \* is a prefix search and needs at least two characters (Smith\*).

## `firstName` (type: `string`):

First name of a Type 1 individual provider. A trailing \* is a prefix search and needs at least two characters. NPPES also matches common aliases (Robert for Bob).

## `npiNumbers` (type: `array`):

Ten-digit NPI numbers to look up one by one, one request and one record each. When this is set the search filters above are ignored, because the API rejects a number query combined with any other criterion. The date filters below still apply.

## `enumeratedFrom` (type: `string`):

Keep only providers whose NPI was issued on or after this date, YYYY-MM-DD. The Read API has no date parameter at all, so this filter is applied to records after they are fetched: it lowers the yield of a run, not its request count.

## `enumeratedTo` (type: `string`):

Keep only providers whose NPI was issued on or before this date, YYYY-MM-DD. Applied after fetching, like Enumerated from.

## `updatedWithinDays` (type: `integer`):

Keep only providers whose record was last updated in the past N days. Applied after fetching, like the enumeration dates. Leave empty for no last-updated filter.

## `maxItems` (type: `integer`):

Stop after this many providers. You are charged per provider delivered, so this is also the cost cap. Measured on 8 September 2026: every dentist practising in Wyoming is 615 records, in Rhode Island 1,068, in California 52,265 (which took 746 requests and 8 minutes, because the Actor had to partition its way past the API's 1,200-record ceiling).

## `maxRequests` (type: `integer`):

Safety budget on calls to the NPI Registry, which also bounds the run time (the Actor makes at most 5 calls a second). One call returns up to 200 providers, and splitting a query that overflows the API's 1,200-record ceiling costs up to 100 probe calls per level. Raise it for a nationwide download.

## Actor input object example

```json
{
  "taxonomies": [
    "Dentist"
  ],
  "states": [
    "CA"
  ],
  "cities": [],
  "postalCodes": [],
  "enumerationType": "both",
  "addressPurpose": "LOCATION",
  "npiNumbers": [],
  "maxItems": 500,
  "maxRequests": 1000
}
```

# Actor output Schema

## `results` (type: `string`):

All providers returned by the run, one JSON object each. Append ?format=csv or ?format=xlsx to download a spreadsheet.

## `summary` (type: `string`):

Counts, the filters applied, how many query cells hit the API's 1,200-record ceiling and were partitioned, duplicates dropped, requests used and the `complete` coverage flag, written at the end of the run.

# API

You can run this Actor programmatically using our API. Below are code examples in JavaScript, Python, and CLI, as well as the OpenAPI specification and MCP server setup.

## JavaScript example

```javascript
import { ApifyClient } from 'apify-client';

// Initialize the ApifyClient with your Apify API token
// Replace the '<YOUR_API_TOKEN>' with your token
const client = new ApifyClient({
    token: '<YOUR_API_TOKEN>',
});

// Prepare Actor input
const input = {
    "taxonomies": [
        "Dentist"
    ],
    "states": [
        "CA"
    ],
    "cities": [],
    "postalCodes": [],
    "enumerationType": "both",
    "addressPurpose": "LOCATION",
    "organizationName": "",
    "lastName": "",
    "firstName": "",
    "npiNumbers": [],
    "maxItems": 500,
    "maxRequests": 1000
};

// Run the Actor and wait for it to finish
const run = await client.actor("knotty_mistveil/nppes-npi-registry").call(input);

// Fetch and print Actor results from the run's dataset (if any)
console.log('Results from dataset');
console.log(`💾 Check your data here: https://console.apify.com/storage/datasets/${run.defaultDatasetId}`);
const { items } = await client.dataset(run.defaultDatasetId).listItems();
items.forEach((item) => {
    console.dir(item);
});

// 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/js/docs

```

## Python example

```python
from apify_client import ApifyClient

# Initialize the ApifyClient with your Apify API token
# Replace '<YOUR_API_TOKEN>' with your token.
client = ApifyClient("<YOUR_API_TOKEN>")

# Prepare the Actor input
run_input = {
    "taxonomies": ["Dentist"],
    "states": ["CA"],
    "cities": [],
    "postalCodes": [],
    "enumerationType": "both",
    "addressPurpose": "LOCATION",
    "organizationName": "",
    "lastName": "",
    "firstName": "",
    "npiNumbers": [],
    "maxItems": 500,
    "maxRequests": 1000,
}

# Run the Actor and wait for it to finish
run = client.actor("knotty_mistveil/nppes-npi-registry").call(run_input=run_input)

# Fetch and print Actor results from the run's dataset (if there are any)
print(f"💾 Check your data here: https://console.apify.com/storage/datasets/{run.default_dataset_id}")
for item in client.dataset(run.default_dataset_id).iterate_items():
    print(item)

# 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/python/docs/quick-start

```

## CLI example

```bash
echo '{
  "taxonomies": [
    "Dentist"
  ],
  "states": [
    "CA"
  ],
  "cities": [],
  "postalCodes": [],
  "enumerationType": "both",
  "addressPurpose": "LOCATION",
  "organizationName": "",
  "lastName": "",
  "firstName": "",
  "npiNumbers": [],
  "maxItems": 500,
  "maxRequests": 1000
}' |
apify call knotty_mistveil/nppes-npi-registry --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,knotty_mistveil/nppes-npi-registry"
        }
    }
}
```

The hosted server signs you in with OAuth on first connect, so no API token belongs in this config. Clients without OAuth support can send an `Authorization: Bearer <APIFY_API_TOKEN>` header instead, using a token from API & Integrations in Apify Console (https://console.apify.com/settings/integrations).

## OpenAPI specification

Download the OpenAPI definition: https://api.apify.com/v2/actors/vio8sRWIhcunb1kvf/builds/VWpYTFewFrs1fA1ka/openapi.json
