# NPPES NPI Registry Lookup (`praise-most-high/nppes-npi-registry-lookup`) Actor

NPPES NPI Registry Lookup extracts US healthcare providers from the CMS NPPES registry and returns 89 flat columns per provider: identity, status, taxonomies, every state licence, mailing and practice addresses, other identifiers and HIE endpoints. Search by NPI, name, specialty or state.

- **URL**: https://apify.com/praise-most-high/nppes-npi-registry-lookup.md
- **Developed by:** [angel nguyen](https://apify.com/praise-most-high) (community)
- **Categories:** Business, Developer tools, Other
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $2.50 / 1,000 provider record 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 Lookup extracts US healthcare providers from the CMS NPPES NPI Registry and gives you 89 flat columns for every provider it returns. Give this npi registry lookup a list of NPI numbers, a surname, a specialty or a state, and you get one row per provider — and you are charged only for the records you actually receive, because a query that matches nothing is free.

![source: CMS NPPES NPI Registry](https://img.shields.io/badge/source-CMS%20NPPES%20NPI%20Registry-0b5394)
![89 columns per provider](https://img.shields.io/badge/columns%20per%20provider-89-2e7d32)
![no API key required](https://img.shields.io/badge/API%20key-not%20required-6a1b9a)

### What this npi registry lookup returns

One flat row per provider, with **89 columns**, read live from the registry during the run that
returns it. The column list is not a marketing number: it is generated from the code that builds
the rows, so what is advertised and what is delivered cannot drift apart.

| group | what you get |
|---|---|
| Identity | `npi`, `entity_type` (Individual or Organization), `entity_type_code` |
| Status and dates | `status`, `enumeration_date`, `last_updated`, `certification_date`, `deactivation_date`, `reactivation_date`, plus both epoch timestamps |
| Individual provider | first, middle and last name, prefix, suffix, `credential`, `sex`, `sole_proprietor` |
| Organization | `organization_name`, `organizational_subpart`, `parent_organization_legal_business_name` |
| Authorized official | name, prefix, suffix, credential, title or position, and direct telephone number |
| Mailing address | nine columns, including telephone and fax |
| Practice address | nine columns, including telephone and fax |
| Primary taxonomy | code, description, group, state, licence number, and whether the registry actually flagged it as primary |
| Every taxonomy | `taxonomy_count`, and all codes, descriptions, **licence numbers and licence states** joined |
| Other names | count, the doing-business-as name, its type and code, and all of them joined |
| Other identifiers | count, the primary one broken out with its code, description, state and issuer, and all of them joined |
| Extra practice locations | count, addresses, states and telephone numbers |
| HIE endpoints | count, endpoints, types, type descriptions, affiliations and uses |
| Provenance | `source`, `source_authority`, `source_url`, `registry_url`, `retrieved_at`, `matched_query`, `query_index` |

Every row carries `registry_url`, a direct link to that provider's page on the NPI Registry, so you
can re-check any row at the source **without going back through this Actor**.

### Why run a nppes provider lookup here rather than by hand

NPPES publishes the national provider registry through an interface built for one person asking one
question at a time. You give this Actor a list, it walks the list inside the registry's own
published limits, and hands back one flat row per provider. What would otherwise be hundreds of
manual queries and a reshaping step becomes one run and one dataset.

Three things it does that are worth knowing before you compare it with anything else:

- **It reads NPPES itself.** Not a resold aggregate, not a cached snapshot that has quietly aged.
  Every field you receive was served by the registry during your run.
- **It keeps what flattening usually loses.** A provider with four taxonomies has four licence
  numbers in four states. Most flat exports keep one. This one keeps the primary broken out into
  typed columns *and* the full set joined, so nothing is silently dropped.
- **It tells you when it found nothing.** A query that matches no provider writes an unbilled row
  saying so, rather than returning a shorter list that reads as "that is all there was".

### How it works

1. You supply one or more search criteria — see **Input** below. NPPES rejects a query with no
   criteria at all, so a run with every field blank looks up nothing and bills nothing.
2. Each criteria set is resolved against the registry's public API, one request at a time.
3. Each matching record is flattened into a row of 89 columns and written to the default dataset.
4. With **Only new or updated providers** on (the default), a provider whose registry record has
   not changed since your last run is skipped and not billed again. "Changed" is judged by the
   registry's own `last_updated_epoch`, not by anything this Actor decides.
5. A criteria set that resolves to nothing writes an **unbilled** row saying which of the three
   things happened: not found, rejected by the registry, or the registry did not answer at all.

Those last three are deliberately kept apart. "The registry says no such provider" and "the registry
did not answer" are different facts, and a row that merges them is worse than no row.

### Input

Give at least one criterion. Every filter below is applied by NPPES itself, so narrowing a search
costs you nothing extra and returns fewer billable rows.

```json
{
  "npiNumbers": ["1760438840", "1104130236"],
  "taxonomyDescription": "Internal Medicine",
  "state": "TX",
  "maxRecordsPerQuery": 50,
  "onlyNewOrUpdated": true
}
```

| field | what it does |
|---|---|
| `npiNumbers` | Look these NPI numbers up directly. The cheapest and most precise mode: one NPI resolves to exactly one provider. |
| `lastName`, `firstName` | Individual provider name. A trailing `*` is a wildcard, e.g. `SMITH*`. |
| `organizationName` | Legal business name of a hospital, clinic or group practice. `*` works here too. |
| `taxonomyDescription` | The registry's own specialty vocabulary, e.g. `Internal Medicine`, `Pharmacy`, `Nurse Practitioner`. |
| `city`, `state`, `postalCode` | Registered address filters. `state` is the two-letter code. |
| `enumerationType` | Restrict to individuals (`NPI-1`) or organizations (`NPI-2`). Defaults to both. |
| `maxRecordsPerQuery` | Hard ceiling on records per search, and therefore on cost. Defaults to 50. |
| `onlyNewOrUpdated` | On by default. A repeat run bills only for records the registry has changed. |
| `includeRawRecord` | Attach the untouched NPPES JSON as `_raw` alongside the flat columns. Does not change what you are charged. |

### Output

One row per provider. This is a real record returned by a real run, trimmed to its populated
columns — the empty ones are present in every row and omitted here only for length:

```json
{
  "source": "NPPES",
  "source_authority": "Centers for Medicare and Medicaid Services (CMS) NPPES NPI Registry",
  "source_url": "https://npiregistry.cms.hhs.gov/api/",
  "registry_url": "https://npiregistry.cms.hhs.gov/provider-view/1225219363",
  "retrieved_at": "2026-09-05T20:41:28.816Z",
  "matched_query": "NPI 1225219363",
  "npi": "1225219363",
  "entity_type_code": "NPI-2",
  "entity_type": "Organization",
  "status": "A",
  "enumeration_date": "2007-11-16",
  "last_updated": "2011-02-25",
  "organization_name": "ACOR ORTHOPAEDIC, INC.",
  "organizational_subpart": "NO",
  "authorized_official_first_name": "JEFFREY",
  "authorized_official_last_name": "ALAIMO",
  "authorized_official_credential": "CPO",
  "authorized_official_title_or_position": "President",
  "authorized_official_telephone_number": "2166624500",
  "mailing_address_1": "18530 S MILES RD",
  "mailing_city": "CLEVELAND",
  "mailing_state": "OH",
  "mailing_postal_code": "441284238",
  "mailing_telephone_number": "216-662-4500",
  "practice_address_1": "18700 S MILES RD",
  "practice_city": "CLEVELAND",
  "practice_state": "OH",
  "practice_postal_code": "441284242",
  "practice_telephone_number": "216-662-4500",
  "address_count": 2,
  "primary_taxonomy_code": "335E00000X",
  "primary_taxonomy_desc": "Prosthetic/Orthotic Supplier",
  "primary_taxonomy_is_primary": true,
  "taxonomy_count": 1,
  "other_names_count": 1,
  "other_name": "CLEVELAND PROSTHETIC CENTER",
  "other_name_type": "Doing Business As",
  "resolved": true
}
```

A criteria set that resolves to nothing emits an **unbilled** row instead, so a miss is visible
rather than silent:

```json
{
  "source": "NPPES",
  "resolved": false,
  "matched_query": "NPI 1063608134",
  "unavailableReason": "NOT_FOUND",
  "note": "The registry answered and matched no provider. This row is not billed."
}
```

### Pricing

| Event | Price | When it fires |
|---|---|---|
| `provider-record` | **$0.0025** per record — **$2.50 per 1,000** | once per provider record written to the dataset |
| `actor-start` | $0.00001 per run — one cent per thousand runs | once when a run starts |

There is no per-query fee, no per-page fee and no meaningful per-run fee. You pay for provider
records and nothing else.

**Worked example.** A run returning 1,000 provider records costs **$2.50** plus $0.00001 for the
start, whether those thousand records came from one search or from fifty. A run that matches
nothing costs $0.00001. A daily repeat run with `onlyNewOrUpdated` left on bills only for the
providers whose registry entry actually changed that day, which in this registry is a small
fraction of any given result set.

**Where the price comes from.** $2.50 per 1,000 is the measured median price of the priced,
run-evidenced comparable listings in this category plus the standard 25% — measured live on
2026-09-05 across nine live comparable listings, seven of which publish a price. It is deliberately
not the cheapest listing in the category. What it buys against the alternatives is the field
surface: 89 columns against the only published field-count claim in this niche, which is 45.

### Limits and troubleshooting

- **The registry serves at most 1,200 records for any single set of criteria** — 200 per page, and
  it stops paging at 1,000. Both were measured directly rather than read off a document, and both
  are enforced *silently* by NPPES: ask for more and you get 1,200 with no error. This Actor caps
  at that figure and says so in the run log instead of quietly returning less than you asked for.
  To reach further, split the search — by state, by specialty, or by ZIP prefix.
- **No API key and no account are needed**, at either end. The registry answered HTTP 200 to an
  unauthenticated request on 2026-09-05, from an ordinary connection and from a datacenter
  connection alike, and it was checked from both because a source proved from one machine is not a
  source proved.
- **Rate limits are the registry's, not ours.** A run that meets one backs off and continues rather
  than failing.
- **NPPES is the authority for correctness.** If a record looks wrong, check it at the
  `registry_url` on that row. This Actor does not correct, enrich, infer or guess at what the
  source served.
- **Fewer rows than expected?** Check `onlyNewOrUpdated` first. On a repeat run it is doing its job.

### Integrations and API

Run it from the Apify Console, the [Apify API](https://docs.apify.com/api/v2), the Apify CLI, or any
scheduler. Results come back as JSON, CSV, XLSX or HTML, through the dataset API, or pushed to a
webhook when the run finishes. Because every row is flat and typed, it loads into a spreadsheet, a
warehouse, Make, Zapier or n8n with no transform step, and into an LLM context without reshaping.

```bash
curl "https://api.apify.com/v2/acts/praise-most-high~nppes-npi-registry-lookup/run-sync-get-dataset-items?token=YOUR_TOKEN" \
  -H 'Content-Type: application/json' \
  -d '{"taxonomyDescription":"Internal Medicine","state":"TX","maxRecordsPerQuery":25}'
```

Three dataset views ship with the Actor — **Providers**, **Licences** and **Addresses and
contact** — so the columns you want are one click away rather than a spreadsheet edit away.

### Frequently asked questions

**Is the data live?** Yes. Every record is read from NPPES during the run that returns it. Nothing
is served from a stored snapshot.

**What happens if my query matches nothing?** You are not charged for it. The run writes an unbilled
row that says which of the three possible things happened.

**Can I run this every day?** Yes, and you should leave `onlyNewOrUpdated` on so you pay only for
what the registry has actually changed. It defaults to on.

**How many providers can I get in one run?** Up to 1,200 per set of criteria, which is the
registry's own ceiling, and as many criteria sets as you like in one run via `npiNumbers`.

**Do you store the underlying records?** No. The output carries identifiers, metadata and the
source's own link.

**Who is the data about?** Healthcare providers in their professional capacity, exactly as CMS
publishes them. This Actor returns no field CMS does not already publish, and no patient data of
any kind.

**Why 89 columns and not more?** Because 89 is what the registry actually serves once its repeated
groups are counted and joined. Offering a column NPPES does not serve would be a false claim, not a
feature.

### Legal and data handling

CMS states, on its own data dissemination page:

> The information disclosed on the NPI Registry and in the downloadable files are FOIA-disclosable
> and are required to be disclosed under the FOIA and the eFOIA amendments to the FOIA. There is no
> charge to view the data.

That statement was read from the
[CMS data dissemination page](https://www.cms.gov/medicare/regulations-guidance/administrative-simplification/data-dissemination)
on 2026-09-05. This Actor reproduces the registry on that basis and names NPPES as the source in
every row it writes. It reads only what NPPES publishes openly, holds no credential belonging to any
third party, and bypasses no access control. It returns information about providers in their
professional capacity and no patient information of any kind.

### Actor Recommendations

Other source-named extractors from this publisher, each reading one named source and saying so:

- [Shopify Store Product Scraper](https://apify.com/praise-most-high/shopify-store-product-catalog)
  — reads a storefront's own product feed and returns one row per product.
- [WTA Tennis Rankings Scraper](https://apify.com/praise-most-high/wta-tennis-rankings-scraper)
  — reads the WTA's own published ranking tables and returns one row per ranked player.
- [Skool Community Stats Scraper](https://apify.com/praise-most-high/skool-community-stats-scraper)
  — reads named Skool community pages and returns their published membership statistics.

### Support and feedback

Open an issue on this Actor's **Issues** tab. Bug reports are answered fastest when they carry the
run ID and the exact input you used, because both let the run be reproduced rather than guessed at.
If a provider record looks wrong, include its `registry_url` — that is the source's own page for
that provider, and it settles whether the problem is in this Actor or in the registry.

# Actor input Schema

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

Look up these NPI numbers directly, one row per number. This is the cheapest and most precise way to use the Actor: an NPI resolves to exactly one provider, so you are billed for exactly what you asked for. An NPI that is not in the registry costs you nothing.

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

Last name of an individual provider. Combine with a state to narrow the search, and a narrower search is a smaller bill. NPPES accepts a trailing \* as a wildcard, for example SMITH\*.

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

First name of an individual provider. Only useful alongside a last name; on its own it matches too broadly to be worth paying for.

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

Legal business name of an organizational provider such as a hospital, clinic or group practice. NPPES accepts a trailing \* as a wildcard, for example CLEVELAND\*.

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

The provider taxonomy description exactly as NPPES publishes it, for example Internal Medicine, Pediatrics, Pharmacy or Nurse Practitioner. This is the registry's own specialty vocabulary, not a free-text search.

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

Two-letter US state or territory code, for example TX, CA or NY. Matches the provider's registered address.

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

City on the provider's registered address, for example HOUSTON.

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

ZIP code on the provider's registered address. NPPES accepts a trailing \* as a wildcard, for example 770\*.

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

Restrict to individual providers (NPI-1) or organizations (NPI-2). Leave on Any to return both.

## `maxRecordsPerQuery` (type: `integer`):

A hard ceiling on how many provider records one search may return, and therefore a hard ceiling on what that search can cost you. Defaults to 50, which is the cheaper side on purpose. The registry itself will not serve more than 1,200 records for any single set of criteria (limit 200 plus skip 1,000, both measured), so values above 1200 are clamped and the run says so in its log.

## `onlyNewOrUpdated` (type: `boolean`):

On by default. A repeat run delivers and bills only for providers whose registry record has actually changed since your last run, judged by NPPES's own last\_updated\_epoch field rather than by anything this Actor decides. Turn it off to receive the full result set every time, which costs more.

## `includeRawRecord` (type: `boolean`):

Attach the untouched NPPES JSON record to each row as \_raw, alongside the 89 flat columns. Useful when you need the full nesting of taxonomies, addresses, endpoints and other names. It does not change what you are charged.

## Actor input object example

```json
{
  "npiNumbers": [
    "1760438840"
  ],
  "lastName": "SMITH",
  "firstName": "MARIA",
  "organizationName": "HOSPITAL*",
  "taxonomyDescription": "Internal Medicine",
  "state": "TX",
  "city": "HOUSTON",
  "postalCode": "77002",
  "enumerationType": "ANY",
  "maxRecordsPerQuery": 50,
  "onlyNewOrUpdated": true,
  "includeRawRecord": false
}
```

# Actor output Schema

## `providers` (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 = {
    "npiNumbers": [
        "1760438840",
        "1104130236"
    ],
    "taxonomyDescription": "Internal Medicine",
    "state": "TX",
    "city": "HOUSTON",
    "enumerationType": "ANY",
    "maxRecordsPerQuery": 50,
    "onlyNewOrUpdated": true,
    "includeRawRecord": false
};

// Run the Actor and wait for it to finish
const run = await client.actor("praise-most-high/nppes-npi-registry-lookup").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 = {
    "npiNumbers": [
        "1760438840",
        "1104130236",
    ],
    "taxonomyDescription": "Internal Medicine",
    "state": "TX",
    "city": "HOUSTON",
    "enumerationType": "ANY",
    "maxRecordsPerQuery": 50,
    "onlyNewOrUpdated": True,
    "includeRawRecord": False,
}

# Run the Actor and wait for it to finish
run = client.actor("praise-most-high/nppes-npi-registry-lookup").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 '{
  "npiNumbers": [
    "1760438840",
    "1104130236"
  ],
  "taxonomyDescription": "Internal Medicine",
  "state": "TX",
  "city": "HOUSTON",
  "enumerationType": "ANY",
  "maxRecordsPerQuery": 50,
  "onlyNewOrUpdated": true,
  "includeRawRecord": false
}' |
apify call praise-most-high/nppes-npi-registry-lookup --silent --output-dataset

```

## MCP server setup

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

```

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/23YEAPREp1SYnR8zA/builds/xo0XZNHh9lud5vu8q/openapi.json
