# Vitals.com Doctor \[$1.99/1k] Scraper · NPI, Specialty & Reviews (`memo23/vitals-scraper`) Actor

\[$1.99/1k] Scrape Vitals.com US doctor & dentist profiles — by specialty/location browse or individual profile URLs. Returns name, NPI, specialty, education, licenses, practice locations, phone, ratings & review counts, languages, insurance and bio in clean JSON/CSV. Pure HTTP, no browser

- **URL**: https://apify.com/memo23/vitals-scraper.md
- **Developed by:** [Muhamed Didovic](https://apify.com/memo23) (community)
- **Categories:** Lead generation, Agents, AI
- **Features:** Limited permissions
- **Stats:** 41 total users, 8 monthly users, 100.0% runs succeeded, 1 bookmarks
- **User rating**: 5.00 out of 5 stars

## Pricing

from $1.99 / 1,000 results

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?

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

## Vitals.com Doctor Scraper — NPI, Specialty, Reviews & More

Scrape **Vitals.com — US doctor & dentist profiles** — browse by specialty/location or pass individual profile URLs. Every provider comes back in one clean schema with name, **NPI**, specialty, education, medical licenses, practice locations, phone, ratings, **every patient review** (stars, comment, date, per-question scores), languages, insurance and bio. Pure HTTP, no browser.

![How it works](https://raw.githubusercontent.com/muhamed-didovic/muhamed-didovic.github.io/main/assets/how-it-works-vitals.png)

### Why use this scraper

- **Provider-grade data** — name, degree, **National Provider Identifier (NPI)**, primary + all specialties, years of experience, gender, languages.
- **Education & licensing** — medical school / residency / fellowship history, and state licenses (type, state, expiry, status).
- **Practice locations** — practice name, address, city/state/zip, phone, lat/long for every office.
- **Ratings & patient reviews** — Vitals rating score, number of ratings, review count, and the full text of every patient review (star rating, comment, date, helpful votes, bedside manner / wait time / follow-up scores), plus telehealth, insurance, Medicare/Medicaid flags.
- **Pure HTTP, no browser** — reads Vitals' embedded `__INITIAL_STATE__` directly. Fast and cheap.

### Overview

Vitals embeds the full provider record in each profile page as a JSON island. The actor classifies each URL (browse / provider), walks specialty/location browse pages (~60 per page, `?page=N`), fans out to each provider, and normalises everything into a consistent row. Ideal for healthcare data teams, recruiters, medical-device/pharma sales, and provider-directory builders.

### Supported inputs

| Input URL shape | Example |
|---|---|
| **Specialty browse** | `vitals.com/cardiologists` |
| **Specialty + location** | `vitals.com/cardiologists/ny/new-york` |
| **Individual provider** | `vitals.com/doctors/{slug}` (or `/dentists/{slug}`) |
| **Old-style or tab links** | `http://www.vitals.com/doctors/Dr_First_Last.html`, `vitals.com/doctors/{slug}/reviews` |

> **Tip:** browse a specialty + city on vitals.com, then copy the URL from your browser's address bar — it drops straight into `startUrls`.

### Use cases

- **Healthcare data teams** — build / enrich provider directories keyed on NPI.
- **Medical-device & pharma sales** — target specialists by specialty, location, affiliation.
- **Recruiters** — source physicians by specialty, experience, location, languages.
- **Market research** — analyse provider density, ratings, and acceptance of new patients by region.

### How it works

1. You provide one or more Vitals URLs (browse or provider).
2. The actor classifies each URL and walks browse pages with `?page=N` pagination.
3. Each provider's `__INITIAL_STATE__` is parsed for the full structured record.
4. Patient reviews are paged in from the same review service the profile's "load more" button uses, newest first.
5. Rows are normalised to one schema and streamed to your dataset — JSON or CSV.

### Input configuration

| Field | Type | Default | Description |
|---|---|---|---|
| `startUrls` | array | — | vitals.com URLs (browse or provider) |
| `includeReviews` | boolean | `true` | Attach every patient review to the provider row |
| `maxReviewsPerProvider` | integer | `100` | Newest-first cap on reviews per provider |
| `flatten` | boolean | `true` | Flatten nested fields for CSV-friendly output |
| `maxItems` | integer | `10000` | Hard cap on rows collected |
| `maxConcurrency` | integer | `6` | Parallel profile fetches |

### Output samples

**Flattened (`flatten: true`, default):**

```json
{
  "portal": "vitals",
  "npi": "1568632859",
  "fullName": "Dmitriy N. Feldman",
  "degree": "MD",
  "gender": "M",
  "yearsOfExperience": 24,
  "primarySpecialty": "Cardiovascular Disease",
  "specialties_json": "[{\"name\":\"Cardiovascular Disease\",\"title\":\"Cardiologist\"}]",
  "city": "New York",
  "state": "NY",
  "phone": "(212) 555-0143",
  "ratingScore": 4.9,
  "reviewCount": 27,
  "education_json": "[{\"type\":\"Fellowship\",\"institution\":\"…\",\"year\":\"2016\"}]",
  "licenses_json": "[{\"type\":\"Medical Doctor\",\"state\":\"New York\",\"status\":\"Active\"}]",
  "languages_json": "[\"English\"]",
  "reviews_json": "[{\"reviewId\":\"43520347\",\"rating\":5,\"comment\":\"Smart, compassionate, good referrals.…\",\"reviewDate\":\"2025-07-05T14:53:23Z\",\"helpfulCount\":1,\"criteria\":{\"Had a good bedside manner\":1,\"Spent time with me\":1}}]",
  "isTelehealth": true,
  "acceptsNewPatients": true
}
```

### Key output fields

| Field | Description |
|---|---|
| `npi`, `providerId`, `entityId`, `vitalsGuid` | Identity (incl. National Provider Identifier) |
| `fullName`, `firstName`, `lastName`, `suffix`, `degree` | Name |
| `gender`, `yearsOfExperience` | Demographics |
| `primarySpecialty`, `specialties[]`, `expertise[]` | Specialties |
| `education[]` | School / residency / fellowship (type, institution, year) |
| `licenses[]` | State licenses (type, state, expiry, status) |
| `practiceLocations[]` | Practice name, address, city/state/zip, phone, lat/long |
| `phone` | Primary phone |
| `city`, `state`, `postcode`, `latitude`, `longitude` | Location |
| `ratingScore`, `numberOfRatings`, `reviewCount`, `vitalsScore` | Ratings |
| `reviews[]` | Patient reviews: `reviewId`, `rating`, `comment`, `reviewDate`, `updatedAt`, `helpfulCount`, `source`, `criteria` (per-question scores such as bedside manner, wait time in minutes) |
| `languages[]`, `acceptsNewPatients`, `isTelehealth` | Access |
| `hospitals[]`, `insurances[]`, `isMedicare`, `isMedicaid` | Affiliations / coverage |
| `bio`, `photoUrl`, `videoUrl` | Profile media |

### FAQ

**Does it include the NPI?**
Yes — the National Provider Identifier is on every provider row, plus state license details.

**Does it include the patient reviews themselves?**
Yes. Each provider row carries a `reviews` array with every written review (up to `maxReviewsPerProvider`, default 100), bundled into the row at no extra charge. Set `includeReviews` to false if you only need the profile.

**Can I scrape dentists too?**
Yes — `/dentists/...` browse and `/dentists/{slug}` profiles work the same as doctors.

**How many providers per browse page?**
Around 60, and the actor paginates with `?page=N` until `maxItems`.

### Support

Found a bug or need a field added? Open an issue on the actor's Apify Console page.

### Explore more scrapers

- **[Avvo Scraper](https://apify.com/memo23/avvo-scraper)** — US lawyer directory
- **[FindLaw Scraper](https://apify.com/memo23/findlaw-scraper)** — US legal directory
- **[Martindale Scraper](https://apify.com/memo23/martindale-scraper)** — US attorney directory

Full portfolio: [apify.com/memo23](https://apify.com/memo23)

### 🤖 For AI Agents & LLM Apps

Compact reference for AI agents calling this actor via the [Apify MCP server](https://mcp.apify.com) or the Apify API (actor: `memo23/vitals-scraper`).

**Purpose:** Extract US doctor & dentist provider profiles from Vitals.com — by specialty/location browse or individual profile URLs — into one clean schema with NPI, specialties, education, licenses, practice locations, ratings and the full patient reviews.

**Minimal input:**

```json
{
  "startUrls": ["https://www.vitals.com/cardiologists/ny/new-york"],
  "maxItems": 5
}
```

`startUrls` accepts specialty browse (`vitals.com/cardiologists`), specialty+location (`vitals.com/cardiologists/ny/new-york`), or individual provider (`vitals.com/doctors/{slug}`, `/dentists/{slug}`, `http://` and old `Dr_First_Last.html` links, profile tabs such as `/reviews`).

**Output:** one dataset row per provider — fields include portal, npi, providerId, entityId, vitalsGuid, fullName, firstName, lastName, degree, gender, yearsOfExperience, primarySpecialty, specialties\[], expertise\[], education\[], licenses\[], practiceLocations\[], phone, city, state, postcode, latitude, longitude, ratingScore, numberOfRatings, reviewCount, reviews\[] (reviewId, rating, comment, reviewDate, updatedAt, helpfulCount, source, criteria), vitalsScore, languages\[], acceptsNewPatients, isTelehealth, hospitals\[], insurances\[], isMedicare, isMedicaid, bio, photoUrl, videoUrl.

**Behaviors an agent should know:**

- URLs are auto-classified; browse pages list ~60 providers each (sponsored cards are skipped) and paginate via `?page=N` until `maxItems`.
- Always set `maxItems` (default 10000) to bound rows and billing.
- `flatten` (default true) JSON-stringifies nested arrays into `*_json` fields for CSV; set false to keep nested JSON.
- `includeReviews` (default true) attaches every patient review, newest first, capped by `maxReviewsPerProvider` (default 100); reviews do not add rows or charges.
- `enrichEmails` is billed per email found — charged only when an email is returned.
- Public data only, read from each page's embedded state. Pure HTTP, no browser.

### ⚠️ Disclaimer

This scraper accesses only publicly available data. Use the extracted data in compliance with Vitals.com's Terms of Use, US privacy laws (including state regulations), and all applicable laws. You are responsible for how you use scraped data — particularly personal information (provider names, contact details). This actor is not affiliated with, endorsed by, or connected to Vitals, WebMD, or Internet Brands.

### SEO Keywords

vitals scraper, vitals.com scraper, doctor directory scraper, physician data scraper, NPI scraper, US healthcare provider data, doctor reviews scraper, medical provider scraper, healthcare lead generation, physician directory api, doctor contact data, dentist scraper, provider npi lookup, healthcare data extraction, medical sales leads

# Actor input Schema

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

Full vitals.com URLs. Browse/specialty URLs are paginated and fanned out to individual provider profiles automatically until `Maximum items` is reached.

## `includeReviews` (type: `boolean`):

When enabled (default), each provider row carries a `reviews` array with every patient review: star rating, comment, date, helpful votes and per-question scores (bedside manner, wait time, …). Reviews are bundled into the provider row at no extra charge.

## `maxReviewsPerProvider` (type: `integer`):

Newest reviews first. Raise it for providers with hundreds of reviews.

## `flatten` (type: `boolean`):

When enabled (default), nested objects/arrays (specialties, education, licenses, practiceLocations, reviews) are JSON-stringified into `*_json` fields for CSV. Disable to keep the full nested JSON.

## `enrichEmails` (type: `boolean`):

If enabled, finds a contact email for each result from its own website (or by discovering it from the name). Adds contactEmail + contactWebsite columns plus a detailed emailEnrichment object. Billed per contact email found; only charged when an email is returned, never for misses.

## `maxItems` (type: `integer`):

Hard cap on the number of providers collected. Vitals browse pages list ~60 providers each; use this cap to control billing.

## `maxConcurrency` (type: `integer`):

Maximum number of profile pages fetched in parallel. 4-8 is the sweet spot.

## `minConcurrency` (type: `integer`):

Minimum number of profile pages fetched in parallel.

## `maxRequestRetries` (type: `integer`):

Retries per page, each on a fresh IP. At least 2 retries are always made.

## `proxy` (type: `object`):

Leave empty — the actor already routes all traffic through its own built-in residential proxy at no extra cost to you. Only set this if you want to use your own proxies.

## Actor input object example

```json
{
  "startUrls": [
    "https://www.vitals.com/cardiologists/ny/new-york",
    "https://www.vitals.com/dermatologists/ca/los-angeles",
    "https://www.vitals.com/dentists/tx/houston"
  ],
  "includeReviews": true,
  "maxReviewsPerProvider": 100,
  "flatten": true,
  "enrichEmails": false,
  "maxItems": 10000,
  "maxConcurrency": 6,
  "minConcurrency": 1,
  "maxRequestRetries": 5
}
```

# 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 = {
    "startUrls": [
        "https://www.vitals.com/cardiologists/ny/new-york",
        "https://www.vitals.com/dermatologists/ca/los-angeles",
        "https://www.vitals.com/dentists/tx/houston"
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("memo23/vitals-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 = { "startUrls": [
        "https://www.vitals.com/cardiologists/ny/new-york",
        "https://www.vitals.com/dermatologists/ca/los-angeles",
        "https://www.vitals.com/dentists/tx/houston",
    ] }

# Run the Actor and wait for it to finish
run = client.actor("memo23/vitals-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 '{
  "startUrls": [
    "https://www.vitals.com/cardiologists/ny/new-york",
    "https://www.vitals.com/dermatologists/ca/los-angeles",
    "https://www.vitals.com/dentists/tx/houston"
  ]
}' |
apify call memo23/vitals-scraper --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,memo23/vitals-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/0Umbn8AjFafALxZIg/builds/aG0oTJQGLXu2htYbz/openapi.json
