Med Spa Lead Scraper — Aesthetics Business Contacts
Pricing
from $50.00 / 1,000 base med spa leads
Med Spa Lead Scraper — Aesthetics Business Contacts
Find med spas, Botox clinics and aesthetics businesses by city. Collect public emails, phones, websites and social links. Export CSV or use the n8n HubSpot company template. SMTP-confirmed emails are identified separately.
Pricing
from $50.00 / 1,000 base med spa leads
Rating
0.0
(0)
Developer
Sam Kleespies
Maintained by CommunityActor stats
0
Bookmarked
35
Total users
4
Monthly active users
8 days ago
Last modified
Categories
Share
Med Spa & Aesthetics Lead Scraper
New: Walkthrough: Med spa business leads to HubSpot companies — setup steps and importable n8n JSON below.
Start here: run the 3-lead sample. Inspect the source links, contact fields and verification status before scaling. No external data-provider API key is required; Apify Actor fees and platform usage apply.
Find med spa and aesthetics businesses on Google Maps, collect public business contact details, and export leads as JSON, CSV, or Excel. Website email and social-link extraction are optional. A result is a business listing; it may not contain an email.
Who this helps
- Aesthetics suppliers and software teams: research med spas, Botox clinics or skin-care clinics by city.
- Agencies: segment businesses by service type before assessing whether an offer is relevant.
- Territory research: save separate city tasks and compare exports; this Actor does not provide automatic cross-run change detection.
Quick start
Choose a business type and location, start with 3–10 results, and inspect Full output before scaling. For larger runs, set a maximum cost and timeout in Apify. Use HubSpot or Salesforce output as convenient column layouts; your CRM may require field mapping.
Pricing
The current configured events are $50 per 1,000 base med spa leads ($0.05 per result), $20 per 1,000 SMTP-confirmed email leads ($0.02 extra per qualifying result), and $0.01 per Actor-start event. Apify says the number of start events depends on Actor memory (one per GB, minimum one). At 4 GB, that means four start events, or $0.04. Platform usage is charged separately. The live Pricing tab is authoritative and may reflect plan discounts.
| Example | Result event fees | Start fees at 4 GB | Platform usage |
|---|---|---|---|
| 10 base leads, no confirmed email | $0.50 | $0.04 | Extra |
| 100 base leads, 20 confirmed emails | $5.40 | $0.04 | Extra |
| 100 base leads, 60 confirmed emails | $6.20 | $0.04 | Extra |
These are examples, not a prediction of email coverage. Base records are chargeable even when no email is found. A run may incur platform usage even if it produces no results.
Email verification
DNS MX records establish that a domain has mail routing; they do not confirm a particular address. SMTP-confirmed means the server accepted the recipient and rejected a random-recipient probe at check time. It does not guarantee message delivery. Catch-all servers, blocked SMTP connections, temporary DNS problems, and inconclusive replies stay unconfirmed; they never set emailVerified to true or trigger the premium email event. Inspect emailVerificationStatus and emailDomainHasMx in Full output for the distinction. No emails are sent by the check, and no bounce-rate percentage is promised.
Only results with an smtp_verified address qualify for the $0.02 premium event. The premium event is based on the stricter SMTP-confirmed rule.
Inputs and output
Select a business type such as Med Spa, a city or region, and a total maxResults limit. Custom queries use searchQuery. Set enrichEmails, verifyEmails, and enrichSocials as needed; verification applies only to extracted emails. Choose full, hubspot, or salesforce output. An optional proxyUrl replaces the default Apify proxy.
Results include the business name, address, phone, website, rating, review count, Maps URL, extracted emails and verification evidence, and available social links. Public sites may hide contact details or provide only a form. Google Maps coverage varies by search. Use a small test before a bulk job.
The actor publishes completed batches as it enriches them. If it fails or times out, inspect the run log and partial dataset. Review the output before using it for outreach, and follow the rules applicable to your use.
For support, open an issue with a fresh run link, search settings, expected behavior, and observed result. Keep tokens, proxy credentials, and private customer data out of public issues.
Copyable input for your next run
This is a configuration example, not a guaranteed result count. Start small and set a run-cost limit. Change the business type and location to match your segment.
{"businessType": "Botox Clinic","location": "Miami, FL","maxResults": 10,"enrichEmails": true,"verifyEmails": true,"enrichSocials": true,"outputFormat": "full"}
Related business segments
Use the dedicated Lawyer, Dentist, or Med Spa Actor for the segment you serve. Each has a small sample on its Examples tab.
Walkthrough: Med spa business leads to HubSpot companies
This starter workflow connects this Actor to HubSpot company records using n8n. It starts with ten results and uses your own service credentials. The Actor extracts public business information; email availability varies and the template does not send outreach.
1. Check a small sample
Open the prepared sample, review its settings and current Pricing tab, and inspect the resulting names, source URLs and contact fields. A sample run has normal Apify charges; it is not a promise of free data or a fixed number of matches.
2. Import and connect
Copy the workflow JSON below into a local .json file, then use n8n's Import from File. Alternatively paste the copied JSON onto a blank n8n canvas. The template uses built-in nodes and remains inactive.
Create/select an n8n Header Auth credential for your Apify account: header name Authorization, value Bearer YOUR_APIFY_TOKEN. Assign it to Start Actor, Get run, and Get dataset. Keep secrets in credentials, never in workflow JSON or public issues.
In HubSpot, create a Company property with internal name samstorm_maps_url, field type Single-line text, and Require unique values enabled. This property identifies each location by its Google Maps URL, so two locations sharing a website remain separate companies. If your HubSpot account cannot create a unique property, use the manual export option below.
Create/select a separate n8n Header Auth credential for your HubSpot private app: Authorization: Bearer YOUR_HUBSPOT_TOKEN, with the company read/write scopes required by HubSpot. Assign it ONLY to Upsert HubSpot companies, never to an Apify node. The template uses the company batch-upsert API. It writes name and the unique source key, plus nonempty phone, website and address fields. Matching companies may have those values refreshed; review this behavior on a test company first. It does not create contacts, deals, enrollments or messages.
3. Run once and inspect
In Configure, change businessType and location for your market. Keep outputFormat full, which includes verification evidence. Email enrichment is on; SMTP verification and social enrichment are off initially. Enable them only when needed and review the current price.
Click Execute workflow. The Actor starts once, waits for completion, validates the dataset, and exports. Failed/aborted/timed-out runs and empty datasets stop before the destination write. Duplicate Maps URLs within a batch are removed. HubSpot upserts by the unique Maps URL property. It merges repeated identical URLs, not every possible URL variant for the same real business.
Check your first exported rows against the Apify dataset. Do not interpret MX records, catch-all results or an unconfirmed SMTP check as a verified mailbox. The template marks emailVerified true only when the Actor also reports smtp_verified. No mailbox check guarantees delivery.
4. Repeat without avoidable costs
If export fails after scraping succeeded, copy the 17-character run ID from the Apify run page into existingRunId and execute again. This retries export without launching another paid scrape. Leave it blank when you want a fresh search. The template retrieves up to 101 records and fails above 100; use pagination before scaling beyond 100. Do not point it at a larger historical dataset expecting complete export.
For new prospects, rotate the city or business segment. Repeated searches can return the same listings. Destination deduplication does not make repeated scraping free.
Only after a successful manual test, optionally replace Manual start with an n8n Schedule Trigger. Choose your timezone and cadence, publish the workflow, prevent overlapping executions, and set a monthly budget. Keep existingRunId blank for fresh scheduled searches. Configure an n8n Error Workflow/notification for failed executions. This starter does not create a schedule or send alerts automatically.
Costs and compatibility
Each fresh execution can incur Actor result fees, applicable enrichment fees and separately billed Apify platform usage, plus any n8n/destination subscription costs. The default $2 maxTotalChargeUsd limits Actor event charges, not separately billed platform usage. The Actor timeout is 600 seconds; n8n execution timeout is 900 seconds. Consult the current Pricing tab before scaling.
The template uses HTTP Request 4.2, Code 2, IF 2.2, Wait 1.1. Local tests cover mapping and failure handling; a complete run against your own n8n and HubSpot credentials is required before production use.
Manual export alternative
Run the Actor with full output, open the dataset, and export CSV or Excel. For HubSpot’s manual company import wizard, choose the Actor’s hubspot output format and review the mapping. Business emails are not automatically HubSpot person/contact records. Preserve Maps URLs as source evidence.
Troubleshooting
- 401/403: check which service's credential is selected and its permissions.
- No leads / source failure: inspect the Apify run log and OUTPUT_SUMMARY; try a narrower, known location.
- Destination error: fix the destination and retry using existingRunId.
- Unexpected duplicates: compare Maps URLs, and avoid concurrent writes. Confirm the HubSpot property requires unique values.
- Need help: open an Actor issue with the run link, business type, location and expected/actual behavior. Do not include tokens or private customer data.
References: n8n workflow import, Apify API, HubSpot upsert.
Importable n8n workflow JSON
{"name":"Med spa business leads to HubSpot companies | samstorm","nodes":[{"name":"Manual start","id":"manual-start","type":"n8n-nodes-base.manualTrigger","typeVersion":1,"position":[0,300],"parameters":{}},{"name":"Configure","id":"configure","type":"n8n-nodes-base.code","typeVersion":2,"position":[220,300],"parameters":{"jsCode":"// Edit these values. Existing run ID reuses results without starting a new paid run.\nconst config={\n \"actorId\": \"DZ2im6CoOrIqye8aJ\",\n \"existingRunId\": \"\",\n \"sheetDocumentId\": \"\",\n \"sheetName\": \"Leads\",\n \"actorInput\": {\n \"businessType\": \"Med Spa\",\n \"location\": \"Miami, FL\",\n \"maxResults\": 10,\n \"enrichEmails\": true,\n \"verifyEmails\": false,\n \"enrichSocials\": false,\n \"outputFormat\": \"full\"\n },\n \"maxActorEventChargeUsd\": 2,\n \"timeoutSeconds\": 600\n};\nif(!Number.isInteger(config.actorInput.maxResults) || config.actorInput.maxResults<1 || config.actorInput.maxResults>100) throw new Error('Set maxResults between 1 and 100.');\nif(config.actorInput.outputFormat!=='full') throw new Error('Keep outputFormat full for this mapping.');\nif(config.existingRunId && !/^[a-zA-Z0-9]{17}$/.test(config.existingRunId)) throw new Error('Enter the 17-character Apify run ID, not a URL.');\nreturn [{json:config}];"}},{"name":"Reuse existing run","id":"reuse-existing-run","type":"n8n-nodes-base.if","typeVersion":2.2,"position":[440,300],"parameters":{"conditions":{"options":{"caseSensitive":true,"leftValue":"","typeValidation":"strict","version":2},"conditions":[{"id":"Reuse existing run","leftValue":"={{ !!$json.existingRunId }}","rightValue":"","operator":{"type":"boolean","operation":"true"}}],"combinator":"and"},"options":{}}},{"name":"Start Actor","id":"start-actor","type":"n8n-nodes-base.httpRequest","typeVersion":4.2,"position":[660,440],"parameters":{"authentication":"genericCredentialType","genericAuthType":"httpHeaderAuth","options":{"timeout":75000,"response":{"response":{"responseFormat":"json"}}},"method":"POST","url":"=https://api.apify.com/v2/actors/{{ $json.actorId }}/runs","sendQuery":true,"queryParameters":{"parameters":[{"name":"build","value":"latest"},{"name":"waitForFinish","value":"60"},{"name":"timeout","value":"={{ $json.timeoutSeconds }}"},{"name":"memory","value":"4096"},{"name":"maxTotalChargeUsd","value":"={{ $json.maxActorEventChargeUsd }}"}]},"sendBody":true,"specifyBody":"json","jsonBody":"={{ $json.actorInput }}"},"retryOnFail":false,"maxTries":3,"waitBetweenTries":2000,"notes":"Choose your own Header Auth credential. Authorization: Bearer YOUR_TOKEN. Never embed tokens in this template.","notesInFlow":false},{"name":"Get run","id":"get-run","type":"n8n-nodes-base.httpRequest","typeVersion":4.2,"position":[660,140],"parameters":{"authentication":"genericCredentialType","genericAuthType":"httpHeaderAuth","options":{"timeout":75000,"response":{"response":{"responseFormat":"json"}}},"url":"=https://api.apify.com/v2/actor-runs/{{ $json.runId || $('Configure').first().json.existingRunId }}","sendQuery":true,"queryParameters":{"parameters":[{"name":"waitForFinish","value":"60"}]}},"retryOnFail":true,"maxTries":3,"waitBetweenTries":2000,"notes":"Choose your own Header Auth credential. Authorization: Bearer YOUR_TOKEN. Never embed tokens in this template.","notesInFlow":false},{"name":"Validate run","id":"validate-run","type":"n8n-nodes-base.code","typeVersion":2,"position":[880,300],"parameters":{"jsCode":"const r=$input.first().json.data;\nconst config=$('Configure').first().json;\nif(!r || !/^[a-zA-Z0-9]{17}$/.test(r.id || '') || r.actId!==config.actorId) throw new Error('Unexpected Apify run response or wrong Actor. No records exported.');\nif(!['READY','RUNNING','SUCCEEDED'].includes(r.status)) throw new Error('Apify run '+r.id+' ended '+r.status+'. Inspect its log and partial dataset; no CRM write performed.');\nif(r.status==='SUCCEEDED' && !/^[a-zA-Z0-9]{17}$/.test(r.defaultDatasetId || '')) throw new Error('Succeeded run has no valid dataset ID.');\nreturn [{json:{runId:r.id,datasetId:r.defaultDatasetId || '',complete:r.status==='SUCCEEDED'}}];"}},{"name":"Run succeeded","id":"run-succeeded","type":"n8n-nodes-base.if","typeVersion":2.2,"position":[1100,300],"parameters":{"conditions":{"options":{"caseSensitive":true,"leftValue":"","typeValidation":"strict","version":2},"conditions":[{"id":"Run succeeded","leftValue":"={{ $json.complete }}","rightValue":"","operator":{"type":"boolean","operation":"true"}}],"combinator":"and"},"options":{}}},{"name":"Wait before next check","id":"wait-before-next-check","type":"n8n-nodes-base.wait","typeVersion":1.1,"position":[1100,100],"parameters":{"amount":15,"unit":"seconds"}},{"name":"Get dataset","id":"get-dataset","type":"n8n-nodes-base.httpRequest","typeVersion":4.2,"position":[1320,300],"parameters":{"authentication":"genericCredentialType","genericAuthType":"httpHeaderAuth","options":{"timeout":60000,"response":{"response":{"responseFormat":"json","fullResponse":true}}},"url":"=https://api.apify.com/v2/datasets/{{ $json.datasetId }}/items","sendQuery":true,"queryParameters":{"parameters":[{"name":"format","value":"json"},{"name":"clean","value":"true"},{"name":"limit","value":"101"}]}},"retryOnFail":true,"maxTries":3,"waitBetweenTries":2000,"notes":"Choose your own Header Auth credential. Authorization: Bearer YOUR_TOKEN. Never embed tokens in this template.","notesInFlow":false},{"name":"Validate and map leads","id":"validate-and-map-leads","type":"n8n-nodes-base.code","typeVersion":2,"position":[1540,300],"parameters":{"jsCode":"const payload=$input.first().json;\nconst rows=payload.body;\nif(!Array.isArray(rows)) throw new Error('Expected a JSON dataset array. Check the Get dataset node response settings.');\nif(!rows.length) throw new Error('No leads returned. Review the Apify log and OUTPUT_SUMMARY before changing the search.');\nif(rows.length>100) throw new Error('This starter workflow supports at most 100 records; use dataset pagination for larger runs.');\nconst seen=new Set();\nconst result=[];\nconst runId=$('Validate run').last().json.runId;\nconst text=v=>typeof v==='string'?v.trim():'';\nfor(const row of rows){\n const name=text(row.name), mapsUrl=text(row.mapsUrl).split('#')[0];\n if(!name || !/^https:\\/\\/(?:[a-z0-9-]+\\.)?google\\.[a-z.]+\\/maps(?:\\/|\\?)/i.test(mapsUrl)) throw new Error('A record lacks a business name or valid Google Maps source. Nothing in this batch was exported.');\n if(seen.has(mapsUrl)) continue;\n seen.add(mapsUrl);\n const status=text(row.emailVerificationStatus);\n const email=text(row.email);\n result.push({json:{mapsUrl,name,category:text(row.category),address:text(row.address),phone:text(row.phone),website:text(row.website),email,\n emailVerified:!!email && row.emailVerified===true && status==='smtp_verified',\n emailVerificationStatus:status || (email?'not_checked':'no_email'),emailVerificationNote:text(row.emailVerificationNote),\n emailDomainHasMx:typeof row.emailDomainHasMx==='boolean'?row.emailDomainHasMx:'',\n rating:typeof row.rating==='number'?row.rating:'',reviewCount:typeof row.reviewCount==='number'?row.reviewCount:'',sourceRunId:runId,capturedAt:new Date().toISOString()}});\n}\nreturn result;"}},{"name":"Build company batch","id":"build-company-batch","type":"n8n-nodes-base.code","typeVersion":2,"position":[1760,300],"parameters":{"jsCode":"const inputs=$input.all().map(({json:r})=>{\n const properties={samstorm_maps_url:r.mapsUrl,name:r.name};\n for(const key of ['phone','website','address']) if(r[key]) properties[key]=r[key];\n return {id:r.mapsUrl,idProperty:'samstorm_maps_url',properties};\n});\nif(!inputs.length || inputs.length>100) throw new Error('Expected 1–100 company records.');\nreturn [{json:{inputs}}];"}},{"name":"Upsert HubSpot companies","id":"upsert-hubspot-companies","type":"n8n-nodes-base.httpRequest","typeVersion":4.2,"position":[1980,300],"parameters":{"authentication":"genericCredentialType","genericAuthType":"httpHeaderAuth","options":{"timeout":75000,"response":{"response":{"responseFormat":"json"}}},"method":"POST","url":"https://api.hubapi.com/crm/v3/objects/companies/batch/upsert","sendBody":true,"specifyBody":"json","jsonBody":"={{ $json }}"},"retryOnFail":false,"maxTries":3,"waitBetweenTries":2000,"notes":"Choose your own Header Auth credential. Authorization: Bearer YOUR_TOKEN. Never embed tokens in this template.","notesInFlow":false},{"name":"Check HubSpot result","id":"check-hubspot-result","type":"n8n-nodes-base.code","typeVersion":2,"position":[2200,300],"parameters":{"jsCode":"const r=$input.first().json;\nconst expected=$('Build company batch').first().json.inputs.length;\nif(r.status!=='COMPLETE' || (r.numErrors || 0)>0 || (r.errors || []).length || !Array.isArray(r.results) || r.results.length!==expected) throw new Error('HubSpot returned an incomplete batch. Inspect this response before retrying with the existing Apify run ID.');\nreturn [{json:{status:'exported',companies:r.results.length,runId:$('Validate run').last().json.runId}}];"}},{"name":"Setup and limits","id":"setup-and-limits","type":"n8n-nodes-base.stickyNote","typeVersion":1,"position":[0,-300],"parameters":{"content":"## Med spa business leads to HubSpot companies\n1. Read the setup walkthrough on the Actor README.\n2. Assign YOUR Apify Header Auth to the three Apify HTTP nodes.\n3. Create a unique company property samstorm_maps_url in HubSpot. Assign YOUR separate HubSpot Header Auth to Upsert HubSpot companies.\n4. Review Configure, then test manually.\n\nDefaults: 10 results, verification off, 600-second Actor timeout. $2 is an Actor-event charge limit; separately billed platform usage still applies. Failed/empty runs stop export.\n\nTo retry export after a destination error, set existingRunId. Do not restart the Actor unnecessarily.\n\nNo automatic schedule. Prevent concurrent executions against the same destination.\nValidation: local control-flow/mapping tests; customer credential setup and live destination test required.","width":560,"height":520}}],"connections":{"Manual start":{"main":[[{"node":"Configure","type":"main","index":0}]]},"Configure":{"main":[[{"node":"Reuse existing run","type":"main","index":0}]]},"Reuse existing run":{"main":[[{"node":"Get run","type":"main","index":0}],[{"node":"Start Actor","type":"main","index":0}]]},"Start Actor":{"main":[[{"node":"Validate run","type":"main","index":0}]]},"Get run":{"main":[[{"node":"Validate run","type":"main","index":0}]]},"Validate run":{"main":[[{"node":"Run succeeded","type":"main","index":0}]]},"Run succeeded":{"main":[[{"node":"Get dataset","type":"main","index":0}],[{"node":"Wait before next check","type":"main","index":0}]]},"Wait before next check":{"main":[[{"node":"Get run","type":"main","index":0}]]},"Get dataset":{"main":[[{"node":"Validate and map leads","type":"main","index":0}]]},"Validate and map leads":{"main":[[{"node":"Build company batch","type":"main","index":0}]]},"Build company batch":{"main":[[{"node":"Upsert HubSpot companies","type":"main","index":0}]]},"Upsert HubSpot companies":{"main":[[{"node":"Check HubSpot result","type":"main","index":0}]]}},"active":false,"settings":{"executionOrder":"v1","executionTimeout":900,"saveDataErrorExecution":"all","saveDataSuccessExecution":"none"},"pinData":{},"tags":[]}
