# Healthgrades Scraper - Doctor Ratings, NPI & Review Monitor (`neverempty/healthgrades-scraper`) Actor

For reputation dashboards, practice-growth tools and provider directories: Healthgrades doctors by specialty and city or profile URL, with star rating, rating count, NPI checked in the US NPI Registry, address, phone, hospitals, insurance and latest reviews. Monitor returns only rating changes.

- **URL**: https://apify.com/neverempty/healthgrades-scraper.md
- **Developed by:** [NeverEmpty](https://apify.com/neverempty) (community)
- **Categories:** Lead generation, Automation, Developer tools
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $11.00 / 1,000 doctor returneds

This Actor is paid per event. You are not charged for the Apify platform usage, but only a fixed price for specific events.
Since this Actor supports Apify Store discounts, the price gets lower the higher subscription plan you have.

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

## Healthgrades Scraper - Doctor Ratings, NPI & Review Monitor

Get **Healthgrades doctor profiles as clean JSON**: star rating and number of ratings, specialty, NPI, practice name, address and phone, hospital affiliations, accepted insurance carriers and plans, education, languages and the latest patient reviews. Search by **specialty and city or ZIP** (`Cardiology | New York, NY`) or paste **doctor profile URLs**. Turn on **Monitor** and scheduled runs return **only doctors whose rating or review count changed** (with the previous values), so a reputation dashboard, a practice-growth tool or a provider directory does not re-download and pay for the same doctors every day.

- **Review change monitor.** `onlyChanges` returns doctors whose Healthgrades star rating or number of ratings changed since the last run of the same watch, plus doctors that newly appear in a search, with `previousRating`, `previousRatingCount`, `ratingChange` and `ratingCountChange`. For searches, unchanged doctors are compared on the result page and their profiles are not even opened.
- **Checked against the official NPI Registry.** Each doctor's NPI is looked up in the US government NPPES registry and the row gets what Healthgrades does not show: registry status (active or deactivated), the registered name and whether the surname matches, primary taxonomy and code, license number and license state, enumeration and last-update dates.
- **Full profile by default.** Hospitals, insurance plans, education, board certifications, languages and up to 10 latest reviews (rating, date, author, text) come from each doctor's profile page.
- **Nothing guessed.** A doctor with no ratings has `ratingCount: 0` and `rating: null`, not a made-up 0-star rating. Values Healthgrades does not show are `null`.
- **No charge when Healthgrades cannot be read.** Missing profiles, empty searches and pages that could not be read come back as free rows that say why.

Unofficial. Reads the public Healthgrades pages (`healthgrades.com/physician/...`, `/dentist/...`, `/providers/...` and `/usearch`), the same pages a person sees without logging in. Only public profile information; no login, no cookies, no API key.

### What you get

One row per doctor. Example (production run on 2026-09-24, default input "Family Medicine | Austin, TX"; insurance lists and reviews shortened here: the row had 26 carriers and 3 reviews):

```json
{
  "status": "ok",
  "changeType": null,
  "healthgradesId": "2SGV3",
  "name": "Adriana Guerra",
  "displayName": "Dr. Adriana Guerra, MD",
  "credentials": "MD",
  "npi": "1104029735",
  "primarySpecialty": "Family Medicine",
  "specialties": [
    "Family Medicine"
  ],
  "rating": 4.7,
  "ratingCount": 37,
  "commentCount": 10,
  "gender": "female",
  "age": 50,
  "yearsOfExperience": null,
  "acceptsNewPatients": true,
  "telehealth": true,
  "isPatientFavorite": false,
  "languages": [
    "English",
    "Spanish"
  ],
  "practiceName": "Children's Medical Group PA",
  "address": "711 W 38th St Ste G2",
  "city": "Austin",
  "state": "TX",
  "zip": "78705",
  "phone": "(512) 910-3800",
  "fax": "(512) 824-0152",
  "latitude": 30.30359,
  "longitude": -97.74,
  "officeCount": 1,
  "hospitals": [
    {
      "name": "St. David's Medical Center",
      "city": "Austin",
      "state": "TX",
      "healthgradesUrl": "https://www.healthgrades.com/hospital/st-davids-medical-center-222089"
    }
  ],
  "hospitalCount": 1,
  "insuranceCarriers": [
    "Cigna",
    "Aetna",
    "Curative",
    "CareFirst Blue Cross Blue Shield"
  ],
  "insurancePlans": [
    {
      "payor": "Cigna",
      "plans": [
        "Cigna"
      ]
    },
    {
      "payor": "Aetna",
      "plans": [
        "Aetna"
      ]
    }
  ],
  "boardCertifications": [],
  "education": [
    {
      "type": "Residency Hospital",
      "name": "University of Illinois College of Medicine",
      "year": 2007
    }
  ],
  "latestReviews": [
    {
      "rating": 5,
      "date": "2024-11-10",
      "author": "RDV",
      "text": "Dr. Guerra is an excellent physician who really listens to patients and takes the time to consider all treatment options. She is brilliant and friendly. I highly recommend her."
    }
  ],
  "healthgradesVerifiedAt": "2026-09-15",
  "photoUrl": "https://photos.healthgrades.com/img/prov/2/S/G/2SGV3_w120h160_v4100.jpg?name=Dr.%20Adriana%20Guerra%2C%20MD",
  "profileUrl": "https://www.healthgrades.com/physician/dr-adriana-guerra-2sgv3",
  "previousRating": null,
  "previousRatingCount": null,
  "ratingChange": null,
  "ratingCountChange": null,
  "previousCheckedAt": null,
  "profileDetailsRead": true,
  "npiRegistryChecked": true,
  "npiRegistryFound": true,
  "npiRegistryStatus": "active",
  "npiRegistryName": "ADRIANA GUERRA",
  "npiNameMatches": true,
  "npiPrimaryTaxonomy": "Family Medicine",
  "npiTaxonomyCode": "207Q00000X",
  "npiLicenseNumber": "N3132",
  "npiLicenseState": "TX",
  "npiEnumerationDate": "2007-06-10",
  "npiLastUpdated": "2013-11-26",
  "target": "Family Medicine | Austin, TX",
  "watchName": null,
  "checkedAt": "2026-09-24T12:16:08.425Z"
}
```

Checked against the Healthgrades profile pages in a browser right after the run: 5 of 5 doctors had the same name, star rating and number of ratings ("4.7 Star Rating Based on 37 reviews"), NPI, phone, address and hospital on the page.

The NPI Registry columns are what Healthgrades does not have. Example from the same kind of run: Healthgrades lists Dr. Prateek Baghel under Cardiology, while the NPI Registry lists his primary taxonomy as "Student in an Organized Health Care Education/Training Program" (code 390200000X) with no license, which is the kind of mismatch the registry check is for.

| Column | Meaning |
|---|---|
| `status` | `ok` for a doctor row. Other values are free rows that say why nothing was returned (below) |
| `changeType` | `first-check` (first run of this watch), `new` (not seen by this watch before), `reviews-added`, `reviews-removed`, `rating-changed` (same number of ratings, different average) or `unchanged`. `null` when neither `onlyChanges` nor `watchName` is set (a one-off run compares nothing and remembers nothing) |
| `healthgradesId`, `name`, `displayName`, `credentials` | Healthgrades' provider ID, the name, the name as shown ("Dr. Adriana Guerra, MD") and the credentials |
| `npi` | The NPI number shown on Healthgrades |
| `primarySpecialty`, `specialties` | Specialties as Healthgrades lists them |
| `rating`, `ratingCount`, `commentCount` | Star rating (1-5), number of ratings, number of written comments (search results only) |
| `previousRating`, `previousRatingCount`, `ratingChange`, `ratingCountChange`, `previousCheckedAt` | What this watch saw last time and the difference. Null on a first check or a new doctor |
| `gender`, `age`, `yearsOfExperience`, `languages` | As shown on the profile or in the search result |
| `acceptsNewPatients`, `telehealth`, `isPatientFavorite` | Healthgrades' flags |
| `practiceName`, `address`, `city`, `state`, `zip`, `phone`, `fax`, `latitude`, `longitude`, `officeCount` | Primary office |
| `hospitals`, `hospitalCount` | Affiliated hospitals (name, city, state, Healthgrades link) |
| `insuranceCarriers`, `insurancePlans` | Accepted insurance carriers, and each carrier's plans (profile only) |
| `boardCertifications`, `education` | As listed on the profile |
| `latestReviews` | Newest patient reviews on the profile (rating, date, author, text), up to `maxReviewsPerDoctor` |
| `healthgradesVerifiedAt`, `photoUrl`, `profileUrl` | When Healthgrades last verified the profile, the photo (null when Healthgrades shows only a silhouette) and the profile link |
| `profileDetailsRead` | `true` when the profile page was read; `false` for search-only rows (then profile-only columns are `null`, meaning "not read", not "none") |
| `npiRegistryChecked`, `npiRegistryFound`, `npiRegistryStatus`, `npiRegistryName`, `npiNameMatches` | Result of the NPPES lookup of this NPI |
| `npiPrimaryTaxonomy`, `npiTaxonomyCode`, `npiLicenseNumber`, `npiLicenseState`, `npiEnumerationDate`, `npiLastUpdated` | From the NPPES registry |
| `target`, `watchName`, `checkedAt` | The search or URL this row came from, your watch name, and the time of the check |
| `note` | Free rows only: why nothing (or not everything) was returned |

#### Free rows (not charged)

| `status` | When |
|---|---|
| `no-results` | Healthgrades shows no doctors for this search |
| `no-change` | Monitor on: no doctor changed since the last run (one row per search, one for all profile URLs) |
| `not-found` | Healthgrades has no page at this URL (the doctor was removed or the URL is wrong) |
| `bad-input` | The input could not be used (for example a search without a location) |
| `unreadable` | The page could not be read, even after asking again from other IP addresses. Nothing is remembered, so a later run returns it |
| `blocked` | Healthgrades showed a check page. This Actor does not solve or bypass check pages; it stops |
| `budget-reached` | The run hit the maximum total charge you set. Doctors not returned are not remembered, so the next monitor run returns them |

### Input

| Field | Type | Default | Description |
|---|---|---|---|
| `searches` | list of strings | - | `specialty, condition or name | city, state or ZIP`, for example `Cardiology | New York, NY`, `Dentistry | 60605`. The location is required. Up to 20. With no searches and no URLs, the example search `Family Medicine | Austin, TX` runs |
| `startUrls` | list of URLs | - | Doctor profile URLs (`/physician/...`, `/dentist/...`, `/providers/...`) or search URLs (`/usearch?what=...&where=...`). Up to 1,000 profiles |
| `maxDoctorsPerSearch` | integer 1-1,000 | 20 | How many doctors to check per search, in Healthgrades' order (20 per page) |
| `includeProfileDetails` | boolean | true | For search results, also read each returned doctor's profile (hospitals, insurance plans, education, reviews) |
| `maxReviewsPerDoctor` | integer 0-10 | 3 | Newest reviews to include per doctor |
| `checkNpiRegistry` | boolean | true | Look up each NPI in the official NPPES NPI Registry |
| `onlyChanges` | boolean | false | Monitor: return only doctors whose rating or number of ratings changed, and new doctors |
| `watchName` | string | - | Runs with the same watch name share what they have seen; give two schedules different names |
| `resetMonitoringState` | boolean | false | Forget what this watch remembered, so the run is a first check again |

### Examples

Cardiologists in Manhattan with full profiles:

```json
{ "searches": ["Cardiology | New York, NY"], "maxDoctorsPerSearch": 40 }
```

Daily review monitor for your own doctors (schedule this; each run returns only the doctors whose rating or number of ratings changed):

```json
{ "startUrls": ["https://www.healthgrades.com/physician/dr-adriana-guerra-2sgv3", "https://www.healthgrades.com/dentist/dr-angel-frazier-8n1gfif952"], "onlyChanges": true, "watchName": "my-practice" }
```

New and re-rated dermatologists in three cities, search page only (cheapest):

```json
{ "searches": ["Dermatology | Chicago, IL", "Dermatology | Houston, TX", "Dermatology | 94103"], "maxDoctorsPerSearch": 100, "includeProfileDetails": false, "onlyChanges": true, "watchName": "derm-3-cities" }
```

### How monitoring works

The Actor remembers, per `watchName`, each doctor's star rating and number of ratings the last time it saw them (in a named key-value store in your account). On the next run it compares: a doctor not seen before is `new`; more ratings is `reviews-added`, fewer is `reviews-removed`, the same number with a different average is `rating-changed`. With `onlyChanges` on, only changed and new doctors are returned and charged; a search with nothing to report comes back as one free `no-change` row. **The run start fee is still charged on a run with no changes** (it pays for the check). For searches, the comparison uses the rating shown on the search results page, so unchanged doctors' profile pages are not opened. Doctors that could not be read or returned are not remembered, so the next run returns them. Two runs with the same watch name at the same moment can overwrite each other's memory, so do not overlap schedules of one watch.

### Pricing

Pay per event: a small start fee per run that returned at least one doctor (in monitor mode: per run that read and compared at least one doctor, even when nothing changed), plus a fee per doctor row returned. Free rows are never charged. A run where Healthgrades could not be read, a search with no doctors, or a URL with no profile charges nothing. If your maximum total charge for a run has no room for the start fee plus one doctor, the run requests nothing and charges nothing.

### Limits

- Ratings change over time; a row is what Healthgrades showed at `checkedAt`.
- Search results follow Healthgrades' own order and its limit of 20 doctors per page. A search needs a location; without one Healthgrades answers for the place of the IP address.
- Healthgrades shows up to 10 newest reviews on a profile page; older reviews are not read.
- Healthgrades may show a check page to automated traffic; the Actor does not bypass it and returns a free `blocked` row instead.
- Public profile information only. Do not use the output to contact patients or to verify credentials: Healthgrades itself says its data is not sufficient for credential verification.

### Support

Found a doctor where the output differs from the Healthgrades page? Open an issue on the Issues tab with the run ID and it will be looked at.

# Actor input Schema

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

Healthgrades searches, one per line, written as specialty, condition or doctor name, then "|", then a city and state or a ZIP code. Examples: "Cardiology | New York, NY", "Dentistry | 60605", "Adriana Guerra | Austin, TX". The location is required: without it Healthgrades picks the place from the IP address. Up to 20 searches per run. If both this and Start URLs are empty, the example search "Family Medicine | Austin, TX" is used.

## `startUrls` (type: `array`):

Healthgrades doctor profile URLs (…/physician/…, …/dentist/… or …/providers/…), one per line, for example https://www.healthgrades.com/physician/dr-adriana-guerra-2sgv3. Healthgrades search URLs (…/usearch?what=…\&where=…) are also accepted. Up to 1,000 profiles per run. Every profile is read in full.

## `maxDoctorsPerSearch` (type: `integer`):

How many doctors to check from each search, in Healthgrades' order (20 per result page). In monitor mode only the changed ones are returned and charged. 1 to 1,000.

## `includeProfileDetails` (type: `boolean`):

For search results, also read each returned doctor's profile page to add hospital affiliations, insurance plans, education, board certifications, languages and the latest patient reviews. When off, rows have only what the search page shows (rating, rating count, specialty, office, phone, insurance carriers) and the profile-only columns are null. Doctor URLs from Start URLs are always read in full.

## `maxReviewsPerDoctor` (type: `integer`):

How many of the newest patient reviews shown on the profile page to include per doctor (rating, date, author and text). 0 to 10; Healthgrades shows up to 10 on the page.

## `checkNpiRegistry` (type: `boolean`):

Look up each doctor's NPI number in the US government NPPES NPI Registry and add columns Healthgrades does not have: registry status (active or deactivated), the registered name and whether the surname matches, primary taxonomy and code, license number and state, enumeration date and last update.

## `onlyChanges` (type: `boolean`):

Return only doctors whose Healthgrades star rating or number of ratings changed since the last run with the same watch name, plus doctors that newly appear in a search. The first run returns everyone as the starting point. For searches, unchanged doctors are compared on the search page and their profiles are not opened. A run in which nothing changed returns a free row saying so and charges only the run start fee.

## `watchName` (type: `string`):

Name of the remembered state used to compare runs (letters, digits, dot, dash, underscore; up to 40). Setting it (or turning on monitor mode) fills changeType and the previous rating columns. Use a different name for each list of doctors you track on its own schedule. With monitor mode on and no name, the name "default" is used.

## `resetMonitoringState` (type: `boolean`):

Start this watch over: forget the remembered ratings before this run, so every doctor is returned as a first check.

## Actor input object example

```json
{
  "searches": [
    "Family Medicine | Austin, TX"
  ],
  "startUrls": [],
  "maxDoctorsPerSearch": 20,
  "includeProfileDetails": true,
  "maxReviewsPerDoctor": 3,
  "checkNpiRegistry": true,
  "onlyChanges": false,
  "resetMonitoringState": false
}
```

# Actor output Schema

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

One row per doctor: name, credentials, specialties, star rating and number of ratings, change since the last check (changeType, previousRating, previousRatingCount), NPI with the official NPI Registry status, license and taxonomy, practice address and phone, hospital affiliations, insurance carriers and plans, education, languages and the latest patient reviews. A search with no doctors, no change, a missing profile, a refused request or a run that hit its maximum charge comes back as a free row that says why.

# 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": [
        "Family Medicine | Austin, TX"
    ],
    "startUrls": []
};

// Run the Actor and wait for it to finish
const run = await client.actor("neverempty/healthgrades-scraper").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": ["Family Medicine | Austin, TX"],
    "startUrls": [],
}

# Run the Actor and wait for it to finish
run = client.actor("neverempty/healthgrades-scraper").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": [
    "Family Medicine | Austin, TX"
  ],
  "startUrls": []
}' |
apify call neverempty/healthgrades-scraper --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,neverempty/healthgrades-scraper"
        }
    }
}
```

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/51aZdxmFyfsvKGYoP/builds/t3BUfm45lGLolqH2z/openapi.json
