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. 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.
Python: The Same Lookups Through the Managed Scraper
If you would rather not maintain the pagination and flattening code above, the NPI Registry scraper takes the same filters as plain inputs (taxonomyDescription, state, city, postalCode, organizationName, firstName, lastName, npiNumber) and returns rows that are already flat: npi, type, name, credential, status, primary_specialty, taxonomy_code, license, license_state, address, city, state, postal_code, phone, enumeration_date and last_updated. It picks the primary taxonomy and the LOCATION address for you, the same way flatten_provider does. Two small differences: status comes back as Active rather than A, and type reads Individual or Organization instead of NPI-1 or NPI-2.
import os
import pandas as pd
from apify_client import ApifyClient
apify = ApifyClient(os.environ["APIFY_TOKEN"])
def find_providers(max_results=200, **filters):
"""Look up providers in the CMS NPI Registry via the managed scraper."""
run = apify.actor("themineworks/npi-registry-healthcare").call(
run_input={**filters, "maxResults": max_results}
)
rows = apify.dataset(run["defaultDatasetId"]).iterate_items()
return [r for r in rows if "_type" not in r] # drop the run summary row
# Every cardiology provider in Massachusetts, counted by city
ma = pd.DataFrame(find_providers(taxonomyDescription="Cardiology", state="MA", max_results=1200))
print(len(ma))
print(ma.groupby("city").size().nlargest(10))
# Check one NPI from a billing file
print(find_providers(npiNumber="1234567890", max_results=1))
The same 1,200-result ceiling applies, since it comes from the registry. For a whole state, split the pull by city or by specialty, or use the monthly bulk file.
Filtering by taxonomyDescription and state is the quickest way to size a provider market or plan sales territories. The registry covers providers, not patients, so there is no protected health information in any of this. It also changes slowly, so a monthly refresh of a provider list is usually enough.
The function above also works as a tool for an LLM agent. Give it a short description (“Look up US healthcare providers by specialty, state, city or organization”) and tell the model to cite the NPI for every provider it mentions, so each answer can be checked against the registry.
Tracking One Organization Across Healthcare Sources
The NPI Registry answers who the providers are and where they practise. A few other public sources answer the questions that usually come next:
- ClinicalTrials Scraper: what a sponsor has in its clinical pipeline, by phase and status, with trial locations.
- FDA Recalls Scraper: drug, device and food recalls, filterable by recalling firm and classification.
- Healthcare Provider Intelligence: NPI identity joined with CMS Open Payments and Medicare Part D prescribing volume in one row.
- CMS Hospital Quality, Medicare Part D and FDA 510(k) for facility quality, drug spending and device clearances.
Running one company name through the organization search here, the sponsor filter on trials and the recalling-firm filter on recalls gives you its provider footprint, pipeline and recall history side by side.
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.
Explore the scraper referenced in this article: inputs, outputs, and pricing, then run it on Apify.
FDA Recall Data API: How to Monitor Drug, Device, and Food Recalls Programmatically
openFDA serves drug, device and food recalls plus adverse events and 510(k) clearances over a free REST API. How the endpoints and fields work.
CourtListener API: How to Search US Court Records and Case Law Programmatically
CourtListener exposes 10M+ court opinions and dockets via a free REST API. Here is how to query it, what the rate limits actually are, and when a scraper is faster.
Federal Register API: How to Track US Rules, Proposed Rules, and Executive Orders
The Federal Register publishes every US executive action, proposed rule, and final rule via a REST API. Here is how to query it and what the data contains.
India Government Data API: How to Pull Any data.gov.in Dataset Without the Documentation Confusion
data.gov.in has 10,000+ datasets including mandi prices, foreign trade, and census data. The OGD API works but has quirks that are not documented anywhere.