# Bulk Website Contact Extractor & Email Finder (`harvestlab/contact-extractor`) Actor

Extract public emails, phones, social profiles and contact pages from company domains or website URLs. Enable CRM exports for deduplicated company and selected-contact CSV files plus JSON merge instructions. Optional email checks, phone formatting and tech-stack signals.

- **URL**: https://apify.com/harvestlab/contact-extractor.md
- **Developed by:** [harvestlab](https://apify.com/harvestlab) (community)
- **Categories:** Lead generation, Business
- **Features:** Limited permissions
- **Stats:** 260 total users, 12 monthly users, 100.0% runs succeeded, 2 bookmarks
- **User rating**: No ratings yet

## Pricing

from $0.90 / 1,000 lead discovereds

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

## Bulk Website Contact Extractor & Email Finder

Use this bulk website contact extractor and email finder to turn company domains or URLs into records with public emails, phone numbers, social profiles, addresses, contact-page URLs, and optional tech-stack signals. The Console starter reads one public contact page with phone formatting and enrichment switched off. If `validatePhones` is omitted from an API input, phone formatting is enabled for compatibility and costs $0.02 per qualifying number at the no-discount base price, in addition to the website event; set it to `false` for raw-phone discovery only. Public contact details are not permission to send unsolicited messages.

**Plan discounts:** Starter (Bronze), Scale (Silver), and Business (Gold) receive 20%, 30%, and 40% off ordinary events. Basic discovery is $1.50 / $1.20 / $1.05 / $0.90 per 1,000 useful sites at base / Starter / Scale / Business prices. Other single prices and examples below use the no-discount base unless labelled otherwise; see the [full event table](#pricing). Discounts follow your Apify plan, not the number of sites in a run.

[Run the ready-made B2B lead task](https://apify.com/harvestlab/contact-extractor/examples/get-b2b-leads-from-company-websites) to create company and contact CSV files from a small website list. Its starter disables paid phone formatting, verification, tech detection, and AI. Open the configurable Actor for larger batches or optional enrichment.

| First-run question | Answer |
| --- | --- |
| What should I enter? | Start with the prefilled public contact-page URL, then replace it with your own. Clear that URL before entering a domain list. |
| What comes back? | One website record with public contacts and source context. Enable `exportCrm` for separate deduplicated company and selected-contact CSV files, plus JSON merge instructions. |
| What does it cost? | At no-discount base prices, this starter uses basic discovery at $0.0015 per useful site with enrichment and phone formatting off. Enabling phone formatting adds $0.02 per qualifying phone. Tech/MX/SMTP enrichment uses a $0.01 website event, with checks charged separately. Eligible Apify plans receive the discounts above. |
| What if a site is unreachable? | The Actor returns a clear no-charge diagnostic instead of billing it as a contact result. |

**Best first run**

Start with a direct page that publishes contact details. A homepage can return useful social links without any email address; an empty email list does not mean that the rest of the site has no contacts.

```json
{
  "urls": ["https://www.python.org/about/help/"],
  "maxWebsites": 1,
  "maxPagesPerSite": 1,
  "includeSubpages": false,
  "detectTechStack": false,
  "validatePhones": false,
  "verifyEmails": false,
  "deepEmailVerification": false,
  "enableAiAnalysis": false
}
```

To create import files, add `exportCrm: true` or use the B2B lead task above. Published emails remain unverified unless you enable a check. For your own homepage or domain list, increase `maxPagesPerSite` and enable `includeSubpages` to look for contact and about pages; the one-page starter starts from the supplied contact page and skips linked subpages.

Use this actor when you need a website contact extractor, email finder from website, contact details extractor, B2B lead extractor, or CRM enrichment workflow before outreach, market mapping, recruiting, or data cleanup. Start with a small run to confirm the target domain works, then turn on `verifyEmails`, `deepEmailVerification`, or `enableAiAnalysis` only after the basic run works.

**Product focus**

- **Best for:** company-domain enrichment, website contact extraction, email finder from website lists, CRM cleanup, ABM account research, recruiting research, and tech-stack segmentation.
- **Not best for:** guaranteed personal emails, logged-in databases, hidden contacts, or bulk spam list building.
- **Why choose it:** one run returns contact details plus context your CRM actually needs: source URL, contact-page URL, validated phones, socials, optional email checks, tech-stack signals, and Shopify storefront context when confidently detected.
- **Resilience:** if a homepage is blocked or empty, the actor tries canonical host variants, sitemap contact URLs, and common contact/about/legal paths before returning a no-charge diagnostic.

**What you get back**

- One row per submitted site with normalized `url`, discovered `emails`, `phones_validated`, `social_profiles`, `address`, `contact_page_url`, and `tech_stack`.
- When `detectTechStack` is enabled and Shopify is confidently detected, the row can add `is_shopify_store`, detection confidence and signals, store name, canonical URL, currency, and business country. These fields are omitted for non-Shopify sites and inconclusive detections.
- `result_type` shows whether the row contains `contact_data`, a no-charge `partial_result`, a no-charge `fetch_failed`, `site_timeout`, or `site_error` diagnostic. `fetch_diagnostics` lists attempted/reached URLs, timeout details, or sanitized per-site error details for debugging blocked and slow sites.
- Optional `emails_verified` and `ai_contact_analysis` fields appear only when the paid verification or AI toggles are enabled. `maxEmailsToVerify` caps paid email verification checks across the run.
- Good starter run: 5 pages per site, tech detection on, verification off. Then re-run only promising domains with email verification enabled.

Example output shape:

```json
{
  "url": "https://example.com",
  "domain": "example.com",
  "result_type": "contact_data",
  "billable_contact_found": true,
  "emails": ["sales@example.com", "support@example.com"],
  "phones_validated": [
    {"raw": "+1 415 555 0100", "e164": "+14155550100", "country": "US", "type": "FIXED_LINE", "valid": true}
  ],
  "social_profiles": {
    "linkedin": "https://www.linkedin.com/company/example",
    "github": "https://github.com/example"
  },
  "contact_page_url": "https://example.com/contact",
  "tech_stack": ["Shopify", "Cloudflare", "Google Analytics"],
  "is_shopify_store": true,
  "shopify_detection_confidence": "high",
  "shopify_detection_signals": ["response-header", "shopify-store-identity", "shopify-asset-host"],
  "shopify_store_name": "Example Store",
  "shopify_canonical_url": "https://example.com/",
  "shopify_currency": "USD",
  "shopify_country": "US",
  "fallback_used": false,
  "fetch_diagnostics": {
    "pages_attempted": ["https://example.com", "https://example.com/contact"],
    "pages_reached": ["https://example.com", "https://example.com/contact"]
  },
  "scraped_at": "2026-05-10T18:15:00Z"
}
```

**Best for:** website contact extraction, email finder from website/domain lists, B2B prospect lists, CRM cleanup, tech-stack segmentation, contact-page discovery, and fresh lead enrichment from company domains.

**Not best for:** private databases, logged-in pages, websites that intentionally hide contact details, or guaranteed personal decision-maker emails on every domain.

> **No cookies, no OAuth, no API keys (for the scraping)** - runs on public data only. AI keys are optional and only needed when `enableAiAnalysis: true`.

**Pay-per-event pricing. Email checks and phone formatting are optional.** At no-discount base prices, basic discovery costs $0.0015 per useful site and enriched contact records cost $0.01. Qualifying email checks and phone formatting have separate additive charges. MX results concern the email domain; SMTP verdicts are mailbox acceptance signals, not a guarantee of message delivery. No Actor rental fee or monthly minimum.

### Website Contact Extractor

Give the actor a list of domains or URLs and it crawls each public website for contact details that are visible on the homepage and high-signal subpages such as contact, about, imprint, legal, and team pages. Results are normalized into one row per website, with raw contacts separated from email-check results, formatted phones, social profiles, address data, contact-page URLs, and technology signals.

### Contact Details Extractor

Extract public contact details from company websites in a format your CRM can use immediately: email addresses, phone numbers, social links, physical addresses, contact-page URLs, and technology signals. Use it for contact discovery, enrichment, deduplication, and fresh lead records before outreach.

### Email Finder from Website

Use it as an email finder from website and domain lists when you need fresh outreach data before a campaign. The actor finds public role, team, and company addresses, adds optional deliverability checks, keeps contact-page context, and returns each website as a structured row that can move directly into a CRM, spreadsheet, or enrichment pipeline.

### Shopify Store Contact Enrichment

Enable `detectTechStack` for Shopify store URLs already in your domain list. When strong public evidence identifies a Shopify storefront, the contact row can add the positive detection flag, confidence and evidence signals, public store name, canonical URL, currency, and business country when exposed in top-level Store or Organization metadata. Inconclusive and non-Shopify sites receive no Shopify fields. The Actor reuses the page fetched for contact extraction; it does not discover stores, request a product catalog, or access private Shopify APIs.

### B2B Lead Extractor

Extract published emails, phone numbers, social profiles and addresses from accessible public websites. Add optional email checks, formatted phones and tech-stack signals, then export company and selected-contact files for CRM import. Built for SDRs, RevOps teams, recruiters, ABM marketers and M\&A researchers working from domain or URL lists; coverage depends on what each website exposes.

For each domain you provide, the actor checks the homepage and high-signal subpages such as `/contact`, `/about`, and `/imprint`; extracts public emails and business contact data; formats phone numbers when enabled; identifies social profiles; parses physical addresses; and can add tech-stack signals, email checks and AI role classification. The dataset is structured JSON. Enable `exportCrm` for company and selected-contact CSV files plus JSON merge instructions; import them using your destination's field mapping and authorized credentials. The export does not write to a CRM.

### Features

- **Batch-safe runs** - Each website is handled independently, so one slow or malformed target does not wipe out the whole batch. Completed results are preserved whenever possible.
- **Deep mailbox verification (v1.7)** - Opt in with `deepEmailVerification: true` to classify MX-cleared emails as **`deliverable`**, **`undeliverable`**, **`catchall`**, **`greylisted`**, **`port_blocked`**, **`rate_limited`**, or **`error`**. The check is rate-limited per mail host, includes catch-all detection, and charges $0.02 only for definitive deliverable or undeliverable verdicts. Requires `verifyEmails: true`.
- **Email-domain checks (v1.6)** - Opt in with `verifyEmails: true` to inspect extracted email domains for MX records, common typos, free-inbox and disposable hosts, and **HIGH / MEDIUM / LOW / UNKNOWN** deliverability tiers. Results appear in `emails_verified`. The $0.01 event qualifies only when `mx_valid` is boolean `true`, the tier is HIGH or MEDIUM, and there is no typo or disposable flag. LOW and UNKNOWN results are free even when MX records resolve. Domain checks do not prove that an individual mailbox exists.
- **Phone validation (v1.5)** - Extracts phone numbers in international formats, then normalizes valid numbers into `phones_validated` with E.164 format, country code, and line type. Numbers that parse but fail strict regional checks end up in `phones_uncertain` so you can triage them separately.
- **Social media profiles (16 platforms)** - Identifies LinkedIn, Twitter/X, Facebook, Instagram, YouTube, GitHub, TikTok, Pinterest, Telegram, Discord, Threads, **WhatsApp**, **Snapchat**, **Vimeo**, **Twitch**, and **Mastodon** profiles linked from the website. (v1.9: +5 platforms - parity with vdrmota's 15+ claim, plus Mastodon.)
- **Physical address parsing** - Locates public business addresses and returns them alongside the contact record.
- **Contact-page URL capture** - Returns the canonical /contact or /kontakt page URL so you can deep-link prospects straight into the form.
- **Tech stack detection (175+ signals)** - Identifies technologies across CMS platforms, frontend frameworks, analytics tools, chat widgets, CDNs, and e-commerce solutions.
- **Shopify storefront context** - With `detectTechStack: true`, confidently detected Shopify sites can include public store name, canonical URL, currency, and business country from pages the Actor already fetched. Negative and inconclusive detections add no Shopify fields.
- **Smart subpage crawling** - Automatically visits /contact, /about, /imprint, and similar pages where contact information typically lives.
- **Structured business data capture** - Captures public company details that websites publish for search engines and business directories.
- **Domain input support** - Enter bare domains like "example.com" without needing to add the protocol. HTTPS is added automatically.
- **Duplicate handling** - Automatically deduplicates when the same URL appears in both `urls` and `domains` inputs.
- **Optional AI contact enrichment** - When enabled (`enableAiAnalysis`), AI classifies each email by role (sales, support, hr, legal, executive, general, personal), groups near-duplicate team addresses (`dedup_groups`), flags non-monitored no-reply inboxes (`deliverability_flags`), picks the single best `primary_contact` for B2B outreach, and picks the single best `primary_phone` from the validated set. Supports OpenRouter, Anthropic, Google AI, OpenAI, or self-hosted Ollama.

#### What makes this different from other Apify contact scrapers

- **16 social platforms vs 6** - LinkedIn (with company-profile-over-personal selection), Twitter/X, Facebook, Instagram, YouTube, GitHub, TikTok, Pinterest, Telegram, Discord, Threads, WhatsApp, Snapchat, Vimeo, Twitch, Mastodon. Competitors typically stop at the first six. (v1.9 expanded 11 -> 16.)
- **Phone format split** - `phones_validated` gives you E.164 + country + line-type per number. Most Apify contact scrapers ship a single raw `phones` list and leave cleanup to you.
- **MX + mailbox deliverability** - every email can be checked against DNS MX records and, when deep verification is enabled, mailbox-level acceptance signals. Outputs include HIGH / MEDIUM / LOW / UNKNOWN deliverability, typo suggestions, disposable-host detection, and mailbox verdicts such as `deliverable`, `undeliverable`, `catchall`, `greylisted`, and `port_blocked`.
- **175+ tech-stack detectors** on the same fetch - one run gets you contacts + CMS + analytics + chat + CRM + payment stacks. Competitors either do contacts *or* tech but rarely both.
- **Integrated AI enrichment** - role classification, dedup, primary-contact + primary-phone pick, and deliverability flags.
- **Multiple AI provider choices** - Use hosted providers or a self-hosted model for teams that need stricter data-control workflows.

#### Choose a First-Run Cost

At no-discount base prices, basic discovery costs $0.0015 per useful website with enrichment switched off.
The enriched base is $0.01 per useful website when tech detection, email checks
or deep verification is enabled. Phone formatting, qualifying email checks,
SMTP checks and AI have separate additive events; the enriched base does not
include all of them. Review the Pricing section and maximum Actor charge before
enabling optional features.

The B2B starter below disables those optional charges and produces downloadable
CRM files. Published emails remain unverified unless a check was actually
performed; no mode guarantees mailbox availability or delivery of a message.

### Use Cases

#### SDR / AE Outbound Prospecting

Enrich company lists with published emails and their actual check status. Upload target company domains to extract public contacts and optional enrichment, then review the CRM import files and map them to your destination. Configure any downstream integration separately with authorized credentials; extraction does not enroll contacts in outreach. Optional AI role classification helps distinguish shared inboxes such as `info@`, `support@`, and `noreply@`.

#### Account-Based Marketing (ABM) Lists - match-rate by domain, not name + company guess

Build enriched ABM target lists from a domain seed (e.g. "all SaaS companies running Segment + Marketo on Shopify"). Tech stack detection (175+ signals) lets you filter by **CMS, analytics, CRM, chat widget, payment processor, CDN, search platform**. Combine with `enableAiAnalysis` for primary-contact selection per account.

#### Lead-Gen Agencies & RevOps - pay-per-event beats per-seat

Run hundreds of client enrichment jobs without buying seats per analyst. **Pay only for sites that returned data**: failed fetches and DNS errors are free. Keep source URLs and contact-page context for QA and client delivery.

#### Recruiter Sourcing & Talent Intel - find decision-makers via tech-stack match

Identify hiring managers at companies running specific stacks, such as SaaS teams using modern frontend frameworks or developer tools. Tech-stack detection reveals what languages and platforms a company appears to use today, which can be more current than profile skill tags. Pair with social profile capture to surface candidate-targeting signals.

#### M\&A Due Diligence & Investor Research - rapid target screening

Run a watchlist of acquisition targets through the Actor every quarter. Where published, output includes company description, address, contact emails, phones, social profiles and optional tech signals. Split lists larger than 200 websites into separate capped runs; runtime and completeness depend on target responsiveness and pages crawled. Use the evidence to screen technology adoption and public company context, not as a guarantee of complete coverage.

#### Download CRM Import Files

Set `exportCrm: true` to get files directly in the run's **CRM Import Files**
output. No script download or CRM credentials are needed. The existing
[B2B lead task](https://apify.com/harvestlab/contact-extractor/examples/get-b2b-leads-from-company-websites)
enables this export with paid enrichment and phone formatting switched off.
This sample reads a published contact page and exports a selected unverified
email when one is available; a homepage-only crawl may find no contact email.

```json
{
  "urls": ["https://www.python.org/about/help/"],
  "maxWebsites": 1,
  "maxPagesPerSite": 1,
  "includeSubpages": false,
  "detectTechStack": false,
  "validatePhones": false,
  "verifyEmails": false,
  "deepEmailVerification": false,
  "enableAiAnalysis": false,
  "exportCrm": true,
  "crmRequireMx": false,
  "proxyConfiguration": {"useApifyProxy": true}
}
```

The files contain:

| File | Contents and import key |
| --- | --- |
| `CRM-companies.csv` | One company per normalized `company_key` domain; website, name, LinkedIn, available formatted phone and technology signals. |
| `CRM-contacts.csv` | At most one selected eligible published email per website observation, deduplicated by `contact_key` email; source URL, check status and observation time. A shared email across companies has a blank company association for review. |
| `CRM-upserts.json` | The same records with merge instructions that preserve existing values when incoming fields are empty. |
| `CRM-EXPORT` | Completion status, counts and file names. Import only after `status` is `complete`. |

Import companies first, then contacts using the keys above. Review blank company
associations before linking contacts. The complete original email lists remain
in the dataset. This export adds no Actor event; normal storage usage still
applies. Basic extraction remains $0.0015 per useful website when enrichment
and phone formatting are off.

Unchecked emails are labelled `published_unverified`. Existing failed,
LOW/UNKNOWN, disposable and typo checks are excluded. For checked-only contacts,
enable `verifyEmails` deliberately and set `crmRequireMx: true`. The filter
itself does not turn on verification or its charges. MX checks concern the
email domain, not mailbox existence; formatted phones are not reachability
checks. Zero eligible emails produces an empty contacts file, not an invented
person.

For repeat refreshes, save your own task with the same company list and choose
a schedule appropriate to your business need. Import completed runs by key,
preserve existing fields on empty values, and never delete a contact because
it is absent from a later crawl. These files do not perform CRM writes or
retain a cross-run baseline. If the export fails or a source is unavailable,
keep your last accepted CRM data and inspect the dataset diagnostics.

#### Review CRM changes in n8n

Use the [Contact n8n template](https://api.apify.com/v2/key-value-stores/L7nr7abk9HDHXa13i/records/n8n-2026-10-26-contact-existing-run.json?signature=FON5xHi5Gt5LhQO3gtRZ\&attachment=true) with an existing
succeeded run that used `exportCrm: true`. It prepares changed company and
selected-contact records for review and retains previously accepted records.
It does not start a new scrape or write to your CRM. Follow the
[setup guide](https://api.apify.com/v2/key-value-stores/L7nr7abk9HDHXa13i/records/n8n-2026-10-26-README.md?signature=1FEUXjJJ5S4WMP8fTQ7eT\&attachment=true) for private credentials, replay handling and
destination acceptance.

The same guide covers an [Amazon price-review template](https://api.apify.com/v2/key-value-stores/L7nr7abk9HDHXa13i/records/n8n-2026-10-26-amazon-existing-run.json?signature=TwaGitVAEi6cy09akH0i\&attachment=true)
for existing [Amazon Scraper](https://apify.com/harvestlab/amazon-scraper) runs.
Templates were tested on n8n 2.44.7; verify your own source and destination before
enabling a downstream integration.

#### CRM Data Hygiene & Re-enrichment

Upload domains for any CRM segment with empty `phone`, `email`, `linkedin_url`, or `tech_stack` fields and bulk-update with fresh data from the source website. The dual `phones` + `phones_validated` shape lets you pick raw-extract vs format-valid per workflow. AI dedup groups `info@` / `hello@` / `contact@` into a single primary contact per account.

#### Competitive Intelligence & Tech-Stack Tracking

Schedule regular scans on competitor domains. Detect when a target migrates CMS, swaps analytics, adds a CDP, or rolls out a new payment processor. The `tech_stack` array surfaces 175+ technologies, including CDPs (Segment, Pendo, PostHog), analytics, search platforms (Algolia, Typesense), build tools (Vite, Webpack), and feature-flag systems (LaunchDarkly).

#### Market Research & Industry Datasets

Build structured datasets of companies in a niche by feeding a domain list and exporting JSON / CSV / Excel. Combine tech stack adoption with contact data to segment markets by technology profile, company digital maturity, and likely deal size - perfect for analyst reports, investor decks, and trend studies.

### Input

| Parameter | Type | Default | Description |
|-----------|------|---------|-------------|
| `urls` | array | `[]` | List of full website URLs (e.g., `https://example.com`). |
| `startUrls` / `url` / `website` | array/string | - | API/CLI aliases for URL inputs. `startUrls` is merged with `urls`; singular aliases are accepted for one-off calls. |
| `domains` | array | `[]` | List of bare domains (e.g., `example.com`, `stripe.com`). HTTPS is added automatically. |
| `maxPagesPerSite` | integer | `5` | Number of pages to crawl per site (1-20). More pages means more contact data found but longer runtime. |
| `includeSubpages` | boolean | `true` | Automatically crawl /contact, /about, /imprint, and similar pages. Highly recommended - many sites only list contact info on subpages. |
| `detectTechStack` | boolean | `false` | Detect CMS, frameworks, analytics, and 175+ other technologies. Confidently detected Shopify stores can also return public store name, canonical URL, currency, and business country when available. Uses enriched-mode pricing: $0.01 per useful website instead of $0.0015 basic discovery. |
| `validatePhones` | boolean | `true` | Format valid phones as E.164, with country and line type, for $0.02 per qualifying number in addition to the base event. Set false to return raw phones only. Not a carrier or reachability check. |
| `verifyEmails` | boolean | `false` | Check extracted email domains for MX records, typos and disposable/free-inbox signals, returning HIGH / MEDIUM / LOW / UNKNOWN in `emails_verified`. The $0.01 event requires boolean `mx_valid: true`, HIGH/MEDIUM and no typo/disposable flag. LOW/UNKNOWN is free even when MX resolves; domain checks do not prove mailbox existence. |
| `deepEmailVerification` | boolean | `false` | Opt-in mailbox-level verification for every MX-cleared email. Classifies each as `deliverable` / `undeliverable` / `catchall` / `greylisted` / `port_blocked` / `rate_limited`. Rate-limited per mail host. Charges $0.02 per definitive verdict only. Requires `verifyEmails: true`. |
| `enableAiAnalysis` | boolean | `false` | Enable AI-powered contact enrichment - email role classification, team-address deduplication, primary-contact selection, and deliverability flags. Requires an API key for your chosen AI provider. |
| `llmProvider` | string | `openrouter` | AI provider when enrichment is enabled: `openrouter`, `anthropic`, `google`, `openai`, or `ollama`. |
| `openrouterApiKey` / `anthropicApiKey` / `googleApiKey` / `openaiApiKey` | string | - | API key for the chosen provider. Ollama (self-hosted) uses `ollamaBaseUrl` instead. |
| `outputConnectors` | array | - | Optional MCP connectors for per-website contact summaries. Sends counts plus extracted public emails, phones, social profiles, and contact metadata to authorized Slack, Notion, GitHub, Sheets, CRM, or other connector tools. |
| `connectorAlertTarget` | string | - | Optional connector destination, such as a Slack channel, Notion database ID, sheet name, table, or CRM list. |
| `proxyConfiguration` | object | `{"useApifyProxy": true}` | Uses automatic Apify Proxy selection by default. Choose a residential group for websites that block datacenter traffic. |

You can provide input via `urls`, `startUrls`, `url`, `website`, `domains`, or any combination of them. Duplicates across inputs are removed automatically.

#### Pricing

This Actor uses **pay-per-event (PPE)** pricing. Ordinary runs use the per-site and optional events below. Prices are USD per event; base means Free/no-discount pricing. Starter (Bronze), Scale (Silver), and Business (Gold) receive 20%, 30%, and 40% discounts respectively. Platinum and Diamond match Gold. These are Apify subscription-plan discounts, not quantity breakpoints. The legacy $5 bundle stays flat. Check the [live Pricing tab](https://apify.com/harvestlab/contact-extractor/pricing) for your plan.

| Event | Base | Starter / Bronze | Scale / Silver | Business / Gold | When charged |
| --- | ---: | ---: | ---: | ---: | --- |
| `lead-discovered` | $0.0015 | $0.0012 | $0.00105 | $0.0009 | Per persisted useful website in basic mode (`detectTechStack: false`, `verifyEmails: false`, `deepEmailVerification: false`). Diagnostic-only rows do not request this event. |
| `contact-extracted` | $0.01 | $0.008 | $0.007 | $0.006 | Per useful website when tech detection, MX checks, or SMTP checks are enabled. Phone formatting and AI alone do not select this base tier; their qualifying events are additive. |
| `verified-phone-extracted` | $0.02 | $0.016 | $0.014 | $0.012 | Per number that passes phone-format validation (E.164 + country + line-type). This is not a carrier or reachability check. |
| `verified-email-checked` | $0.01 | $0.008 | $0.007 | $0.006 | Per email with boolean `mx_valid: true`, HIGH/MEDIUM deliverability and no typo/disposable flag. LOW/UNKNOWN is free, even when MX resolves. Only when `verifyEmails` is on. |
| `verified-email-deep-checked` | $0.02 | $0.016 | $0.014 | $0.012 | Per definitive SMTP verdict (`deliverable` or `undeliverable`). `catchall`, `greylisted`, `rate_limited`, `port_blocked`, and `error` are free. Only when `deepEmailVerification` is on. |
| `ai-analysis-completed` | $0.05 | $0.04 | $0.035 | $0.03 | Per completed structured analysis persisted in a useful contact row. Empty, invalid, provider-error, or unpersisted AI results are not charged. |
| `connector-alert-dispatched` | $0.002 | $0.0016 | $0.0014 | $0.0012 | Per successful per-website summary delivery through an MCP output connector. Failed attempts are not charged. |
| `skyfire-bundle-1000-leads` | $5.00 | $5.00 | $5.00 | $5.00 | Listed legacy bundle; ordinary runs do not request this event. A prepaid 1,000-lead route has not been verified. |

Failed requests (unreachable sites, DNS errors) are not charged.

Actor-event subtotals for 100 useful websites:

| Scenario | Base | Starter / Bronze | Scale / Silver | Business / Gold |
| --- | ---: | ---: | ---: | ---: |
| Raw discovery; tech/MX/SMTP/AI off, `validatePhones: false` | $0.15 | $0.12 | $0.105 | $0.09 |
| Same discovery plus one qualifying phone per site; `validatePhones: true` | $2.15 | $1.72 | $1.505 | $1.29 |
| Tech-enriched discovery, phone/MX/SMTP/AI off | $1.00 | $0.80 | $0.70 | $0.60 |
| Tech-enriched discovery plus one qualifying phone per site | $3.00 | $2.40 | $2.10 | $1.80 |
| Tech-enriched discovery plus one qualifying MX email per site, phones off | $2.00 | $1.60 | $1.40 | $1.20 |

The basic and enriched website events are mutually exclusive. Phone, MX, SMTP, AI, and successful connector events are additive. Examples are Actor-event subtotals, not quotes for external AI or downstream services. `maxWebsites` defaults to 50 and has a hard cap of 200 per run; split larger lists into batches. These examples assume the specified useful results are found, not that every submitted website succeeds. Set a maximum charge in the Apify run options as an additional spending limit.

**vs. commercial alternatives**: this actor is a pay-per-event alternative to subscription lead tools and seat-based enrichers. You only pay for successful events.

##### Skyfire bulk bundle

The Pricing list contains a `skyfire-bundle-1000-leads` event at **$5.00**, but ordinary runs do not request it. A separate prepaid 1,000-lead payment route has not been verified. Use the documented per-site and optional-event prices when estimating a normal run; the listed bundle does not guarantee 1,000 results or replace those charges.

### Automated Contact Enrichment Workflows

Contact-extractor returns structured records (`emails`, `phones_validated`, `tech_stack`, `social_profiles`, and optional verification fields). Optional role labels and contact selection live under `ai_contact_analysis.email_roles` and `ai_contact_analysis.primary_contact`, not at the row's top level. Use the mapping below in your own authorized integration; no private repository access is needed.

#### Company and contact mapping

| Source field | Destination and rule |
| --- | --- |
| `domain` | Stable company key; upsert rather than create a company every refresh. |
| `company_name`, `url`, `social_profiles.linkedin` | Company name, website, LinkedIn URL; preserve populated fields when source values are empty. |
| `emails[]` | Contact email key; choose a published address, not an AI-invented contact. |
| `emails_verified[]` | Reject available LOW/UNKNOWN, failed MX, disposable/typo flags and `smtp_probe.result: undeliverable`. HIGH/MEDIUM MX still means mailbox unknown. Unchecked published email is unverified. |
| `ai_contact_analysis.primary_contact.email` | Optional preferred email, only if it belongs to the eligible extracted list. |
| `ai_contact_analysis.email_roles[]` | Optional role matched by email; not a numerical contact score. |
| `phones_validated[].e164` | Optional format-valid phone, not a reachability check; raw `phones[]` is not validated. |
| `scraped_at`, `url` | Observation timestamp and public provenance. |

Fetch the exact succeeded run's dataset; discard diagnostic and partial-only rows,
deduplicate companies by domain and contacts by email, then upsert companies
before contacts. Company-only records can enrich accounts without creating people.
If one email appears under several domains, review the association rather than
guessing. Replayed completions use the same keys. Failed/empty exports preserve
prior good CRM data. Test mapping and unique keys in your authorized destination
account before production writes. The Actor does not send email, enroll sequences
or provide CRM credentials.

Common automation patterns:

- Enrich a target-account CSV before an SDR sequence and keep only domains with verified company emails.
- Add phone, social, address, and tech-stack fields to incomplete CRM accounts.
- Re-run priority accounts quarterly to catch changed contact pages, new social profiles, or technology changes.
- Send verified contacts to HubSpot, Salesforce, Pipedrive, Airtable, Google Sheets, or a webhook destination after each run.

#### MCP Quickstart - call this actor from Claude / Cursor / ChatGPT

Open Apify's hosted MCP server with this actor selected:

```text
https://mcp.apify.com?tools=harvestlab/contact-extractor
```

Then prompt the agent:

> "Use harvestlab/contact-extractor through Apify MCP. Enrich these company domains, return emails, phone numbers, social profiles, contact-page URLs, tech stack, and the best public outreach contact. Keep email verification off for the first run unless I ask for it."

Through Apify MCP, the agent can generate the actor input, run the job, and read the typed dataset output back into your conversation.

### Output

Each website produces a structured JSON object:

```json
{
    "url": "https://example.com",
    "domain": "example.com",
    "company_name": "Example Inc",
    "company_description": "Example Inc is a technology company that helps businesses build better software solutions and streamline their operations.",
    "emails": ["info@example.com", "sales@example.com"],
    "emails_verified": [
        {
            "email": "info@example.com",
            "domain": "example.com",
            "mx_valid": true,
            "mx_hosts": ["aspmx.l.google.com", "alt1.aspmx.l.google.com"],
            "catchall": false,
            "disposable": false,
            "free_inbox": false,
            "role_based": true,
            "typo_suggestion": null,
            "deliverability": "MEDIUM",
            "deep_probe": true,
            "probe_result": "deliverable",
            "smtp_probe": {
                "result": "deliverable",
                "response_code": 250,
                "response_text": "2.1.5 OK",
                "mx_host": "aspmx.l.google.com",
                "probed_at": "2026-04-23T03:15:22+00:00",
                "is_catchall": false
            }
        },
        {
            "email": "sales@example.com",
            "domain": "example.com",
            "mx_valid": true,
            "mx_hosts": ["aspmx.l.google.com", "alt1.aspmx.l.google.com"],
            "catchall": null,
            "disposable": false,
            "free_inbox": false,
            "role_based": true,
            "typo_suggestion": null,
            "deliverability": "MEDIUM",
            "deep_probe": true,
            "probe_result": "rate_limited",
            "smtp_probe": {
                "result": "rate_limited",
                "response_code": null,
                "response_text": "daily_cap_reached:10/10",
                "mx_host": "aspmx.l.google.com",
                "probed_at": "2026-04-23T03:15:22+00:00",
                "is_catchall": null
            }
        }
    ],
    "phones": ["+1 888 926 2289", "555-1234"],
    "phones_validated": [
        {
            "raw": "+1 888 926 2289",
            "e164": "+18889262289",
            "country": "US",
            "type": "TOLL_FREE",
            "valid": true,
            "possible": true
        }
    ],
    "phones_uncertain": ["555-1234"],
    "social_profiles": {
        "linkedin": "https://linkedin.com/company/example",
        "twitter": "https://twitter.com/example",
        "facebook": null,
        "instagram": null,
        "youtube": null,
        "github": "https://github.com/example",
        "tiktok": "https://tiktok.com/@example",
        "pinterest": null,
        "telegram": "https://t.me/examplecompany",
        "discord": null,
        "threads": "https://threads.net/@example"
    },
    "address": "123 Main St, City, State, 12345",
    "contact_page_url": "https://example.com/contact",
    "tech_stack": ["Shopify", "Cloudflare", "Google Analytics"],
    "is_shopify_store": true,
    "shopify_detection_confidence": "high",
    "shopify_detection_signals": ["response-header", "shopify-store-identity", "shopify-asset-host"],
    "shopify_store_name": "Example Store",
    "shopify_canonical_url": "https://example.com/",
    "shopify_currency": "USD",
    "shopify_country": "US",
    "pages_crawled": 3,
    "scraped_at": "2026-04-10T12:00:00Z"
}
```

**Which phone field should you use?** For format-valid dialing, cold-call campaigns, or CRM import, use **`phones_validated`** - every entry carries a canonical E.164 string, ISO-3166-1 country code, and line-type so your dialer knows whether it's mobile, toll-free, or a PBX fixed line. The legacy **`phones`** list (all raw extracts) and **`phones_uncertain`** (parsed but not format-validated) are kept for backward compatibility and triage workflows.

The `shopify_*` fields and `is_shopify_store` are positive-only: they appear only when `detectTechStack` is enabled and public evidence confidently identifies Shopify. They are omitted for non-Shopify sites and inconclusive detections. This enrichment uses pages already fetched for contact extraction and does not request a product catalog or private Shopify API.

Null values indicate that the Actor searched for the data but did not find it on the crawled pages. Output is available as JSON, CSV, or Excel. Use Apify integrations to push results directly to Google Sheets, HubSpot, Salesforce, Airtable, or any webhook endpoint.

When `enableAiAnalysis` is `true`, each output item additionally includes:

- `ai_contact_analysis.email_roles` - per-email classification (sales, support, hr, legal, executive, general, personal)
- `ai_contact_analysis.primary_contact` - selected email and reasoning; this is not a numerical contact score
- `ai_contact_analysis.primary_phone` - selected format-valid phone, when available
- `ai_contact_analysis.dedup_groups` - near-duplicate team addresses grouped together
- `ai_contact_analysis.deliverability_flags` - warnings for no-reply and non-monitored addresses

#### Technologies Detected

The Actor recognizes 175+ technologies across these categories:

- **CMS and Platforms** - WordPress, Shopify, Wix, Squarespace, Drupal, Joomla, Ghost, Webflow, HubSpot
- **JavaScript Frameworks** - React, Next.js, Vue.js, Nuxt.js, Angular, Svelte, jQuery
- **CSS Frameworks** - Bootstrap, Tailwind CSS, Foundation, Bulma, Materialize
- **Analytics and Marketing** - Google Analytics, Google Tag Manager, Facebook Pixel, Hotjar, Mixpanel, Segment, Plausible, Matomo, Microsoft Clarity, Amplitude, LinkedIn Insight Tag
- **Product Analytics** - FullStory, LogRocket, PostHog, Pendo, Datadog RUM, Sentry, LaunchDarkly
- **Email Marketing** - Mailchimp, SendGrid, Klaviyo, ActiveCampaign, Marketo, Pardot, ConvertKit
- **CRM and Sales** - Salesforce, HubSpot, Pipedrive
- **Chat and Support** - Intercom, Drift, Zendesk, Crisp, LiveChat, Tawk.to, Freshdesk
- **Authentication** - Auth0, Okta, Firebase, Supabase
- **Search** - Algolia, Elasticsearch, Typesense
- **Infrastructure** - Cloudflare, Fastly, CloudFront, Vercel, Netlify, nginx, Apache, Akamai
- **E-commerce** - WooCommerce, Magento, PrestaShop, BigCommerce, Stripe, PayPal
- **Video and Media** - YouTube Embed, Vimeo, Wistia, Loom
- **Scheduling** - Calendly, Acuity Scheduling
- **Social Proof** - Trustpilot Widget, G2, Yotpo
- **Build Tools** - Webpack, Vite, GraphQL, WebSocket

### Quick Start

The simplest possible input - a list of domains:

```json
{
    "domains": ["stripe.com", "shopify.com", "hubspot.com"]
}
```

Or full URLs with custom crawl depth:

```json
{
    "urls": ["https://example.com", "https://anothersite.org"],
    "maxPagesPerSite": 10,
    "includeSubpages": true,
    "detectTechStack": true
}
```

Enable email-domain and SMTP acceptance checks before reviewing CRM import eligibility:

```json
{
    "domains": ["stripe.com", "shopify.com"],
    "verifyEmails": true,
    "deepEmailVerification": true
}
```

Enable AI role classification and primary-contact selection:

```json
{
    "domains": ["stripe.com", "shopify.com"],
    "verifyEmails": true,
    "enableAiAnalysis": true,
    "llmProvider": "openrouter",
    "openrouterApiKey": "your-key-here"
}
```

### Troubleshooting

**A target is rejected as invalid input**
Use a bare domain or an HTTP(S) website URL. URLs with embedded usernames or
passwords, malformed hosts or invalid ports are rejected before crawling.
Rejected values are omitted from warnings and dataset diagnostics; the diagnostic
retains `invalid_input_count` and redacted `invalid_inputs` placeholders.
Mixed batches continue with valid targets. Rejected targets request no Actor
business events, but platform usage is separate. This does not rewrite Apify's
original INPUT record: never paste credentials into website inputs. It is not
a login workflow or a general redirect/network-access security guarantee.

**SMTP verification returns "greylisted" for most emails**
Greylisting temporarily defers an SMTP request. A `greylisted` result is inconclusive: it does not establish whether that mailbox exists or will accept a message. The Actor requests no deep-verification event for this result. Keep the status when exporting and do not relabel it as a verified mailbox; any later check remains subject to the mail host's rate limits and may still be inconclusive.

**Many emails flagged as "catchall" or "undeliverable"**
Catchall domains accept addresses regardless of whether a mailbox exists. Treat them as "valid domain, unknown mailbox." Filter the exported `emails_verified[].smtp_probe.result` field in your downstream workflow, retaining only `deliverable` when definitive mailbox evidence is required. MX HIGH/MEDIUM alone does not prove mailbox existence.

**No tech stack signals returned**
Tech detection depends on what the target website publishes publicly. Pages behind login walls, very dynamic apps, or sites that block automated access may return fewer tech signals. The actor detects 175+ technologies from publicly visible website assets only. Shopify context is likewise omitted unless the public evidence is strong enough for a positive detection.

**AI role classification returns wrong roles**
The AI role classifier uses the email prefix and page context to infer roles (sales/marketing/engineering/executive/support). It cannot access LinkedIn or internal org data. For ambiguous prefixes (`info@`, `hello@`, `contact@`), it classifies as "unknown" - these are typically shared inboxes.

**Rate limited or IP blocked mid-crawl**
The actor implements reputation-safe probe rate limits (max 10 SMTP probes per domain per UTC day, 10s cooldown). If a domain blocks the probe IP, the actor logs `smtp_blocked: true` and falls back to MX-only verification. HTTP proxy settings do not route SMTP probes, and the actor never charges for blocked or inconclusive probes.

**No emails found for a domain**
The site may not publish contact emails publicly. The actor still checks canonical host variants, sitemap-discovered contact URLs, and common paths such as `/contact`, `/about`, `/impressum`, and `/legal`. If no billable contact data is found, the row is marked as `partial_result`, `fetch_failed`, `site_timeout`, or `site_error` and includes `fetch_diagnostics`. The actor only extracts contacts that are publicly visible - it does not guess or generate addresses.

**How many pages should I crawl per website?**
The default of 5 pages works well for most sites. The Actor prioritizes /contact, /about, and /imprint pages. Set `maxPagesPerSite` to 1 if you only need tech stack data and want the fastest possible run. Increase to 10-20 for large corporate sites where contact details may be spread across many sections.

**Can I input just domain names without https://?**
Yes. Use the `domains` field with bare domain names like "stripe.com" or "hubspot.com". The Actor adds HTTPS automatically. You can also mix `urls` (full URLs) and `domains` (bare domains) in the same run.

**How does email deliverability verification work?**
When you set `verifyEmails: true`, every extracted email runs through a four-step pipeline:

1. **Typo check** - Common mail-domain misspellings such as `gmial.com`, `outllok.com`, `hotmial.com`, and `iclould.com` are flagged with a `typo_suggestion` and tagged **LOW** deliverability.
2. **Disposable / burner check** - Known disposable hosts (Mailinator, 10MinuteMail, GuerrillaMail, YOPmail) are hard-flagged **LOW** regardless of MX state.
3. **Real MX lookup** - The Actor runs an actual DNS MX query against the domain. Domains that have no MX record cannot receive mail and are tagged **LOW**.
4. **Tiering** - Emails at free inboxes (Gmail / Yahoo / Outlook / iCloud / ProtonMail) OR with role prefixes (`info@`, `hello@`, `support@`, `sales@`) land in **MEDIUM**. Named addresses at non-free company domains with resolved MX land in **HIGH**.

**How does the deep SMTP probe work?**
When `verifyEmails` and `deepEmailVerification` are both on, every MX-cleared email receives a mailbox-level acceptance check. No email content is delivered. Results are classified as `deliverable`, `undeliverable`, `catchall`, `greylisted`, `port_blocked`, `rate_limited`, or `error`, and the checks are capped per mail host to avoid aggressive probing.

**How does phone validation work?**
Every phone candidate is normalized and checked against regional phone-number rules. Numbers that pass validation land in `phones_validated` with E.164, country code, and line type. Numbers that parse but fail strict validation land in `phones_uncertain`. This gives you a cleaner phone field without paying for a separate cleanup service.

**What if a website blocks the scraper?**
Some sites block datacenter IP addresses. The default preset lets Apify select a compatible proxy automatically. Choose a residential group for harder sites. If proxy setup fails, the actor retries once and returns a no-charge diagnostic instead of terminating the run. Sites behind login walls or aggressive bot protection may still remain inaccessible. Failed requests are not charged.

### Frequently Asked Questions

**Can I extract contacts from a website URL?**
Yes. Provide full URLs in `urls` or bare domains in `domains`. The actor normalizes each target, crawls the homepage plus contact-oriented subpages, and returns emails, phones, socials, addresses, contact-page URLs, and technology signals when they are publicly visible.

**Is this a contact details extractor or an email finder?**
Both. The core workflow extracts public contact details from a website, including emails, phones, social profiles, addresses, and contact-page URLs. Email verification and AI role labeling are optional paid enrichments for teams that need cleaner outreach records.

**What is the best first run for a domain list?**
Start with `maxPagesPerSite: 3`, `detectTechStack: false`, and `verifyEmails: false` on one or two domains. If the output looks useful, run the larger list with tech-stack detection enabled, then re-run only promising domains with email verification.

**Can I use this as a B2B lead extractor?**
Yes. Start with a list of company domains, then export the resulting contact rows to your CRM or outreach tool. The actor is strongest for company-level B2B enrichment, ABM lists, recruiting research, market maps, and CRM cleanup.

**Does it verify email addresses?**
Yes, when `verifyEmails: true` is enabled. The actor checks email domains with MX lookup and deliverability signals. For deeper mailbox checks, enable `deepEmailVerification: true`; definitive deliverable or undeliverable SMTP verdicts are charged only when produced.

**Does it find personal emails for every company?**
No. It extracts contact details that the website makes publicly visible. Many companies publish shared inboxes like `info@`, `sales@`, or contact forms rather than personal decision-maker emails. AI role classification can help prioritize the best available inbox.

**Can it extract phone numbers and social profiles too?**
Yes. The output includes raw phones, validated phones in E.164 format when possible, LinkedIn and other social profiles, addresses, and a canonical contact-page URL.

Workflow: provide a list of public company domains or website URLs, then run contact-extractor to return CRM-ready contact records and enrichment signals.

***

### Scheduling and webhooks

Save your tested input as a task and add a weekly Apify schedule. Configure an Apify run-succeeded webhook or integration to fetch that exact run's default dataset. Use the company/contact mapping above with your own authorized destination credentials. Reject diagnostics and failed checks, deduplicate keys, preserve non-empty values, and retry by upsert instead of repeated contact creation. Do not trigger outreach or sequence enrollment as an extraction side effect.

***

### Legal and Compliance

This actor scrapes publicly available data. By using this actor, you agree to the following:

- **Your responsibility**: You are solely responsible for ensuring your use complies with all applicable laws, regulations, and the target website's terms of service. This includes but is not limited to **GDPR** (EU), **CCPA / CPRA** (California), **CAN-SPAM Act** (US), **CASL** (Canada), **PECR** (UK), **LGPD** (Brazil), and other data protection / anti-spam laws in your jurisdiction.
- **No legal advice**: This actor does not constitute legal advice. Consult a qualified attorney if you have questions about the legality of your specific use case.
- **Intended use**: This actor is designed for legitimate business purposes such as market research, competitive analysis, and B2B lead generation using publicly accessible data.
- **Data handling**: You are responsible for how you store, process, and share any data collected. Ensure you have a lawful basis (e.g. **legitimate interest** under GDPR Art. 6(1)(f), or pre-existing business relationship under CASL) for processing any personal data under applicable privacy laws.
- **CAN-SPAM compliance** (US): Any commercial email you send using contacts from this actor must include a clear, conspicuous opt-out mechanism, a valid physical postal address, accurate "From" / "Reply-To" headers, non-deceptive subject lines, and you must honor unsubscribe requests within 10 business days. Penalties are up to **$53,088 per violation** under the FTC's enforcement schedule.
- **GDPR compliance** (EU/UK): Even publicly available personal data is subject to GDPR. You must have a lawful basis (typically **legitimate interest with documented LIA**), respect Art. 14 transparency requirements (notify the data subject within 30 days when collecting from a third-party source), honor erasure / objection requests promptly, maintain records of processing under Art. 30, and not transfer EU data outside the EU/EEA without an SCC, adequacy decision, or other approved mechanism.
- **Anti-spam / outreach**: Do **NOT** use this tool for unsolicited bulk messaging, spam, scraped-list email blasts, or list-building for resale. Use for permission-tested, targeted, B2B-relevant outreach only.
- **Rate limiting**: This actor implements polite crawling practices including request delays and retry backoff to minimize impact on target servers.
- **No warranty**: This actor is provided "as is" without warranty. Data accuracy depends on the target website's content and structure.
- **Personal data minimization**: Implement data retention policies (typically 6-12 months for cold outreach contacts), encrypt PII at rest and in transit, restrict internal access on a need-to-know basis, and honor opt-out / Do-Not-Contact requests across your entire system, not just the channel that received the request.
- **Mailbox verification**: Deep email verification uses rate-limited mailbox checks and never delivers email content. Some mail server operators may still log or flag verification attempts, so use this option responsibly and only on domains you have explicitly listed in `urls` or `domains`.

#### Related Actors

- **[Indeed Scraper](https://apify.com/harvestlab/indeed-scraper)** - Pipe Indeed job listings into a recruiter sourcing pipeline: scrape hiring companies, then run Contact Extractor on the listing's company URL to get hiring-manager emails and phones for outbound recruiting.

# Actor input Schema

## `urls` (type: `array`):

Full public website URLs to analyze. Start with the prefilled contact page, then replace it with your own URLs. Clear this sample before using Domains instead.

## `startUrls` (type: `array`):

Alternative field name for Website URLs, provided for compatibility with Apify's common 'startUrls' convention (e.g. https://example.com). The canonical field is `urls` above - prefer that. If both are supplied, they are merged.

## `url` (type: `string`):

CLI alias for a single website URL. Hidden from Console form.

## `website` (type: `string`):

CLI alias for a single website URL. Hidden from Console form.

## `domains` (type: `array`):

Domains without protocol (e.g. example.com). HTTPS is added automatically. Clear the sample in Website URLs before using this alternative; when both fields are supplied, URLs are processed first and duplicate domains are merged.

## `maxWebsites` (type: `integer`):

Bill-safety cap across Website URLs, Start URLs, URL aliases, and Domains after deduplication. Default 50; raise deliberately for larger lead batches.

## `maxPagesPerSite` (type: `integer`):

Maximum number of pages to crawl per website. Higher values find more contacts but take longer.

## `includeSubpages` (type: `boolean`):

Automatically crawl /contact, /about, /imprint, and similar pages to find additional contact information.

## `detectTechStack` (type: `boolean`):

Detect CMS, frameworks, analytics, and other public technology signals. Confidently detected Shopify stores can also return public store name, canonical URL, currency, and business country when available from pages already fetched. This uses enriched-mode pricing: $0.01 per useful website instead of $0.0015 basic discovery.

## `validatePhones` (type: `boolean`):

The Console starter switches this off for raw phones only, with no phone-validation charge. If omitted from API input, this defaults true for compatibility: format-valid phones are normalized to E.164 with country and line type at $0.02 per number, in addition to the base website event. This is not a reachability or carrier check.

## `verifyEmails` (type: `boolean`):

Check domain MX records, flag typos/disposable inboxes, and tag emails HIGH / MEDIUM / LOW / UNKNOWN. Adds emails_verified. Cost: $0.01 only for HIGH/MEDIUM emails with valid MX and no disposable/typo flag; LOW/UNKNOWN are free. MX resolution does not prove that a mailbox exists. Enables $0.01 enriched base pricing per useful website.

## `maxEmailsToVerify` (type: `integer`):

Bill-safety cap for MX and SMTP email verification across the whole run. Raw extracted emails are still returned; only verification is capped. Default 50, maximum 500.

## `deepEmailVerification` (type: `boolean`):

Opt-in: run an async SMTP RCPT TO probe against the primary MX host for every MX-cleared email. Classifies each email as deliverable / undeliverable / catchall / greylisted / port_blocked. Reputation-safe: per-MX-host probe history persists across runs (named KV store), capped at 10 probes/host/day with 10s cooldown between probes to the same host. Port 25 is frequently blocked on Apify datacenter egress; when it is, every entry returns `port_blocked` (free). Cost: $0.02 per email that receives a definitive verdict (deliverable or undeliverable) - catch-all, greylisted, and non-verdict results are free. Requires `verifyEmails: true`.

## `exportCrm` (type: `boolean`):

Save deduplicated company and selected-contact CSV files plus merge-ready JSON in this run's key-value store. At most one selected email per website observation; no extra Actor event, CRM login, or destination write. Files exclude diagnostics and failed email checks. CSV export adds only normal storage usage.

## `crmRequireMx` (type: `boolean`):

Filter CRM contacts to existing HIGH/MEDIUM email checks with valid MX and no typo/disposable flag. This filter does not enable paid verification. With Verify Email Deliverability off, unchecked emails are excluded and the contacts CSV can be empty. MX does not prove mailbox existence.

## `enableAiAnalysis` (type: `boolean`):

Use an LLM to classify each email by role (sales/support/hr/legal/executive/general/personal), group near-duplicate team addresses, flag non-monitored no-reply inboxes, and pick the single best primary contact for B2B outreach. When `verifyEmails` is also enabled, the AI picks the primary contact only from HIGH/MEDIUM deliverability emails. Hosted providers require your own API key; self-hosted Ollama needs no API key. Cost: $0.05 per website analyzed.

## `llmProvider` (type: `string`):

Choose the provider for email role classification, deduplication, and primary-contact selection. Hosted providers require your own API key; provider token charges are separate from the Actor AI event and depend on model and usage. Self-hosted Ollama needs no API key.

## `llmModel` (type: `string`):

Specific model to use. Leave empty for the provider default (google/gemini-3.8-flash for OpenRouter, claude-sonnet-5-5 for Anthropic, gemini-3.8-flash for Google AI, gpt-4o-mini for OpenAI, llama3.1 for Ollama).

## `openrouterApiKey` (type: `string`):

Your OpenRouter API key. Get one at openrouter.ai/keys

## `anthropicApiKey` (type: `string`):

Your Anthropic API key. Get one at console.anthropic.com

## `googleApiKey` (type: `string`):

API key for Google AI (Gemini). Get one at aistudio.google.com/app/apikey

## `openaiApiKey` (type: `string`):

API key from platform.openai.com (required if using OpenAI provider)

## `ollamaBaseUrl` (type: `string`):

Base URL for Ollama API. Default: http://localhost:11434

## `outputConnectors` (type: `array`):

Optional MCP connectors for contact extraction summaries. Choose Slack, Notion, GitHub, Sheets, CRM, or another compatible connector. At the end of the run, the actor sends a compact contact summary plus structured payload through each connector. Connector failures never fail the scrape. Billed at $0.002 per successful connector dispatch.

## `connectorAlertTarget` (type: `string`):

Optional connector destination such as a Slack channel, Notion database ID, sheet name, table, or CRM list. Leave empty when the selected connector already has a default target.

## `proxyConfiguration` (type: `object`):

Proxy settings. Automatic Apify Proxy selection is the compatible default. Choose residential proxy for websites that block datacenter traffic.

## Actor input object example

```json
{
  "urls": [
    "https://www.python.org/about/help/"
  ],
  "maxWebsites": 1,
  "maxPagesPerSite": 1,
  "includeSubpages": false,
  "detectTechStack": false,
  "validatePhones": false,
  "verifyEmails": false,
  "maxEmailsToVerify": 50,
  "deepEmailVerification": false,
  "exportCrm": false,
  "crmRequireMx": false,
  "enableAiAnalysis": false,
  "llmProvider": "openrouter",
  "ollamaBaseUrl": "http://localhost:11434",
  "proxyConfiguration": {
    "useApifyProxy": true
  }
}
```

# Actor output Schema

## `datasetOutput` (type: `string`):

Website contact records with emails, phones, social profiles, tech stack, and optional Shopify store context, email checks, and role labels.

## `crmFiles` (type: `string`):

Company/contact CSV, merge-ready JSON and a completion summary when exportCrm is enabled. Empty file list otherwise. Review CRM-EXPORT before importing; never delete missing CRM records.

# 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 = {
    "urls": [
        "https://www.python.org/about/help/"
    ],
    "maxWebsites": 1,
    "maxPagesPerSite": 1,
    "includeSubpages": false,
    "detectTechStack": false,
    "validatePhones": false,
    "verifyEmails": false,
    "deepEmailVerification": false,
    "enableAiAnalysis": false,
    "ollamaBaseUrl": "http://localhost:11434",
    "proxyConfiguration": {
        "useApifyProxy": true
    }
};

// Run the Actor and wait for it to finish
const run = await client.actor("harvestlab/contact-extractor").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 = {
    "urls": ["https://www.python.org/about/help/"],
    "maxWebsites": 1,
    "maxPagesPerSite": 1,
    "includeSubpages": False,
    "detectTechStack": False,
    "validatePhones": False,
    "verifyEmails": False,
    "deepEmailVerification": False,
    "enableAiAnalysis": False,
    "ollamaBaseUrl": "http://localhost:11434",
    "proxyConfiguration": { "useApifyProxy": True },
}

# Run the Actor and wait for it to finish
run = client.actor("harvestlab/contact-extractor").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 '{
  "urls": [
    "https://www.python.org/about/help/"
  ],
  "maxWebsites": 1,
  "maxPagesPerSite": 1,
  "includeSubpages": false,
  "detectTechStack": false,
  "validatePhones": false,
  "verifyEmails": false,
  "deepEmailVerification": false,
  "enableAiAnalysis": false,
  "ollamaBaseUrl": "http://localhost:11434",
  "proxyConfiguration": {
    "useApifyProxy": true
  }
}' |
apify call harvestlab/contact-extractor --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,harvestlab/contact-extractor"
        }
    }
}
```

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/bHzVpEmytvDNJk48m/builds/gMdjxlMd5VfOOHJIN/openapi.json
