# RateMDs Scraper \[From $1.5💰] | Profiles | Reviews | Leads (`ahmed_jasarevic/ratemds-scraper`) Actor

Extract doctor profiles and patient reviews from RateMDs for reputation monitoring, healthcare lead generation and patient sentiment analysis. US & Canada data, structured JSON on Apify.

- **URL**: https://apify.com/ahmed_jasarevic/ratemds-scraper.md
- **Developed by:** [Ahmed Jasarevic](https://apify.com/ahmed_jasarevic) (community)
- **Stats:** 2 total users, 1 monthly users, 50.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $1.40 / 1,000 doctors

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

## RateMDs Scraper

A RateMDs scraper that extracts doctor profiles and patient reviews from [RateMDs.com](https://www.ratemds.com) across the United States and Canada: 33 profile fields land in the default dataset and 15 review fields in a separate `reviews` dataset — built for reputation monitoring for doctors and healthcare review monitoring when no official RateMDs API exists.

### Main use cases

- **Doctor reputation monitoring / online reputation management for doctors** — track the five rating dimensions plus fresh patient reviews for a physician watchlist on a schedule.
- **Healthcare review monitoring** — pull RateMDs reviews alongside the other physician rating websites (Healthgrades, Vitals, Zocdoc, WebMD) in a multi-site pipeline.
- **Patient sentiment analysis** — analyze review text, star ratings and per-dimension sub-ratings to see how patients talk about a practice.
- **Healthcare lead generation** — build doctor leads with ratings and reviews: profile rows carry phone, address, specialty, review count and average score.
- **Physician market research** — scrape doctor reviews for a research dataset, or compare reviews across cities and specialties.
- **Find the best rated doctors in any US or Canadian city** — search by state/city and specialty to surface top-rated physicians.

### How it works

The actor runs a CheerioCrawler (no browser automation) with Apify residential proxies. In `search` mode it walks paginated listing pages (`/best-doctors/{state}/{city}/[{specialty}]/?page={N}`); in `profile` mode it fetches specific `/doctor-ratings/{slug}/` profile URLs directly. It solves the site's HTTP 202 JavaScript proof-of-work challenge inside the request session, then reads structured data from server-rendered `window.DATA` JSON blobs, JSON-LD (`Physician` / `MedicalWebPage` / `BreadcrumbList`) and DOM fallbacks. Profiles land in the default dataset; reviews go to their own `reviews` dataset, fetched page by page from the server-rendered organic and partner review pages inside the profile's sticky-IP session.

### Does RateMDs Have An API? (And How To Get Its Reviews)

RateMDs publishes no public API — there is no developer portal or documented endpoint for doctor or review data. That gap is why this actor exists: a RateMDs Reviews API substitute that returns the same data as structured JSON.

- **No official API**: RateMDs exposes no documented REST endpoint, so programmatic access requires a scraper.
- **Programmatic reviews**: start a run, then read the `reviews` dataset (15 fields per review) with `GET /v2/datasets/<reviewsDatasetId>/items`, or read both dataset URLs from `GET /v2/actor-runs/:runId` in `output.profiles` / `output.reviews`.
- **API-friendly output**: an actor output schema (`actorOutputSchemaVersion` 1) publishes both dataset URLs, so downstream code fetches profiles and reviews without guessing storage IDs.

### Scrape RateMDs Doctor Profiles And Patient Reviews

- **Two modes**: `search` crawls listing pages by location and specialty; `profile` scrapes specific doctor profile URLs directly.
- **Dual-dataset writes**: 33 profile fields (name, specialty, practice, address, phone, education, languages, geo-coordinates, five rating dimensions) to the default dataset; 15 review fields (text, date, stars, sub-ratings, author, appointment type, source) to the separate `reviews` dataset — each with its own schema and Output-tab view.
- **Parallel by default**: `maxConcurrency` (default 10, max 20) fills every slot immediately; those slots share up to 3 sticky residential sessions, so the proof-of-work challenge is solved once per session, not once per request.
- **Review pagination handled**: both organic (`?page=N`) and partner (`?third_party_page=N`) lists are walked — up to 10 pages per list per doctor, and pages after page 1 are prefetched in small parallel batches so doctors with hundreds of reviews no longer cost one round-trip per page.

### Monitor Physician Reviews Across RateMDs, Healthgrades, Vitals And Zocdoc

Reputation-monitoring platforms list RateMDs as a monitored source alongside Healthgrades, Vitals, WebMD, Yelp and Zocdoc, so healthcare review monitoring needs the RateMDs slice too:

- Run daily or weekly per doctor or per city/specialty to catch new reviews.
- Every review row carries `runId`, `date` and `source` (`organic` / `partner`), so you can diff runs over time.
- Profile rows carry `reviewCount`, `averageScore` and the five sub-ratings — a rating trend line per physician.
- Reviews are stored page by page as they arrive, so a stopped run keeps every page it already fetched.

### Build A Doctor Lead List With Ratings And Reviews

The profile dataset doubles as a doctor lead list for healthcare lead generation and provider data enrichment: contact and geo fields (`phone`, `streetAddress`, `city`, `state`, `zipCode`, `country`, `latitude`, `longitude`), credential context (`name`, `specialty`, `practiceName`, `school`, `graduationYear`, `languages`, `openingHours`, `clinics`), the ratings that qualify a lead (`reviewCount`, `averageScore`, `overallRating`, `staffRating`, `punctualityRating`, `helpfulnessRating`, `knowledgeRating`) and enrichment links (`profileUrl`, `photoUrl`). With `search` mode filtering by state/city and specialty, you can build geographically targeted physician lists for the US and Canada.

### Patient Sentiment Analysis From Real Doctor Reviews

The dedicated reviews dataset is built for patient sentiment analysis and physician ratings data work:

- **Text**: `text` holds the full review text (description plus title when present).
- **Ratings**: `rating` (1-5 overall) plus `staffRating`, `punctualityRating`, `helpfulnessRating`, `knowledgeRating` sub-ratings per review.
- **Context**: `date`, `author`, `appointmentType` (e.g. "Office visit") and `source` (`organic` = on RateMDs, `partner` = collected by a partner).
- **Joins**: `doctorName`, `doctorProfileUrl` and `runId` link every review back to its profile row and run.

Because reviews live in their own dataset rather than embedded in profile items, you can aggregate thousands of reviews without re-parsing profile JSON.

### Provider Data Enrichment For US And Canadian Doctors

Coverage is US + Canada only, enforced by the `country` enum (`us` / `ca` / `both`, default `both`). The 33 profile fields cover identity (`name`, `gender`, `profileUrl`), credentials (`specialty`, `school`, `graduationYear`), location, contact and practice, reputation (`reviewCount`, `averageScore`, `overallRating`, `staffRating`, `punctualityRating`, `helpfulnessRating`, `knowledgeRating`) and media/meta (`photoUrl`, `breadcrumbTrail`, `scrapedAt`).

`location` accepts state/city paths (`tx/houston`, `on/toronto`), country prefixes (`us/`, `ca/`) or full RateMDs URLs — cleaned up automatically.

### Find The Best Rated Doctors In Any US Or Canadian City

RateMDs' own city ranking pages (e.g. "Best Doctors in Toronto, ON") prove the best-rated-doctors-in-city query pattern across US and Canadian cities. This actor answers it with `search` mode:

- `location` takes a state or state/city slug (`tx`, `tx/houston`, `on`, `on/toronto`); `specialty` narrows to a slug (`dentist`, `family-gp`, `dermatologist`, `cardiologist`).
- `maxPages` caps listing pages per location/specialty combination (0 = unlimited, default 10), following `<link rel="next">`; `maxItems` caps total profiles (0 = unlimited, default 100).

### Input Parameters

All 11 input fields from `input_schema.json`. Only `mode` is required.

| Field | Type | Required | Default | Notes |
|-------|------|----------|---------|-------|
| `mode` | `search` | `profile` | Yes | `search` | `search` crawls listing pages by location/specialty; `profile` scrapes specific doctor profile URLs. |
| `location` | string | No (search mode) | `""` | State/city path from the RateMDs URL, e.g. `tx/houston`, `on/toronto`. Country prefixes (`us/`, `ca/`) and full RateMDs URLs accepted. Missing value in search mode fails fast. |
| `specialty` | string | No | `""` | Specialty slug from the RateMDs URL path, e.g. `dentist`, `family-gp`, `dermatologist`. Used in search mode. |
| `country` | `us` | `ca` | `both` | No | `both` | Country to search: `us`, `ca` or `both`. |
| `profileUrls` | string\[] | No (profile mode) | `[]` | Doctor profile URLs or slugs to scrape directly. |
| `maxItems` | integer | No | `100` | Max doctor profiles. `0` = unlimited (capped by Apify limits). |
| `maxPages` | integer | No | `10` | Max listing pages per location/specialty. `0` = unlimited. |
| `includeReviews` | boolean | No | `true` | Scrape patient reviews into the separate `reviews` dataset. Increases scraping time significantly. |
| `maxReviewsPerDoctor` | integer | No | `5` | Max reviews per doctor. `0` = all; organic + partner lists, up to 10 pages per list per doctor. |
| `maxConcurrency` | integer | No | `10` | Parallel requests, range 1-20. Up to 3 sticky residential sessions are shared by those requests, so PoW is solved once per session. 10+ slots need 1024 MB+ memory. |
| `proxy` | object | Yes\* | residential prefill | **Residential proxies REQUIRED** — datacenter IPs get HTTP 406. Prefill: `{"useApifyProxy": true, "apifyProxyGroups": ["RESIDENTIAL"]}`. |
\* Apify Proxy with the `RESIDENTIAL` group is mandatory; the actor fails with an explicit message if RateMDs returns 406.

### Output Fields

The run's **Output** tab shows both outputs side by side — **Doctor profiles** and **Patient reviews** — each linking to its own dataset. `GET /v2/actor-runs/:runId` returns the same links in `output.profiles` / `output.reviews`.

#### Doctor profiles — default dataset, schema `profiles_dataset_schema.json` (33 fields)

One item per doctor; reviews are **not** embedded — they live in their own dataset. The default dataset declares its schema in `.actor/actor.json` (`storages.datasets.default`), so the Output tab renders a **Doctors** table and the field list is published to MCP/AI agents.

- **Identity**: `name`, `profileUrl`, `gender`, `specialty`, `photoUrl`, `breadcrumbTrail`
- **Credentials**: `school`, `graduationYear`
- **Address**: `streetAddress`, `city`, `state`, `stateSlug`, `country`, `zipCode`, `latitude`, `longitude`
- **Contact and practice**: `phone`, `practiceName`, `clinics`, `openingHours`, `languages`
- **Ratings**: `reviewCount` (headline = organic + partner), `averageScore`, `overallRating`, `staffRating`, `punctualityRating`, `helpfulnessRating`, `knowledgeRating`
- **Meta**: `scrapedAt`
- **Fallback-only** (only when a review falls back into the default dataset): `type` (always `"review"`; profiles carry no `type`), `runId`, `doctorName`, `doctorProfileUrl`

#### Patient reviews — separate `reviews` dataset, schema `reviews_dataset_schema.json` (15 fields)

When `includeReviews` is true, every scraped review is written to its own dataset, declared under `storages.datasets.reviews`. The two storages are completely separate: the default dataset holds one item per doctor (Output-tab view **Doctors**), the `reviews` dataset one item per review (view **Reviews**).

- **Core**: `id` (RateMDs rating id, de-duplicates reviews across paginated fetches), `date` (YYYY-MM-DD), `rating` (1-5), `text` (full review text: description plus title when present)
- **Sub-ratings**: `staffRating`, `punctualityRating`, `helpfulnessRating`, `knowledgeRating`
- **Context**: `author`, `appointmentType`, `source` (`organic` = written on RateMDs, `partner` = collected by a partner)
- **Joins and meta**: `type` (always `"review"`), `doctorName`, `doctorProfileUrl`, `runId`

Each review links to its doctor via `doctorName` / `doctorProfileUrl`; `runId` isolates items of a single run. On the platform the `reviews` alias is a run-scoped storage created at run start (fresh for every run), logged as `reviewsDatasetId`. Fetch it from the run page (**Storage** tab -> *Dataset* -> `reviews`) or with `GET /v2/datasets/<reviewsDatasetId>/items`.

**Fallback**: a restricted-token run (`LIMITED_PERMISSIONS`, e.g. MCP/token runs) that cannot write the second storage logs a warning and writes those reviews into the **default dataset** marked `"type": "review"`. Start from the Apify Console (full access) for the separate dataset.

### Example Input And Output

#### Example Input — search mode, Toronto dentists

```json
{
  "mode": "search",
  "location": "on/toronto",
  "specialty": "dentist",
  "country": "ca",
  "maxItems": 50,
  "maxPages": 5,
  "includeReviews": true,
  "maxReviewsPerDoctor": 20,
  "proxy": {
    "useApifyProxy": true,
    "apifyProxyGroups": ["RESIDENTIAL"]
  }
}
```

#### Example Output — profile row (default dataset)

```json
{
  "name": "Dr. Jane Doe",
  "profileUrl": "https://www.ratemds.com/doctor-ratings/dr-jane-doe-toronto-on/",
  "specialty": "Family Medicine",
  "practiceName": "Toronto Family Clinic",
  "city": "Toronto",
  "state": "Ontario",
  "country": "Canada",
  "phone": "(416) 555-0123",
  "reviewCount": 42,
  "averageScore": 4.6,
  "overallRating": 4.6,
  "staffRating": 4.5,
  "punctualityRating": 4.7,
  "helpfulnessRating": 4.6,
  "knowledgeRating": 4.8,
  "latitude": 43.6532,
  "longitude": -79.3832,
  "scrapedAt": "2026-10-05T12:34:56.789Z"
}
```

#### Example Output — review row (`reviews` dataset)

```json
{
  "id": 9003,
  "date": "2024-01-15",
  "rating": 5,
  "text": "Excellent doctor, very caring...",
  "staffRating": 5,
  "punctualityRating": 5,
  "helpfulnessRating": 5,
  "knowledgeRating": 5,
  "source": "organic",
  "type": "review",
  "doctorName": "Dr. Jane Doe",
  "doctorProfileUrl": "https://www.ratemds.com/doctor-ratings/dr-jane-doe-toronto-on/",
  "runId": "AbCdEf123456"
}
```

### RateMDs Scraper For Apify — Pricing, Performance And Cost

- **Pay-per-event pricing**: `Doctor` $0.0015 per profile (DIAMOND $0.0014), `Review` $0.00015 per review (DIAMOND $0.00014), `Actor Start` $0.00005. At the user's maximum charge (`ACTOR_MAX_TOTAL_CHARGE_USD`) the crawler stops instead of billing more.
- **Free/paid tier**: free-tier accounts get 10 profiles per run; paid accounts are unlimited.
- **Memory**: 1024 MB default (peak ~330 MB with 6 parallel requests; 256 MB was OOM-killed); 256-4096 MB range, default timeout 3600 s (max 14400 s). Each additional parallel slot parses its own page, so use 1024 MB for 10 slots and 2048 MB for 20.
- **Speed**: ~100 profiles in 30-60 seconds with 10 parallel requests (was ~2 minutes with a single request). Slots start together and share a few sessions, so the crawler is not limited by one PoW solve per IP. Review pages after page 1 are fetched in parallel (3 at a time), which mainly speeds up `maxReviewsPerDoctor` values above 20.
- **Compute**: ~0.01-0.05 CU per 100 profiles; CheerioCrawler is 10-20x faster and 4-8x cheaper on Apify compute units than browser automation. Residential bandwidth is required (included in Apify paid plans).

**Limitations**: residential proxy is an additional cost; free tier limited to 10 profiles per run; partner reviews keep only the fields both lists share (id, date, rating, text, author) — per-item sub-ratings have no organic counterpart and are not guessed; some profile fields may be missing if not present in embedded JSON or JSON-LD.

### Anti-Bot Strategy: Residential Proxies And Proof-Of-Work

RateMDs sits behind Cloudflare with a JavaScript proof-of-work challenge, answered without a browser:

- **HTTP 202 proof-of-work**: page requests get HTTP 202 plus `window.POW_CHALLENGE_DATA = { challenge_nonce, challenge_hmac, difficulty, difficulty_char, issued_at, ... }`. The actor solves it like the site's script (SHA-256 over `nonce + issued_at + n` until the hash starts with `difficulty_char` repeated `difficulty` times — difficulty 3 is a few thousand hashes), stores the `pow_bypass` cookie and **refetches in the same session** with `context.sendRequest`.
- **In-session refetch is essential**: a solve is only accepted from the sticky proxy IP it was issued to; retrying through Crawlee's queue picks a new IP with a fresh challenge — the failure that previously ended runs with "0 succeeded, 1 failed".
- **HTTP 406 handling**: datacenter IPs are rejected outright, so `RESIDENTIAL` is mandatory. A mid-run 406 usually means the sticky IP went bad: the actor retires the session (Apify hands out a **different sticky residential IP**), drops the cookie for a fresh challenge and retries; the run aborts after 10 blocked responses. Review pages recover the same way, up to 3 rotations per doctor.
- **Shared sticky sessions**: `maxConcurrency` fills every crawler slot immediately, but at most 3 sticky residential sessions are live (`MAX_PROXY_SESSIONS`). The first request on a session solves the PoW challenge and the rest reuse its cookie, so solve cost scales with sessions rather than slots. Solves are serialized per session (`withSessionLock`), so concurrent requests cannot interleave `pow_trace` / `pow_bypass` cookie writes.

**Data extraction priority**: (1) `window.DATA.*` JSON blobs (`doctorDetailProps`, `ratingsPage`) — ratings, reviews, schools, languages, locations, coordinates; (2) JSON-LD (`Physician` / `MedicalWebPage` / `BreadcrumbList`) — address, phone, hours, aggregate rating; (3) DOM — listing cards (`.doctor-profile-card`) and fallbacks.

### Endpoints And Review Pagination

- **Listing pages**: `https://www.ratemds.com/best-doctors/{state}/{city}/[{specialty-slug}]/?page={N}`. Pagination follows `<link rel="next">` up to `maxPages`. There is **no** country prefix — `us/` and `ca/` prefixes in the input are stripped automatically.
- **Profile pages**: `https://www.ratemds.com/doctor-ratings/{slug}/` (also accepts the legacy form `/doctor-ratings/{id}/Dr-Name-City-ST.html/`).
- **Review pages (organic)**: `https://www.ratemds.com/doctor-ratings/{slug}/?page=N` (page 1 is the profile URL itself). The response is an ordinary server-rendered HTML page whose `window.DATA.doctorDetailProps.ratingsPage` contains that page of the list, so it is parsed with Cheerio. The scraper walks it when the organic count exceeds what the profile embedded, from page 1, deduplicating by id. The embedded widget's `total_pages` can be wrong (1 page even for a doctor with 525 reviews), so page 1's payload is used to correct it.
- **Review pages (partner)**: `https://www.ratemds.com/doctor-ratings/{slug}/?third_party_page=N`. RateMDs splits reviews into two lists; the headline `reviewCount` is the sum, so doctors with hundreds of ratings and 0-2 organic ones (e.g. 426 partner) would otherwise yield none. The same HTML page embeds `doctorDetailProps.thirdPartyRatingsPage`; entries use different field names (`description`, `reviewer_name`, `date_added`) and are tagged `source: "partner"`.
- The internal `?json=true` XHR endpoints used by the site's own JavaScript are **not** called (they are the most aggressively rate-limited part of the site). Both review lists are fetched as plain HTML **inside the profile request's session** (same sticky IP and solved cookie), up to 10 pages per list per doctor (10 reviews per page).

### Integrations & Automation

- **Apify Console**: paste an input JSON (see Example Input), select the `RESIDENTIAL` proxy group, start. Both datasets appear on the run's Output tab.
- **Apify API**: start runs with `POST https://api.apify.com/v2/acts/LUulX2IdYvMb9dy3V/runs`, poll `GET /v2/actor-runs/<runId>`, then fetch `output.profiles` and `output.reviews`.
- **Webhooks**: fire on run `SUCCEEDED` to push `output.reviews` into your database or review-monitoring pipeline automatically.
- **Zapier / Make**: trigger on a new run or dataset item to route reviews into Sheets, Airflow or a CRM.
- **Recommended schedule**: run **daily or weekly** per doctor watchlist (profile mode) or per city/specialty (search mode) for ongoing healthcare review monitoring.

### Related Actors

- [ratemds-scraper](https://apify.com/parseforge/ratemds-scraper)
- [ratemds-scraper](https://apify.com/scrapersdelight/ratemds-scraper)
- [healthgrades-scraper](https://apify.com/parseforge/healthgrades-scraper)
- [vitals-scraper](https://apify.com/memo23/vitals-scraper)
- [zocdoc-scraper](https://apify.com/silentflow/zocdoc-scraper)

### FAQ

#### Is there an official RateMDs API?

No. RateMDs publishes no public API — no developer portal, no documented endpoint for doctor or review data. To get RateMDs reviews programmatically you need a scraper like this actor, then query its datasets over the Apify API.

#### What are the alternatives to this actor?

Other RateMDs-focused actors exist in the Apify Store — parseforge's and scrapersdelight's `ratemds-scraper`, among them. For adjacent physician rating websites, see the Healthgrades, Vitals and Zocdoc scrapers linked in Related Actors; WebMD scrapers are also available in the Store.

#### Which websites should I monitor for physician reviews?

Reputation-audit guides typically name Healthgrades, Vitals, RateMDs, WebMD, Yelp and Zocdoc as the core physician review platforms. This actor covers the RateMDs slice; pair it with scrapers for the others.

#### What are the best doctor rating sites?

Commonly compared sites include Healthgrades, Zocdoc, Vitals and RateMDs. RateMDs covers US and Canadian doctors with multi-dimensional ratings (overall, staff, punctuality, helpfulness, knowledge) and patient review text.

#### How do I build a doctor lead list with ratings?

Use `search` mode with a `location` (state or state/city slug) and optional `specialty`, then read the default dataset: each profile row carries `phone`, address fields, `specialty`, `reviewCount`, `averageScore` and the five rating dimensions.

### SEO Keywords

ratemds scraper, ratemds reviews api, ratemds api, ratemds scraper apify, scrape ratemds.com doctor data, doctor reviews data api, healthcare provider reviews api, physician ratings data, healthcare review monitoring, reputation management for doctors, online reputation management for doctors, patient sentiment analysis, healthcare lead generation, doctor leads with ratings and reviews, provider data enrichment, physician market research, doctor rating sites, physician rating websites, best doctor rating sites, compare doctor reviews, scrape doctor reviews for research dataset, medical practice marketing online reviews, clinic reputation monitoring, best rated doctors in city, doctor ratings near me

### For AI Agents & LLM Apps

**Purpose**: extract doctor profiles and patient reviews from RateMDs.com (US + Canada) into two structured datasets.

**Minimal working input**:

```json
{
  "mode": "search",
  "location": "tx/houston",
  "proxy": {
    "useApifyProxy": true,
    "apifyProxyGroups": ["RESIDENTIAL"]
  }
}
```

**Variant — profile mode**: `{"mode": "profile", "profileUrls": ["https://www.ratemds.com/doctor-ratings/dr-jane-doe-toronto-on/"]}`

**Output fields**: 33 profile fields in the default dataset, 15 review fields in the `reviews` dataset — full inventory with groupings in the **Output Fields** section above.

**Behaviors an agent should know**:

- `mode` is the only required field: `search` (default) crawls listing pages, `profile` fetches `profileUrls` directly. US + Canada only (`country`: `us` / `ca` / `both`, default `both`); a country prefix in `location` is stripped automatically.
- Dual-dataset writes: profiles go to the default dataset, reviews to the separate `reviews` dataset; both URLs are exposed at `output.profiles` and `output.reviews`. If the second storage cannot be written (restricted token), reviews fall back into the default dataset marked `"type": "review"`.
- `includeReviews` defaults to `true` — each stored review is a billed `Review` event. Cost-relevant defaults if unset: `maxItems` 100, `maxPages` 10, `maxReviewsPerDoctor` 5; `0` means unlimited (capped by Apify limits).
- `maxConcurrency` (1-20, default 10) fills parallel slots immediately; those requests share up to 3 sticky residential sessions (PoW solved once per session). The residential proxy is mandatory (datacenter IPs get HTTP 406 and the run fails). Use 1024 MB+ memory for 10+ parallel slots.

**Billing (pay-per-event)**: `Doctor` $0.0015 per profile (DIAMOND $0.0014) + `Review` $0.00015 per review (DIAMOND $0.00014) + `Actor Start` $0.00005.

### Legal & Compliance Disclaimer

This actor is an independent tool and is **not affiliated with, endorsed by, or sponsored by RateMDs** or its operators.

It accesses only **publicly available RateMDs pages** — no login, paywall or authentication bypass. It uses residential proxies and solves the site's public proof-of-work challenge to retrieve those public pages.

You are solely responsible for complying with RateMDs' Terms of Service and applicable data-protection laws (GDPR, CCPA where relevant) when collecting, storing or using the data. Output includes doctor names, practice contact details and patient review content — all publicly posted on RateMDs. Do not use personal contact data for unsolicited commercial outreach in violation of applicable law, and do not represent scraped ratings or reviews as verified medical advice.

# Actor input Schema

## `mode` (type: `string`):

Search mode: crawl listing pages by location/specialty. Profile mode: scrape specific doctor profile URLs.

## `location` (type: `string`):

State/city path from the RateMDs URL, e.g. 'tx/houston', 'on/toronto', 'ca/los-angeles'. A country prefix ('us/', 'ca/') and a full RateMDs URL are also accepted and cleaned up automatically. Used in search mode.

## `specialty` (type: `string`):

Specialty slug from the RateMDs URL path (optional), e.g. 'dentist', 'family-gp', 'dermatologist', 'cardiologist'. Used in search mode.

## `country` (type: `string`):

Country to search in. 'us' for United States, 'ca' for Canada, 'both' for both countries.

## `profileUrls` (type: `array`):

Array of doctor profile URLs to scrape directly. Used in profile mode.

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

Maximum number of doctor profiles to scrape. 0 = unlimited (capped by Apify limits).

## `maxPages` (type: `integer`):

Maximum number of listing pages to crawl per location/specialty combination. 0 = unlimited.

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

Whether to scrape individual patient reviews from doctor profile pages. Increases scraping time significantly.

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

Maximum number of reviews to scrape per doctor profile. 0 = all reviews.

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

How many pages to scrape at the same time. All slots start immediately; up to 3 sticky residential proxy sessions are shared by those requests, so the proof-of-work challenge is solved once per session instead of once per request. Higher is faster; lower is gentler on RateMDs. Use 1024 MB or more memory with 10+ parallel requests.

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

Select proxies to be used by your crawler. Residential proxies are REQUIRED to bypass RateMDs Cloudflare protection.

## Actor input object example

```json
{
  "mode": "search",
  "location": "houston",
  "specialty": "",
  "country": "both",
  "profileUrls": [],
  "maxItems": 10,
  "maxPages": 1,
  "includeReviews": true,
  "maxReviewsPerDoctor": 5,
  "maxConcurrency": 10,
  "proxy": {
    "useApifyProxy": true,
    "apifyProxyGroups": [
      "RESIDENTIAL"
    ]
  }
}
```

# Actor output Schema

## `profiles` (type: `string`):

One item per scraped RateMDs doctor profile (default dataset).

## `reviews` (type: `string`):

One item per scraped patient review, written to the separate reviews dataset.

# 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 = {
    "location": "houston",
    "proxy": {
        "useApifyProxy": true,
        "apifyProxyGroups": [
            "RESIDENTIAL"
        ]
    }
};

// Run the Actor and wait for it to finish
const run = await client.actor("ahmed_jasarevic/ratemds-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 = {
    "location": "houston",
    "proxy": {
        "useApifyProxy": True,
        "apifyProxyGroups": ["RESIDENTIAL"],
    },
}

# Run the Actor and wait for it to finish
run = client.actor("ahmed_jasarevic/ratemds-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 '{
  "location": "houston",
  "proxy": {
    "useApifyProxy": true,
    "apifyProxyGroups": [
      "RESIDENTIAL"
    ]
  }
}' |
apify call ahmed_jasarevic/ratemds-scraper --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,ahmed_jasarevic/ratemds-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/LUulX2IdYvMb9dy3V/builds/5pcMP6g9uLEkGf2ey/openapi.json
