NPI Registry API: How to Look Up Any US Healthcare Provider Programmatically
CMS publishes the National Provider Identifier registry as a free API. Here is how to search by provider name, specialty, location, and NPI number — and what the data contains.
The actor referenced in this article is live on Apify. Pay only for results delivered.
Every US healthcare provider who bills insurance has a National Provider Identifier. It is a 10-digit number assigned by CMS, required for Medicare and Medicaid billing, and used as the universal identifier across payers, networks, and health records systems.
CMS publishes the full NPI registry as a free, public API. No authentication is required. You can search by name, specialty, organization, and geography, and the response contains the provider’s address, credential, taxonomy codes, and enumeration date.
This post covers what NPI numbers are, the difference between Type 1 and Type 2 NPIs, the NPPES API query structure, and Python code for searching providers by specialty and location, bulk downloading all active providers in a state, and validating provider lists.
TL;DR: The NPPES NPI Registry API is at
https://npiregistry.cms.hhs.gov/api/. No auth required. Returns JSON with provider demographics, addresses, and taxonomy codes. Pagination vianumber(limit) andskipparameters. The full registry is also available as a bulk CSV download at CMS, updated monthly.
What NPI Numbers Are
The National Plan and Provider Enumeration System (NPPES) was established in 2004. Every provider who transmits health information electronically in HIPAA-covered transactions must have an NPI.
There are two types:
Type 1 NPI (Individual). Assigned to individual human healthcare providers: physicians, nurses, dentists, therapists, chiropractors, and others. Each individual gets exactly one Type 1 NPI for life, regardless of where they practice or how many organizations they work for.
Type 2 NPI (Organization). Assigned to organizations: hospitals, clinics, group practices, pharmacies, labs. An organization can have multiple Type 2 NPIs if it operates in multiple locations or has distinct subparts (a hospital with a separate skilled nursing facility, for example).
The NPI itself does not tell you specialty. Specialty comes from the provider’s declared taxonomy codes, which follow the Healthcare Provider Taxonomy Code Set maintained by NUCC.
The NPPES API
Base URL: https://npiregistry.cms.hhs.gov/api/
Method: GET, JSON response. No API key required.
Key query parameters:
| Parameter | Description | Example |
|---|---|---|
number | NPI number (exact match) | number=1234567890 |
enumeration_type | NPI-1 (individual) or NPI-2 (org) | enumeration_type=NPI-1 |
first_name | Provider first name (partial match) | first_name=James |
last_name | Provider last name (partial match) | last_name=Smith |
organization_name | Organization name (partial match) | organization_name=Mount+Sinai |
taxonomy_description | Specialty description (partial match) | taxonomy_description=Cardiology |
city | City | city=Chicago |
state | 2-letter state code | state=IL |
postal_code | ZIP code (5 or 9 digit) | postal_code=60601 |
country_code | 2-letter country code | country_code=US |
limit | Results per page (max 200) | limit=200 |
skip | Pagination offset | skip=400 |
version | API version | version=2.1 |
Always include version=2.1. Older versions return a different response structure.
Response Structure
A typical response looks like this:
{
"result_count": 1,
"results": [
{
"created_epoch": "1197835200000",
"enumeration_type": "NPI-1",
"last_updated_epoch": "1623888000000",
"number": "1234567890",
"addresses": [
{
"country_code": "US",
"country_name": "United States",
"address_purpose": "LOCATION",
"address_type": "DOM",
"address_1": "123 Medical Dr",
"city": "Chicago",
"state": "IL",
"postal_code": "606015678",
"telephone_number": "312-555-0100",
"fax_number": "312-555-0101"
},
{
"address_purpose": "MAILING",
"address_1": "PO Box 1000",
"city": "Chicago",
"state": "IL"
}
],
"taxonomies": [
{
"code": "207RC0000X",
"desc": "Cardiovascular Disease",
"primary": true,
"state": "IL",
"license": "036-075890"
}
],
"basic": {
"first_name": "James",
"last_name": "Smith",
"credential": "MD",
"gender": "M",
"enumeration_date": "2007-12-17",
"last_updated": "2021-06-17",
"status": "A",
"name_prefix": "DR."
}
}
]
}
Key fields to extract:
number: the NPI itselfenumeration_type: NPI-1 or NPI-2basic.credential: MD, DO, NP, RN, etc.basic.status:A(active) orD(deactivated)taxonomies: list of specialty codes; the one withprimary: trueis the declared primary specialtyaddresses: separate LOCATION and MAILING addresses;address_purpose = "LOCATION"is the practice location
Python: Searching All Cardiologists in Chicago
import requests
import pandas as pd
import time
NPPES_BASE = "https://npiregistry.cms.hhs.gov/api/"
def search_nppes(params, max_results=1000):
"""
Search the NPPES registry with pagination.
Returns a list of provider dicts.
"""
base_params = {"version": "2.1", "limit": 200}
base_params.update(params)
all_results = []
skip = 0
while len(all_results) < max_results:
base_params["skip"] = skip
response = requests.get(NPPES_BASE, params=base_params)
response.raise_for_status()
data = response.json()
results = data.get("results", [])
if not results:
break
all_results.extend(results)
result_count = data.get("result_count", 0)
print(f"Fetched {len(all_results)} / {min(result_count, max_results)}")
if len(all_results) >= result_count:
break
skip += 200
time.sleep(0.2)
return all_results[:max_results]
def flatten_provider(p):
"""Flatten a provider record to a single dict for dataframe use."""
basic = p.get("basic", {})
# Get practice location address
location_addr = next(
(a for a in p.get("addresses", []) if a.get("address_purpose") == "LOCATION"),
{}
)
# Get primary taxonomy
primary_tax = next(
(t for t in p.get("taxonomies", []) if t.get("primary")),
p.get("taxonomies", [{}])[0] if p.get("taxonomies") else {}
)
return {
"npi": p.get("number"),
"type": p.get("enumeration_type"),
"status": basic.get("status"),
"first_name": basic.get("first_name", ""),
"last_name": basic.get("last_name", ""),
"org_name": basic.get("organization_name", ""),
"credential": basic.get("credential", ""),
"gender": basic.get("gender", ""),
"enumerated": basic.get("enumeration_date", ""),
"last_updated": basic.get("last_updated", ""),
"specialty_code": primary_tax.get("code", ""),
"specialty_desc": primary_tax.get("desc", ""),
"address_1": location_addr.get("address_1", ""),
"city": location_addr.get("city", ""),
"state": location_addr.get("state", ""),
"zip": location_addr.get("postal_code", ""),
"phone": location_addr.get("telephone_number", ""),
}
# Search: all individual cardiologists in Chicago
chicago_cardiologists = search_nppes({
"enumeration_type": "NPI-1",
"taxonomy_description": "Cardiology",
"city": "Chicago",
"state": "IL",
"country_code": "US",
}, max_results=500)
df = pd.DataFrame([flatten_provider(p) for p in chicago_cardiologists])
df = df[df["status"] == "A"] # active providers only
print(f"Active cardiologists in Chicago: {len(df)}")
print(df[["npi", "first_name", "last_name", "credential", "address_1", "zip"]].head(10).to_string(index=False))
Python: Bulk Downloading All Active NPIs in a State
def bulk_state_download(state_code, enumeration_type="NPI-1", max_results=50000):
"""
Download all active providers in a state.
Note: NPPES API caps results at ~1200 per query due to pagination limits.
For complete state datasets, use the monthly bulk CSV download from CMS.
This function is appropriate for subsets (by specialty + state).
"""
return search_nppes({
"enumeration_type": enumeration_type,
"state": state_code,
"country_code": "US",
}, max_results=max_results)
# All NPI-2 organizations in Texas
tx_orgs = bulk_state_download("TX", enumeration_type="NPI-2", max_results=5000)
tx_df = pd.DataFrame([flatten_provider(p) for p in tx_orgs])
print(f"Organizations in Texas: {len(tx_df)}")
For truly complete state downloads, the NPPES API hits practical limits around 1,200 results for broad queries. For a full state-level dataset, download the monthly bulk file from https://download.cms.gov/nppes/NPI_Files.html. The monthly full replacement file is a CSV with all active NPIs and is about 8GB uncompressed.
Python: Validating a Provider List Against the Registry
def validate_provider_list(npi_list):
"""
Validate a list of NPI numbers against the live registry.
Returns a dict mapping each NPI to its status and key fields.
"""
results = {}
for npi in npi_list:
response = requests.get(NPPES_BASE, params={"version": "2.1", "number": npi})
data = response.json()
providers = data.get("results", [])
if not providers:
results[npi] = {"found": False, "status": None}
else:
p = providers[0]
results[npi] = {
"found": True,
"status": p.get("basic", {}).get("status"),
"name": f"{p.get('basic', {}).get('first_name', '')} {p.get('basic', {}).get('last_name', '')}".strip() or p.get("basic", {}).get("organization_name", ""),
"credential": p.get("basic", {}).get("credential", ""),
"type": p.get("enumeration_type"),
}
time.sleep(0.1)
return results
# Check a list of NPIs from a billing spreadsheet
npis_to_check = ["1234567890", "0987654321", "1111111112"]
validation = validate_provider_list(npis_to_check)
for npi, info in validation.items():
status = info.get("status")
print(f"NPI {npi}: found={info['found']}, status={status}, name={info.get('name','')}")
Status A is active. Status D is deactivated. Claims submitted with a deactivated NPI will be rejected by payers.
Use Cases
Healthcare market research. Count providers by specialty and geography to identify underserved markets. Compare specialist density across ZIP codes or counties for healthcare access analysis.
Provider directory building. Pull all NPIs for a specialty within a defined geography and enrich with the taxonomy, address, and credential data from NPPES. This gives you a starter directory without manual data collection.
Billing validation. Before submitting claims or processing provider records, validate each NPI against the live registry. Flag deactivated NPIs, mismatched names, and wrong provider types before they cause claim rejections.
Referral network analysis. In conjunction with Medicare claims data (also public), NPI numbers let you map referral relationships between physicians. The NPI is the join key between NPPES demographic data and claims-level utilization data.
The NPI Registry healthcare scraper handles paginated bulk pulls, scheduled monthly refreshes, and output normalization for teams building provider databases or compliance tools on top of NPPES data.
Try the scraper referenced in this article — live on Apify, pay only for results.
Open npi-registry-healthcare on Apify →How to Scrape AmbitionBox Company Reviews and Ratings
AmbitionBox is India largest employer review platform with 300,000 companies. Learn how to pull ratings, review counts, salary data, and dimension scores as structured JSON without any official API.
AliExpress Product Data API: Prices, Ratings, and Orders in Python
AliExpress affiliate API has restricted coverage. Learn how to scrape AliExpress product listings for prices, ratings, order counts, and seller data as structured JSON — no affiliate approval needed.
ClinicalTrials.gov API v2: How to Search 500,000 Studies and Track Trial Status
ClinicalTrials.gov upgraded to a v2 REST API in 2024. Here is how to use it, what changed from v1, and how to build automated trial monitoring pipelines in Python.