Back to Blog
TechnicalMarch 13, 2026

NPI Lookup in 5 Minutes: Search 9 Million Providers From Your App

Every healthcare app needs provider data. Here's how to search, look up, and filter the NPI registry from TypeScript — with code you can ship today.

NPI provider search tutorial

Every healthcare app eventually needs to answer the same question: who is this provider? Whether you're verifying a referral, populating a directory, or matching claims to clinicians, you need NPI data. The National Provider Identifier registry has 9 million+ records — and querying it directly from CMS is an exercise in patience and XML parsing.

This tutorial shows how to search, look up, and filter NPI data using FHIRfly's API and TypeScript SDK. Working code throughout, no CMS SOAP endpoints required.

What's in an NPI Record?

Every healthcare provider in the US — individual clinicians and organizations — gets a unique 10-digit NPI from CMS. The NPI record includes:

  • Identity: Name, credentials, entity type (individual vs. organization)
  • Taxonomies: Specialty classifications from the NUCC taxonomy (e.g., 207X00000X = Orthopedic Surgery)
  • Addresses: Practice location, mailing address, secondary locations
  • Status: Active/deactivated, enumeration date, last update

FHIRfly enriches the raw NPPES data with NUCC taxonomy display names and updates daily.

Setup

npm install @fhirfly-io/terminology
import { Fhirfly } from "@fhirfly-io/terminology";

const fhirfly = new Fhirfly({ apiKey: process.env.FHIRFLY_API_KEY! });

Look Up a Single Provider

If you have an NPI, pull the full record:

const result = await fhirfly.npi.lookup("1750638235", {
  shape: "standard",
  include: ["display"],
});

console.log(result.data.display.full_name);
// "John R. Smith, MD"

console.log(result.data.taxonomies[0].classification);
// "Orthopedic Surgery"

console.log(result.data.practice_address);
// { line1: "123 Medical Center Dr", city: "Denver", state: "CO", postal: "80202" }

Three response shapes control how much data you get back:

ShapeFieldsBest for
compactName, specialty, location, active statusSearch results, lists
standard+ Structured name, taxonomies, address, datesDetail views, verification
full+ Secondary locations, mailing address, deactivation history, identifiersCompliance, deep analysis

The include: ["display"] option adds pre-formatted strings — useful when you need display-ready text without assembling it yourself.

Search Providers

The search endpoint supports full-text queries with filters:

const results = await fhirfly.npi.search(
  { q: "cardiology", state: "CA", entity_type: "individual" },
  { limit: 10 }
);

console.log(`Found ${results.total} cardiologists in California`);

for (const provider of results.items) {
  console.log(`${provider.name} — ${provider.specialty} — ${provider.location}`);
}
// "Maria Garcia, MD — Cardiovascular Disease — Los Angeles, CA"
// "James Chen, DO — Interventional Cardiology — San Francisco, CA"
// ...

Search returns the compact shape by default — enough for list views without over-fetching.

Available Filters

Combine text search with any of these:

// Search by name and location
await fhirfly.npi.search({ name: "smith", state: "TX", city: "Houston" });

// Find organizations by specialty
await fhirfly.npi.search({
  organization: "medical center",
  state: "NY",
  entity_type: "organization",
});

// Exact taxonomy code match
await fhirfly.npi.search({ q: "physical therapy", taxonomy: "225100000X" });

// Filter by ZIP prefix
await fhirfly.npi.search({ specialty: "dermatology", postal_code: "90210" });

Every search requires at least one text parameter (q, name, organization, or specialty). Filter-only queries return a 400 — this is intentional to prevent unbounded scans across 9 million records.

Pagination

let page = 1;
let hasMore = true;

while (hasMore) {
  const results = await fhirfly.npi.search(
    { specialty: "family medicine", state: "CO" },
    { limit: 50, page }
  );

  for (const provider of results.items) {
    await processProvider(provider);
  }

  hasMore = results.has_more;
  page++;
}

Pagination caps at 10,000 results (100 pages × 100 per page). The total_capped flag tells you if the real count exceeds this — tighten your filters if it does.

Facets

Search responses include facet counts for entity type and state, useful for building filter UIs:

const results = await fhirfly.npi.search({ q: "cardiology" });

console.log(results.facets.entity_type);
// { individual: 45200, organization: 3100 }

console.log(results.facets.state);
// { CA: 5200, NY: 4800, TX: 4100, FL: 3900, ... }

Batch Lookups

When you have a list of NPIs — from a claims file, a referral list, an enrollment spreadsheet — use the batch endpoint instead of N individual calls:

const response = await fhirfly.npi.lookupMany(
  ["1750638235", "1447440417", "0000000000"],
  { shape: "standard" }
);

for (const result of response.results) {
  if (result.status === "ok") {
    console.log(`${result.npi}: ${result.data.display?.full_name}`);
  } else {
    console.log(`${result.npi}: ${result.status} — ${result.error}`);
  }
}
// "1750638235: John R. Smith, MD"
// "1447440417: Valley Medical Group"
// "0000000000: not_found — NPI not found in NPPES"

Batch accepts up to 100 NPIs per request. Each result includes its own status, so partial failures don't break the whole call.

REST API (Without the SDK)

The SDK wraps a straightforward REST API. If you're working in Python, Go, or another language:

# Single lookup
curl -H "x-api-key: $FHIRFLY_API_KEY" \
  "https://api.fhirfly.io/v1/npi/1750638235?shape=standard&include=display"

# Search
curl -H "x-api-key: $FHIRFLY_API_KEY" \
  "https://api.fhirfly.io/v1/npi/search?q=cardiology&state=CA&limit=10"

# Batch
curl -X POST -H "x-api-key: $FHIRFLY_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"codes": ["1750638235", "1447440417"]}' \
  "https://api.fhirfly.io/v1/npi/_batch?shape=standard"

Data Freshness

FHIRfly checks for NPPES updates daily and applies them as soon as CMS publishes new data — typically weekly. Every response includes provenance metadata so you know exactly what you're working with:

const result = await fhirfly.npi.lookup("1750638235", { shape: "full" });

console.log(result.meta.source.source_update_cadence);
// "weekly"

console.log(result.meta.source.fhirfly_updated_at);
// "2026-03-13T06:00:00.000Z"

console.log(result.meta.legal.source_name);
// "CMS NPPES with NUCC Taxonomy"

NPI data is public domain — no licensing restrictions on what you build with it.

Key Takeaways

  • Three shapes (compact, standard, full) let you fetch only the data you need
  • Search requires a text query plus optional filters — no unbounded scans
  • Batch endpoint handles up to 100 NPIs per request with per-item status
  • Daily sync from CMS NPPES with provenance metadata on every response
  • Public domain data — no licensing restrictions

Next Steps

Grab an API key from the FHIRfly dashboard and try a search. The NPI API docs cover every parameter in detail, and the @fhirfly-io/terminology SDK handles auth, retries, and types out of the box.

Tags:npitutorialsdkprovider-directoryapi

Written by The FHIRfly Team — a collective of healthcare data experts, AI specialists, and industry veterans building better clinical coding APIs.

Ready to get started?

Try FHIRfly free — no credit card required. Get instant access to clinical coding APIs.