# 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
- **Stats:** 28 total users, 4 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?

Actors are web data automations that power AI and operations. They run on the Apify platform to scrape websites, process data, connect APIs, and automate workflows.
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.
Actors are written with capital "A".

## 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.
The best way to integrate Actors is as follows.

- **AI agents and MCP clients** — the [Apify MCP server](https://docs.apify.com/integrations/mcp.md) at `https://mcp.apify.com` (remote, streamable HTTP, OAuth on first use).
- **Agentic workflows and local Actor development** — [Agent Skills](https://apify.com/.well-known/agent-skills/index.json) with the [Apify CLI](https://docs.apify.com/cli/docs.md): `npm install -g apify-cli`, then `apify login`.
- **JavaScript/TypeScript projects** — the official [JS/TS client](https://docs.apify.com/api/client/js/docs.md): `npm install apify-client`.
- **Python projects** — the official [Python client](https://docs.apify.com/api/client/python/docs.md): `pip install apify-client`.
- **Any other language** — the [REST API](https://docs.apify.com/api/v2.md).

For usage examples, see the [API](#api) section below.

For more details, see Apify documentation as [Markdown index](https://docs.apify.com/llms.txt) and [Markdown full-text](https://docs.apify.com/llms-full.txt).

# 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 + review counts, 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 & reviews** — Vitals rating score, number of ratings, review count, 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}`) |

> **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. 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) |
| `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\"]",
  "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 |
| `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.

**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 and ratings.

**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}`).

**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, 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 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.
- `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.

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

When enabled (default), nested objects/arrays (specialties, education, licenses, practiceLocations) 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`):

Number of retries before a failed request is given up.

## `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"
  ],
  "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/8G3Xr3itaaBPZH1gx/openapi.json
