# NPI Registry Provider Search & Change Monitor (`keepable_injera/npi-registry-monitor`) Actor

Search the CMS NPPES NPI Registry by name, taxonomy, city, state or NPI and monitor providers for changes (address, taxonomy, status) with onlyNew mode. FOIA-disclosable public data. Unofficial; not affiliated with CMS or HHS.

- **URL**: https://apify.com/keepable_injera/npi-registry-monitor.md
- **Developed by:** [Chee Chiaw Lim](https://apify.com/keepable_injera) (community)
- **Categories:** Business, Lead generation, Automation
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

$1.50 / 1,000 provider results

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?

An Actor is a serverless cloud program that runs on the Apify platform. It has two run modes.
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.

Apify vocabulary and the platform model are defined once, in the agent quickstart at https://apify.com/agents.md.

## 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.

Do not guess an integration path. Every one of them is in the agent quickstart at https://apify.com/agents.md: the Apify MCP server, Agent Skills with the Apify CLI, the JavaScript and Python clients, the REST API, and the account-free path for an agent with no human to sign in. It also carries the rule on stating cost before the first paid run.

For examples already wired to this Actor's own input schema, see the [API](#api) section below.

Each client library has reference documentation the quickstart does not restate: [JavaScript/TypeScript](https://docs.apify.com/api/client/js/docs.md) (`npm install apify-client`) and [Python](https://docs.apify.com/api/client/python/docs.md) (`pip install apify-client`).

# README

## NPI Registry Provider Search & Change Monitor

Search the public CMS NPPES NPI Registry API v2.1 and track changes to provider names, status, update dates, primary taxonomy, practice addresses, and taxonomies. Supports individual and organization providers. No API key or login is required.

### Input examples

A small targeted search, including previously returned providers.

```json
{
  "searches": [{ "last_name": "Smith", "state": "CA", "limit": 5 }],
  "maxPerSearch": 5,
  "maxItems": 5,
  "onlyNew": false
}
```

Direct lookups with change monitoring.

```json
{
  "npis": ["1234567893"],
  "onlyNew": true,
  "stateStoreName": "npi-registry-monitor-state"
}
```

| Field | Default | Meaning |
| --- | --- | --- |
| `searches` | `[]` | JSON array of CMS search objects. |
| `npis` | `[]` | List of ten-digit NPI strings. |
| `maxPerSearch` | `200` | Maximum records fetched per search, up to 1,200. |
| `maxItems` | `500` | Maximum provider records pushed per run, up to 10,000. |
| `onlyNew` | `true` | Emit only new or changed providers. |
| `stateStoreName` | `npi-registry-monitor-state` | Named KV store for change history. |

Search fields are `number`, `enumeration_type` (`NPI-1` or `NPI-2`), `taxonomy_description`, `first_name`, `last_name`, `organization_name`, `city`, `state`, `postal_code`, `country_code`, `limit` (1–200), and `skip` (0–1000). Supply at least one search or direct NPI. State alone is not a valid CMS search criterion. This Actor also requires a number, name, organization, taxonomy, city, or postal code to keep searches targeted. Pagination begins at `skip`, stops on a short page, and respects the API's skip ceiling.

### Output sample

This fictional record shows the output shape. Missing source values are returned as `null`.

```json
{
  "npi": "1234567893",
  "enumerationType": "NPI-1",
  "name": "JANE SMITH MD",
  "firstName": "JANE",
  "lastName": "SMITH",
  "credential": "MD",
  "organizationName": "",
  "gender": "F",
  "soleProprietor": "NO",
  "status": "A",
  "enumerationDate": "2010-01-01",
  "lastUpdated": "2026-01-01",
  "primaryTaxonomyCode": "207Q00000X",
  "primaryTaxonomyDesc": "Family Medicine",
  "taxonomies": [{ "code": "207Q00000X", "desc": "Family Medicine", "primary": true, "state": "CA", "license": "EXAMPLE" }],
  "practiceAddress": { "line1": "100 EXAMPLE ST", "line2": "", "city": "SACRAMENTO", "state": "CA", "postalCode": "95814", "phone": "9165550100" },
  "mailingAddress": { "line1": "PO BOX 100", "line2": "", "city": "SACRAMENTO", "state": "CA", "postalCode": "95814", "phone": "" },
  "npiUrl": "https://npiregistry.cms.hhs.gov/provider-view/1234567893",
  "matchedSearch": 0,
  "changeType": "new",
  "changedFields": [],
  "checkedAt": "2026-10-20T00:00:00.000Z"
}
```

`matchedSearch` is a zero-based index. Direct lookups follow the search array. Duplicate NPIs appear once per run. Error records contain `error`, `checkedAt`, and a search index when available. CMS can return `Errors` with HTTP 200; these become error records too. The default KV store contains `RUN_SUMMARY` with fetched, eligible, and pushed counts and charge-limit status.

### Change history

The named store's `PROVIDERS` record maps each NPI to a SHA-256 fingerprint and meaningful field snapshot. Stable JSON covers `name`, `status`, `lastUpdated`, `primaryTaxonomyCode`, `practiceAddress`, and `taxonomies`. Each provider has `changeType` equal to `new`, `changed`, or `unchanged`. `changedFields` lists changed top-level fingerprint fields; new records have an empty list.

History is saved only after a successful provider push. `onlyNew: false` also emits unchanged providers. Dataset pushes and state writes are separate operations, so a crash between them can cause a duplicate on retry. Avoid overlapping runs sharing a state store. Use separate store names for independent histories.

### Pricing

The `provider` event costs $0.0015 per emitted provider, or $1.50 per 1,000 providers. There is no run fee. Errors and skipped providers are not charged. Local runs do not incur event charges. Store pricing must match this event before publication.

### Limits and source terms

- At most 200 records per request and 1,200 per search over six pages. The maximum skip is 1,000.
- Requests start at least 1,000 ms apart per host. Each run allows at most 60 HTTP attempts, including retries.
- HTTP 429 and 5xx responses use bounded exponential backoff and `Retry-After`. Requests time out after 30 seconds.
- Runs stop at `maxItems` or the remaining event charge limit. Search results do not guarantee complete coverage.
- CMS imposes hourly query limits. Bulk users must use the free [NPPES downloadable file](https://download.cms.gov/nppes/NPI_Files.html).
- CMS exposes FOIA-disclosable public data. NPI issuance does not ensure or validate licensing or credentials.

The source is the [CMS NPI Registry API](https://npiregistry.cms.hhs.gov/api-page). [TERMS.md](TERMS.md) records the supplied terms check and the binding bulk-use restriction. This Actor is unofficial, not affiliated with or endorsed by CMS or NPPES. It uses public documented APIs only, no logos, and no login.

### Local development

Use Node 20 or newer. Run `npm test`. Tests stub fetch and use local storage. `npm start` uses `storage/key_value_stores/default/INPUT.json` and normally calls CMS. In a sandbox without network, preload a fetch stub when running it. Tests also launch the real Actor process with stubbed CMS responses and inspect its dataset and state.

To run with the bundled CMS response and inspect local output without network access, use the sample input.

```sh
NODE_OPTIONS='--import=./test/fixtures/preload.mjs' npm start
```

# Actor input Schema

## `searches` (type: `array`):

CMS API search objects. State alone is not a valid search. Supports number, enumeration_type, taxonomy_description, first_name, last_name, organization_name, city, state, postal_code, country_code, limit and skip.

## `npis` (type: `array`):

Ten-digit NPI numbers.

## `maxPerSearch` (type: `integer`):

Maximum providers per search (CMS caps each search at 1,200).

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

maxItems

## `onlyNew` (type: `boolean`):

Compare meaningful fields with the last successfully pushed record.

## `stateStoreName` (type: `string`):

stateStoreName

## Actor input object example

```json
{
  "searches": [
    {
      "last_name": "Smith",
      "state": "CA",
      "limit": 5
    }
  ],
  "npis": [],
  "maxPerSearch": 200,
  "maxItems": 500,
  "onlyNew": true,
  "stateStoreName": "npi-registry-monitor-state"
}
```

# Actor output Schema

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

No description

## `runSummary` (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 = {
    "searches": [
        {
            "last_name": "Smith",
            "state": "CA",
            "limit": 5
        }
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("keepable_injera/npi-registry-monitor").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 = { "searches": [{
            "last_name": "Smith",
            "state": "CA",
            "limit": 5,
        }] }

# Run the Actor and wait for it to finish
run = client.actor("keepable_injera/npi-registry-monitor").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 '{
  "searches": [
    {
      "last_name": "Smith",
      "state": "CA",
      "limit": 5
    }
  ]
}' |
apify call keepable_injera/npi-registry-monitor --silent --output-dataset

```

## MCP server setup

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

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/6cIduur2RnGjcwcBQ/builds/8DWiKfTLcZVJmWfoh/openapi.json
