# NPI Registry Search - US Healthcare Providers (`gubidonius/npi-registry`) Actor

Searches the NPPES NPI Registry for US doctors, dentists, clinics and other healthcare providers, with practice address, phone and specialty. NPPES caps any one search at 1,200 records and never says how many matched, so every row here carries whether the answer is complete. No key and no login.

- **URL**: https://apify.com/gubidonius/npi-registry.md
- **Developed by:** [Gregory Bolshakov](https://apify.com/gubidonius) (community)
- **Categories:** Lead generation, Business, MCP servers
- **Stats:** 1 total users, 0 monthly users, 0.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

Pay per event

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

## NPI Registry Search

Searches the NPPES NPI Registry for US healthcare providers. Doctors, dentists, clinics,
hospitals and anyone else who holds an NPI. You get the practice address, the phone number
on file, the specialty and the licence, as JSON, CSV or Excel.

No API key and no login. The data is public and this Actor reads it directly.

### The problem this one solves

NPPES will not return more than 1,200 records for a single search, and it does not tell you
that. The response has two fields, the page you asked for and how many rows are on it. There
is no total anywhere.

Worse, when you page past the limit it does not return an error. It returns the same page
again. Measured on 31 August 2026, a search for Los Angeles gives identical results at skip
1000, 1100, 1400 and 20000, each one reporting a full page of 200. A scraper that keeps
paging gets 1,200 real providers and then the same 200 over and over, and every repeat looks
like fresh data.

So this Actor does two things about it.

It splits a search that is too big into pieces small enough for NPPES to answer whole. Los
Angeles alone cannot be split enough, but a postal code prefix can, and a search of Puerto
Rico's 009 prefix returned 2,964 providers where a single query stops at 1,200.

When it still cannot get everything, it says so. Every row carries `searchComplete`,
`searchLimitedBy` and the exact query it came from. You never have to guess whether the file
you downloaded is the whole answer.

### What you get

46 columns per provider. The ones people use most:

- `npi`, `entityType`, name fields, `credential`
- `practiceAddress`, `practiceCity`, `practiceState`, `practicePhone`, `practiceFax`
- `mailingAddress` and the rest of the mailing fields, kept separate
- `primaryTaxonomyDescription`, `licenseNumber`, `licenseState`
- `practiceLocationCount` and `practiceLocationCities` for a provider working at several sites
- `searchComplete`, `searchLimitedBy`, `searchSlice`

The practice address and the mailing address are never merged. On 4,600 records I sampled,
32.8% of them have a mailing city that is different from the practice city, because the
mailing address is often a billing service in another state. If you filter on city and those
two are treated as one field, a third of your rows are wrong.

The specialty comes from the record's own primary flag, not from the first item in the list.
`primaryTaxonomySource` tells you which it was.

### A row can match on an address that is not its practice address

Search for Providence, Rhode Island and some rows come back with a Boston or Birmingham
address. The filter is working. NPPES matches your city and state against three things: the
practice address, the mailing address, and every extra clinic on the record.

I ran that search on 3 September 2026 and read 80 rows. 15 had a practice address outside
Rhode Island. 11 of those list a Providence clinic in `practiceLocationCities` and the other
4 have a Providence mailing address in `mailingCity`. Every one of the 15 explains itself on
the row, and none of them is a broken filter.

If you only want providers whose practice address is in the state, filter on `practiceState`
after the run.

### Search fields

State, city, postal code, specialty, first name, last name, organisation name, entity type.
A trailing `*` matches a prefix and needs at least two characters before it, so `Sm*` works
and `S*` does not.

NPPES will not accept a state on its own. Pair it with a city, a postal code or a specialty.

You can also pass a list of NPI numbers and look them up directly.

### Cost

Pay per event, priced on the Actor page. A search that matches nobody is free, because
nothing is charged until rows are actually written to your dataset.

### Limits worth knowing

A search still capped after splitting is reported, not hidden. Look at `searchLimitedBy`. If
it says `sourceCeiling`, narrow the search by postal code or specialty and run it again.

Every record a search returns has status A. I sampled 4,600 across 12 different query shapes
on 31 August 2026 and found no deactivated NPIs, so do not expect this Actor to find them.

`lastUpdated` is worth reading. A record can be years old and still active.

# Actor input Schema

## `state` (type: `string`):

Two-letter US state, for example CA. NPPES will not accept a state on its own, so pair it with a city, a postal code or a specialty. It is matched against the mailing address and the extra clinics as well as the practice address.

## `city` (type: `string`):

City name, for example Providence. A trailing \* matches a prefix and needs at least two characters before it. NPPES matches this against the practice address, the mailing address and every extra clinic on the record, so a row can come back with an out of state practice address and still be a correct match. practiceLocationCities and mailingCity say which one it was.

## `postalCode` (type: `string`):

Five-digit ZIP, or a prefix with a trailing \* such as 029\*. A prefix is also what the Actor splits on when a search is too big for NPPES to return whole.

## `taxonomyDescription` (type: `string`):

Provider specialty as NPPES writes it, for example Dermatology or Family Medicine. A trailing \* matches a prefix.

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

Individual providers only. A trailing \* matches a prefix.

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

Individual providers only. A trailing \* matches a prefix.

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

Organisations only, for example Kaiser\*. A trailing \* matches a prefix.

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

Look up specific 10-digit NPI numbers directly. Runs alongside the search, and each lookup returns the one matching provider.

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

Limit to individual providers or to organisations. Left empty, both are returned and this is the first thing the Actor splits on when a search is too big.

## `activeOnly` (type: `boolean`):

Drop NPIs whose status is not A. Every one of 4,600 sampled search results was already active, so this changes nothing on a normal search today. It is here because the status field is the source's to change, not ours.

## `maxResults` (type: `integer`):

Stop after this many rows. Being stopped here is reported as maxResults, which is not the same as NPPES running out or capping the search.

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

Ceiling on requests to NPPES. A wide search splits into many smaller ones, and this bounds how far that goes.

## Actor input object example

```json
{
  "state": "RI",
  "city": "Providence",
  "enumerationType": "",
  "activeOnly": false,
  "maxResults": 1000,
  "maxRequests": 60
}
```

# Actor output Schema

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

No description

# 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 = {
    "state": "RI",
    "city": "Providence"
};

// Run the Actor and wait for it to finish
const run = await client.actor("gubidonius/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 = {
    "state": "RI",
    "city": "Providence",
}

# Run the Actor and wait for it to finish
run = client.actor("gubidonius/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 '{
  "state": "RI",
  "city": "Providence"
}' |
apify call gubidonius/npi-registry --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,gubidonius/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/OcRrKOtzpIHoTLzDH/builds/L5OmmdxphmeEd4ca4/openapi.json
