Med Spa Lead Discovery - Emails, Scores & Personalization Hooks
Pricing
Pay per event
Med Spa Lead Discovery - Emails, Scores & Personalization Hooks
Find med spa leads with emails, lead scores, and personalized outreach hooks. Built for agencies and SaaS companies targeting aesthetic clinics.
Pricing
Pay per event
Rating
0.0
(0)
Developer
George Kioko
Actor stats
0
Bookmarked
2
Total users
1
Monthly active users
5 days ago
Last modified
Categories
Share
MedSpa Lead Discovery PPE
Budget-aware lead discovery engine for MedSpa and medical aesthetics businesses. Finds outreach-ready leads with ICP scoring, decision maker identification, and personalization signals -- all with pay-per-event pricing so you only pay for results.
How It Works
flowchart LRA["Keywords\n+ Locations"] --> B["Discovery\n(Search + Directories)"]B --> C["Domain\nProbing"]C --> D["Deep\nCrawl"]D --> E["Lead\nExtraction"]E --> F["ICP\nScoring"]F --> G["Classification"]G --> H["Outreach-Ready\nLeads"]style A fill:#4CAF50,color:#fffstyle H fill:#2196F3,color:#fffstyle F fill:#FF9800,color:#fffstyle G fill:#9C27B0,color:#fff
The actor follows a multi-stage pipeline:
- Discovery -- Searches Google, Bing, and MedSpa directories using your keywords and locations
- Domain Probing -- Validates discovered domains are live, relevant, and worth crawling
- Deep Crawling -- Uses Cheerio crawler to extract structured data from each domain (about pages, team pages, contact pages, service pages)
- Lead Extraction -- Pulls business name, address, phone, email, social profiles, services offered, and team members
- ICP Scoring -- Scores each lead against your Ideal Customer Profile (services match, location, size signals, online presence)
- Quality Scoring -- Rates data completeness, contact info availability, and freshness
- Classification -- Labels each lead as
outreachReady,needsManualReview, orrejected - Output -- Delivers structured LeadRecords with personalization hooks for your outreach
Architecture
flowchart TBsubgraph InputKW["Keywords"]LOC["Locations"]SEED["Seed URLs / CSV"]endsubgraph Core["Actor Core"]BG["BudgetGovernor\n(cost control)"]DA["Discovery Adapters\n(Search, Directories, Seeds)"]PROBE["Probe Crawler\n(domain validation)"]DEEP["Deep Crawler\n(Cheerio-based extraction)"]SC["Scorer\n(ICP + Quality)"]CL["Classifier\n(readiness labeling)"]endsubgraph OutputREADY["Outreach-Ready\nLeads"]REVIEW["Needs Manual\nReview"]REJ["Rejected"]endKW --> DALOC --> DASEED --> DADA --> BGBG --> PROBEPROBE --> DEEPDEEP --> SCSC --> CLCL --> READYCL --> REVIEWCL --> REJstyle BG fill:#E53935,color:#fffstyle SC fill:#FF9800,color:#fffstyle CL fill:#9C27B0,color:#fffstyle READY fill:#4CAF50,color:#fff
BudgetGovernor tracks spend in real time and halts crawling when your budget ceiling is reached. You never get a surprise bill.
Pricing
| Event | Price | When Charged |
|---|---|---|
| Outreach-ready lead | $0.01 | Each lead classified as outreachReady with verified contact info |
| Decision maker contact | $0.005 | Bonus when a lead includes direct contact for an owner, CEO, manager, or director |
Example: 100 outreach-ready leads with 40 decision maker contacts = $1.00 + $0.20 = $1.20 total
You pay nothing for leads classified as needsManualReview or rejected. Zero upfront cost -- pure pay-per-result.
Safety Controls
- One base charge per
leadIdper run (no duplicate billing) maxBudgetUsdhard ceiling enforced by BudgetGovernor- Zero charges when a lead does not pass billing quality gates
- Full billing telemetry in
RUN_SUMMARY.billingMeta
Use Cases
mindmaproot((MedSpa Lead\nDiscovery))MedSpa AgenciesClient prospectingTerritory mappingCompetitive analysisHealthcare MarketingCampaign targetingMarket researchService gap analysisLocal Lead GenCity-by-city sweepsNew market entryFranchise prospectingOutreach AutomationCRM enrichmentEmail campaign listsPersonalized sequences
Input Schema
| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
keywords | string[] | No* | ["med spa", "medical spa", "medspa"] | Search terms for discovery |
locations | string[] | No* | -- | Geographic targets (e.g., ["Miami FL", "Los Angeles CA"]) |
seedUrls | string[] | No | [] | Direct URLs to include (skips discovery phase) |
seedCsvText | string | No | -- | Raw CSV text with seed domains for batch ingestion |
seedCsvUrlColumn | string | No | -- | Column name in CSV containing URLs |
seedCsvDomainColumn | string | No | -- | Column name in CSV containing domains |
maxLeads | number | No | 100 | Maximum outreach-ready leads to return |
mode | string | No | "balanced" | Crawl mode: fast, balanced, or high_precision |
qualityThreshold | number | No | 0.6 | Minimum quality score (0-1) for outreach-ready classification |
icpThreshold | number | No | 0.5 | Minimum ICP score (0-1) for outreach-ready classification |
maxBudgetUsd | number | No | 5.0 | Budget ceiling in USD (BudgetGovernor enforced) |
maxPagesPerDomainProbe | number | No | -- | Max pages crawled during probe phase |
maxPagesPerDomainDeep | number | No | -- | Max pages crawled during deep phase |
maxJsRenders | number | No | -- | Cap on JavaScript-rendered pages (controls cost) |
maxRuntimeSeconds | number | No | -- | Hard runtime limit |
includeRejected | boolean | No | false | Include rejected leads in output (useful for debugging) |
enableDecisionMakerSearch | boolean | No | true | Deep-search for owner/CEO/manager contacts |
*At least locations or seedUrls is required.
Mode Comparison
| Mode | Speed | Depth | Best For |
|---|---|---|---|
fast | Fastest | Homepage + contact page only | Quick prospecting sweeps, lowest cost |
balanced | Medium | Homepage + about + team + contact + services | General production use |
high_precision | Slowest | Full site crawl (up to 20 pages/domain) | Maximum data quality on harder domains |
Example Input
{"keywords": ["med spa", "medical spa", "aesthetic clinic"],"locations": ["Miami FL", "Fort Lauderdale FL", "West Palm Beach FL"],"mode": "balanced","maxLeads": 50,"maxBudgetUsd": 3.0}
Output Schema
Each lead in the output dataset is a LeadRecord with the following structure:
| Field | Type | Description |
|---|---|---|
businessName | string | Company name |
domain | string | Website domain |
url | string | Primary URL crawled |
address | object | { street, city, state, zip, country } |
phone | string | Primary phone number |
email | string | Primary contact email |
emails | object | { general, decisionMaker: { email, name, title, source } } |
socialProfiles | object | { facebook, instagram, linkedin, tiktok, youtube } |
services | string[] | Services offered (Botox, fillers, laser, CoolSculpting, etc.) |
brandAffiliations | string[] | Brand partnerships (Allergan, Galderma, etc.) |
teamMembers | object[] | [{ name, title, email, phone, isDecisionMaker }] |
decisionMaker | object | { name, title, email, phone } (if found) |
nicheVerification | object | { score, positiveSignals[], negativeSignals[], verdict } |
icpScore | number | Ideal Customer Profile fit (0-1) |
qualityScore | number | Data completeness score (0-1) |
readinessClass | string | outreachReady, needsManualReview, or rejected |
rejectionReasons | string[] | Why a lead was rejected (when applicable) |
personalizationHooks | string[] | Source-backed talking points for outreach |
painSignals | string[] | Issues detected (slow website, no booking system, old blog) |
serviceGaps | string[] | Services competitors offer that this lead does not |
brandVoiceSnippets | string[] | Tone/language clues for matching outreach voice |
bookingFlowSignal | string | Online booking status detected |
discoverySource | string | How the lead was found (google_search, directory, seed) |
crawledAt | string | ISO timestamp of extraction |
Example Output
{"businessName": "Glow Aesthetics Miami","domain": "glowaestheticsmiami.com","url": "https://glowaestheticsmiami.com","address": { "street": "1455 Brickell Ave, Suite 220", "city": "Miami", "state": "FL", "zip": "33131" },"phone": "+1-305-555-0234","emails": {"general": "hello@glowaestheticsmiami.com","decisionMaker": {"email": "dr.santos@glowaestheticsmiami.com","name": "Dr. Maria Santos","title": "Medical Director & Owner","source": "team_page"}},"services": ["Botox", "Juvederm Fillers", "Laser Hair Removal", "Chemical Peels", "Microneedling"],"brandAffiliations": ["Allergan", "Galderma"],"icpScore": 0.87,"qualityScore": 0.91,"readinessClass": "outreachReady","personalizationHooks": ["No online booking system detected -- competitors in Brickell all have one","Google reviews average 3.9 stars, below the Miami medspa average of 4.4"],"painSignals": ["Website loads in 6.8s (poor)", "Last blog post is 8 months old"],"discoverySource": "google_search","crawledAt": "2026-03-22T09:45:12Z"}
Run Summary
Each run also produces a RUN_SUMMARY in the key-value store with efficiency KPIs:
candidatesDiscovered/domainsAfterDedupe/outreachReadyCountpagesPerOutreachReadyLead/requestsPerOutreachReadyLead- Rejection reason distribution
billingMetawith charge counts and skip reasons
Scoring Explained
ICP Score (0-1)
The Ideal Customer Profile score measures how well a lead matches the typical high-value MedSpa prospect:
| Signal | Weight | What It Measures |
|---|---|---|
| Services breadth | 25% | Number and variety of aesthetic services offered |
| Location match | 20% | Proximity to your target locations |
| Online presence | 20% | Website quality, social media activity, review count |
| Size indicators | 15% | Multi-location, team size, facility indicators |
| Tech adoption | 10% | Online booking, modern website, active social |
| Engagement signals | 10% | Blog activity, promotions, events |
Quality Score (0-1)
The quality score rates data completeness and reliability:
| Signal | Weight | What It Measures |
|---|---|---|
| Contact info completeness | 30% | Phone + email + address all present |
| Decision maker identified | 25% | Owner/CEO/manager name and contact found |
| Data freshness | 20% | Website recently updated, active social presence |
| Verification level | 15% | Email format validated, phone format checked |
| Enrichment depth | 10% | Services listed, team page found, about page present |
Niche Verification
Before deep crawling, every domain is scored against curated signal lists:
- Positive signals: "botox", "filler", "aesthetic nurse", "medical director", "medspa"
- Negative signals: "nail salon", "hair stylist", "massage only", "day spa"
- Domains below the niche threshold are rejected early, saving crawl budget
Classification Rules
| Class | Criteria |
|---|---|
outreachReady | ICP score >= threshold AND quality score >= threshold AND at least one verified contact method |
needsManualReview | One score below threshold but the other is high, OR contact info is partial |
rejected | Both scores below threshold, OR domain fails niche verification, OR no contact info found |
Rejected leads include explicit rejectionReasons so you can understand why and adjust thresholds if needed.
AI Agent Integration
JavaScript (Apify Client)
import { ApifyClient } from 'apify-client';const client = new ApifyClient({ token: 'YOUR_API_TOKEN' });const run = await client.actor('george.the.developer/medspa-lead-discovery-ppe').call({keywords: ['med spa', 'medical aesthetics', 'botox clinic'],locations: ['Miami FL', 'Fort Lauderdale FL'],maxLeads: 100,mode: 'balanced',qualityThreshold: 0.7,icpThreshold: 0.6,maxBudgetUsd: 3.0,});const { items } = await client.dataset(run.defaultDatasetId).listItems();const outreachReady = items.filter(lead => lead.readinessClass === 'outreachReady');console.log(`Found ${outreachReady.length} outreach-ready MedSpa leads`);for (const lead of outreachReady) {console.log(`${lead.businessName} | ${lead.emails?.general} | ICP: ${lead.icpScore}`);console.log(` Hook: ${lead.personalizationHooks?.[0]}`);}
Python (Apify Client)
from apify_client import ApifyClientclient = ApifyClient("YOUR_API_TOKEN")run = client.actor("george.the.developer/medspa-lead-discovery-ppe").call(run_input={"keywords": ["med spa", "medical aesthetics", "botox clinic"],"locations": ["Miami FL", "Fort Lauderdale FL"],"maxLeads": 100,"mode": "balanced","qualityThreshold": 0.7,"icpThreshold": 0.6,"maxBudgetUsd": 3.0,})items = client.dataset(run["defaultDatasetId"]).list_items().itemsoutreach_ready = [lead for lead in items if lead["readinessClass"] == "outreachReady"]print(f"Found {len(outreach_ready)} outreach-ready MedSpa leads")for lead in outreach_ready:print(f"{lead['businessName']} | {lead['emails']['general']} | ICP: {lead['icpScore']}")
cURL
# Start the actor runcurl -X POST "https://api.apify.com/v2/acts/george.the.developer~medspa-lead-discovery-ppe/runs" \-H "Authorization: Bearer YOUR_API_TOKEN" \-H "Content-Type: application/json" \-d '{"keywords": ["med spa", "medical aesthetics"],"locations": ["Los Angeles CA"],"maxLeads": 50,"mode": "balanced","maxBudgetUsd": 3.0}'# Fetch results as JSONcurl "https://api.apify.com/v2/datasets/DATASET_ID/items?format=json" \-H "Authorization: Bearer YOUR_API_TOKEN"# Fetch results as CSV for CRM importcurl "https://api.apify.com/v2/datasets/DATASET_ID/items?format=csv" \-H "Authorization: Bearer YOUR_API_TOKEN" \-o medspa_leads.csv
FAQ
Q: How many leads can I expect per run?
A: It depends on your keywords, locations, and mode. A balanced run for a major US metro typically finds 30-80 outreach-ready leads. Use high_precision mode for maximum coverage.
Q: What if I hit my budget ceiling mid-run?
A: The BudgetGovernor stops crawling immediately and returns all leads discovered up to that point. You will never be charged more than maxBudgetUsd.
Q: Can I use seed URLs instead of keywords?
A: Yes. Pass URLs via seedUrls or batch domains via seedCsvText. The actor skips the discovery phase and goes straight to domain probing and deep crawling. Great for enriching an existing list with quality scores and personalization hooks.
Q: How does niche verification work? A: The actor scores each discovered website against curated lists of positive signals (e.g., "botox", "filler", "medical director") and negative signals (e.g., "nail salon", "hair stylist"). Sites that fail the niche check are rejected before deep crawl, saving budget.
Q: What counts as a "decision maker"? A: Owners, CEOs, founders, medical directors, practice managers, and office managers. The actor identifies them from team pages, about pages, LinkedIn references, and structured data.
Q: Do I get charged for rejected leads?
A: No. You only pay for leads classified as outreachReady. The decision maker bonus is only charged when a decision maker contact is actually found on an outreach-ready lead.
Q: Can I integrate this into my CRM workflow?
A: Absolutely. Use the Apify API or webhooks to push results directly into HubSpot, Salesforce, or any CRM. Export as CSV with ?format=csv for direct import.
Q: What is the difference between PPE and Pro? A: Both versions use the same discovery engine and the same pricing. PPE (Pay Per Event) is the original release. MedSpa Lead Discovery Pro is the same actor with professional branding.
Q: Is this compliant with data privacy regulations? A: This actor performs discovery and enrichment only. It does not send any outbound communications. You are responsible for lawful contact sourcing and required consent/opt-out handling in your outbound systems.