# Healthcare Provider Leads Scraper (`apt_marble/healthcare-provider-leads-scraper`) Actor

Build callable lists of US healthcare providers and practices by specialty and location. Every row has the name, specialty, full practice address and a phone number — and practice rows name the official in charge with their job title and direct line. No login needed.

- **URL**: https://apify.com/apt\_marble/healthcare-provider-leads-scraper.md
- **Developed by:** [Hamza](https://apify.com/apt_marble) (community)
- **Categories:** Lead generation, Other
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $20.00 / 1,000 provider records

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

## Healthcare Provider Leads Scraper

Build a callable list of US healthcare providers and practices in seconds. Choose a
specialty and a location, and every row comes back with the provider or practice name, the
specialty, the full practice address and a **phone number** — plus, for practices, the name
and job title of the person in charge. Built for medical device reps, healthcare software
vendors, pharma field teams and medical recruiters who need a clean territory list they can
start dialling today, not a subscription seat.

No account, login or connected profile is needed. Enter a specialty and a state, press start.

### What you get

**Professional details**

- Full name, first and last name, and title prefix
- Job title — for practices, the role of the named official (President, Practice
  Administrator, General Manager, Owner…)
- Professional credentials for individual providers (MD, DDS, PharmD, RN…)
- Specialty, plus the licence number and licensing state where published

**Contact information**

- Phone number, in both the original and a fully international format
- Whether that number is the named person's direct line or the practice's main line
- Fax number where published

**Practice information**

- Practice or organisation name
- Full street address, city, state and postal code
- Separate mailing address where it differs from the practice location
- Whether the provider is a sole proprietor
- A stable provider ID you can use to match and de-duplicate against your own records
- Whether the record is currently active, and when it was last updated

### What you can do with it

- Build a dialling list for a sales territory — one specialty, one state, and every row has a phone number.
- Reach the decision maker directly: practice records name the official and their job title, with their direct line.
- Target by exact ZIP code for tight route planning or field-rep patch assignment.
- Build a specialty-specific list (every cardiology practice in Illinois, every dental practice in Chicago).
- Recruit clinicians by specialty and location, with credentials and licence details already attached.
- Refresh an existing list — match on the provider ID and see what changed.
- Size a market before you enter it, by counting providers per specialty and city.

### Sample record

```json
{
  "fullName": "TIMOTHY SKIDMORE",
  "firstName": "TIMOTHY",
  "lastName": "SKIDMORE",
  "namePrefix": null,
  "jobTitle": "Practice Administrator",
  "seniority": "decision_maker",
  "credential": null,
  "specialty": "Dentist",
  "specialtyCode": "1223G0001X",
  "licenseNumber": "019024178",
  "licenseState": "IL",
  "companyName": "105 W MADISON DENTAL PC",
  "registryNumber": "1023280917",
  "recordType": "organization",
  "phone": "6308169064",
  "phoneE164": "+16308169064",
  "phoneType": "direct",
  "faxNumber": null,
  "email": null,
  "secureMessagingAddress": null,
  "addressLine1": "105 W MADISON ST STE 1100",
  "city": "CHICAGO",
  "region": "IL",
  "postalCode": "60602-4602",
  "country": "United States",
  "status": "active",
  "lastUpdated": "2024-03-11",
  "collectedAt": "2026-08-18T17:52:03.114Z"
}
```

### Input reference

| Field | Type | Default | What it does |
| --- | --- | --- | --- |
| `specialty` | string | `Cardiology` | The specialty to search for. Several can be entered separated by commas, and each is searched separately — this is also how you collect more than one search's maximum. |
| `state` | string | `NY` | Two-letter US state code. A state on its own is not specific enough; pair it with a specialty, city or postal code. |
| `city` | string | empty | City to search. Several can be entered separated by commas, each searched separately. |
| `postalCode` | string | empty | Narrow to a single ZIP code. |
| `organizationNameContains` | string | empty | Only return practices whose name contains this text, e.g. `Dental`. |
| `providerType` | select | `both` | Practices (named official + job title), individual providers (clinicians), or both. |
| `requirePhone` | boolean | `false` | Skip any record without a usable phone number, so every row is callable. |
| `activeOnly` | boolean | `true` | Exclude records no longer marked active. |
| `strictCityMatch` | boolean | `false` | Keep only practices whose practice address is in the city you entered. See Limits. |
| `maxRecords` | integer | `200` | Stop after this many records. Maximum 20,000. |
| `parallelSearches` | integer | `4` | How many searches run at once. Leave as-is unless building a very large list. |

### Output fields

| Field | Meaning |
| --- | --- |
| `fullName`, `firstName`, `lastName`, `namePrefix` | The named person: the official for a practice, the clinician for an individual record |
| `jobTitle` | The official's role. Present on practice records |
| `seniority` | `decision_maker` when the job title is a leadership role |
| `credential` | Professional credentials, on individual records |
| `specialty`, `specialtyCode` | The provider's primary specialty |
| `licenseNumber`, `licenseState` | Licence details where published |
| `companyName`, `companyLegalName` | The practice or organisation |
| `registryNumber` | Stable provider ID — use it to de-duplicate and to refresh |
| `recordType` | `organization` or `individual` |
| `phone`, `phoneE164` | The number as published, and in international format |
| `phoneType` | `direct` for the named person's own line, `practice` for the main line |
| `faxNumber` | Fax where published |
| `email` | Always empty — see Limits |
| `secureMessagingAddress` | A clinical data-exchange address, when the provider publishes one. **Not a mailbox — do not send mail to it** |
| `addressLine1`, `addressLine2`, `city`, `region`, `postalCode`, `country` | The practice location |
| `mailingAddressLine1` | Mailing address, when it differs from the practice |
| `isSoleProprietor` | Whether an individual provider is a sole proprietor |
| `status` | `active` or `inactive` |
| `enumeratedAt`, `lastUpdated` | When the record was created and last changed |
| `foundOn`, `sourceUrl`, `collectedAt` | Which search produced the row, and when |

### What coverage to expect

Measured on a live run of 60 records across two specialties in Chicago:

| Field | Coverage |
| --- | --- |
| Name | 100% |
| Phone number | 100% |
| Practice address, city, state, postal code | 100% |
| Specialty | 100% |
| Provider ID | 100% |
| Licence number | 80% |
| Job title | Every practice record (practices only) |
| Credentials | About two thirds of individual records |
| Email address | 0% — never available |

Individual provider records identify the clinician and their practice location, but do not
name an employer, so `companyName` is usually empty on those rows. Practice records carry
the organisation and the named official.

### Pricing

You pay per result, so a small test run costs cents:

- **Provider record** — every row added to your dataset.
- **Named decision maker** — charged only for a practice record that arrives complete with
  the official's name, their job title *and* a direct phone line. You are never charged this
  when one of the three is missing.

A 1,000-row territory list with roughly half of them named decision makers costs about
**$27**. A 60-row test costs well under a dollar.

### Limits & what this actor cannot do

- **No email addresses.** Provider records do not include a mailbox, and this actor will not
  invent one. The `email` field is always empty. Where a provider publishes a clinical
  secure-messaging address it is returned under its own field name — that address rejects
  ordinary mail and must not be used for outreach.
- **United States only.**
- **Each individual search returns at most 1,200 records.** To build a larger list, enter
  several specialties or several cities: each is searched separately with its own maximum.
- **A practice can be returned for a neighbouring city.** A record is matched when *any* of
  its addresses is in the city you searched, so a practice with a Chicago mailing address and
  premises in Addison is returned for Chicago — and the `city` column shows Addison, because
  that is where the practice actually is. On a live two-city run, 7 of 26 rows sat outside the
  cities searched. Turn on `strictCityMatch` for a strict territory list; the run summary
  always reports how many rows fell outside.
- A specialty is matched against the official specialty names, so a broad word can pull in
  related specialties — searching `Cardiology` also returns `Pharmacist, Cardiology`. Check
  the `specialty` column and narrow the term if you need a tighter list.
- A search that is too broad is refused rather than guessed at. A state on its own is not
  enough; the run tells you exactly what to add.
- Details are a snapshot at the moment of collection. Providers move practice, change phone
  numbers and update their own records; `lastUpdated` tells you how fresh each row is.
- Not every row will have every field. The actor returns what it actually found and leaves
  the rest empty rather than filling gaps with placeholders.
- Records that are no longer active are excluded by default, and reported separately rather
  than silently dropped.
- Speed depends on how large the job is; no fixed throughput is promised.
- **You are responsible for using this data lawfully.** These are records about identifiable
  people. You are the data controller for anything you collect, and you must have a lawful
  basis for contacting them and honour opt-outs — including under GDPR, CCPA, CAN-SPAM, the
  TCPA and any professional or do-not-contact rules that apply to healthcare outreach in your
  market. Calling and texting clinicians is regulated in most places; check before you dial.

### FAQ

**Do I need an account or a login for anything?**
No. There is nothing to connect and nothing to sign in to. Enter a specialty and a state and
press start.

**Do I need to connect my own profile or supply any credentials?**
No, and you never will. This actor asks for search terms only.

**Will I get email addresses?**
No. Provider records simply do not contain them, and this actor will not guess an address
and present it as real. If you need work emails, pair this list with an email-finding actor.

**How accurate are the phone numbers?**
Every row that has one carries the number exactly as the provider filed it, in both the
original and international format. Numbers are as current as the provider's own record —
`lastUpdated` shows you when that was.

**Can I schedule it, or run it over a list of cities?**
Yes. Schedule it to refresh a territory on any cadence, and enter multiple specialties or
cities as comma-separated lists to cover a wider area in one run.

**How do I get more than 1,200 records?**
Add more specialties or more cities. Each combination is searched on its own and has its own
maximum, so three specialties across four cities gives you twelve searches.

**How do I keep an existing list up to date?**
Match on `registryNumber`, which is stable per provider, and compare `lastUpdated` to spot
the records that changed since your last run.

# Actor input Schema

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

The provider specialty to search for, for example "Cardiology", "Dentist", "Physical Therapist", "Pharmacist" or "Nursing Home". You can enter several separated by commas — each one is searched on its own, which is also how you collect more than the per-search maximum.

## `state` (type: `string`):

Two-letter US state code, for example "NY", "CA" or "TX". A state on its own is not specific enough — combine it with a specialty, a city or a postal code.

## `city` (type: `string`):

City to search in, for example "Brooklyn". You can enter several separated by commas, and every city is searched separately so you get the full amount from each one.

## `postalCode` (type: `string`):

Narrow the search to a single ZIP code. Useful for tight territory lists.

## `organizationNameContains` (type: `string`):

Only return practices whose name contains this text, for example "Dental" or "Family Care". Leave empty to include every practice.

## `providerType` (type: `string`):

Practices return a named official with their job title and a direct phone number. Individual providers return the clinician, their specialty, credentials and their practice phone number.

## `requirePhone` (type: `boolean`):

Skip any record that has no usable phone number, so every row in your list is callable.

## `activeOnly` (type: `boolean`):

Leave this on to exclude records that are no longer marked active.

## `strictCityMatch` (type: `boolean`):

A practice is matched when any of its addresses is in your city, so some results sit in a neighbouring town. Turn this on to keep only those whose practice address is in the city you entered — useful for tight territory lists.

## `maxRecords` (type: `integer`):

Stop after this many records. Each individual search returns up to 1,200 records, so use several specialties or cities to build a bigger list.

## `parallelSearches` (type: `integer`):

How many searches to run at the same time. Leave as it is unless you are collecting a very large list.

## Actor input object example

```json
{
  "specialty": "Cardiology",
  "state": "NY",
  "city": "",
  "postalCode": "",
  "organizationNameContains": "",
  "providerType": "both",
  "requirePhone": false,
  "activeOnly": true,
  "strictCityMatch": false,
  "maxRecords": 200,
  "parallelSearches": 4
}
```

# Actor output Schema

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

Every lead record this run produced.

## `runSummary` (type: `string`):

What this run collected, and anything it could not reach.

# 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 = {
    "specialty": "Cardiology",
    "state": "NY",
    "city": "",
    "postalCode": "",
    "organizationNameContains": "",
    "providerType": "both",
    "requirePhone": false,
    "activeOnly": true,
    "strictCityMatch": false,
    "maxRecords": 200,
    "parallelSearches": 4
};

// Run the Actor and wait for it to finish
const run = await client.actor("apt_marble/healthcare-provider-leads-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 = {
    "specialty": "Cardiology",
    "state": "NY",
    "city": "",
    "postalCode": "",
    "organizationNameContains": "",
    "providerType": "both",
    "requirePhone": False,
    "activeOnly": True,
    "strictCityMatch": False,
    "maxRecords": 200,
    "parallelSearches": 4,
}

# Run the Actor and wait for it to finish
run = client.actor("apt_marble/healthcare-provider-leads-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 '{
  "specialty": "Cardiology",
  "state": "NY",
  "city": "",
  "postalCode": "",
  "organizationNameContains": "",
  "providerType": "both",
  "requirePhone": false,
  "activeOnly": true,
  "strictCityMatch": false,
  "maxRecords": 200,
  "parallelSearches": 4
}' |
apify call apt_marble/healthcare-provider-leads-scraper --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,apt_marble/healthcare-provider-leads-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/k4eWCwhTD06hNUHIm/builds/bW57tZMPWdFi2inNV/openapi.json
