# Contact Details Scraper - Emails (MX Verified), Phones, Socials (`eszetael_lab/contact-details-scraper`) Actor

Extract business contact details from websites: emails with built-in DNS MX verification and role classification, validated E.164 phone numbers, WhatsApp links, 15 social media platforms, and Schema.org company info. Strictly honors robots.txt with transparent status reasons for every domain.

- **URL**: https://apify.com/eszetael_lab/contact-details-scraper.md
- **Developed by:** [Radosław Szal](https://apify.com/eszetael_lab) (community)
- **Categories:** Lead generation, Marketing
- **Security:** Limited permissions
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

$0.80 / 1,000 domain with contacts founds

This Actor is paid per event. You are not charged for the Apify platform usage, but only a fixed price for specific events.

Learn more: https://docs.apify.com/actors/running/actors-in-store.md#pay-per-event

## What's an Apify Actor?

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

## 📩 Website Contact Info Scraper — Emails (MX Verified), Phones & 15 Socials

The **Website Contact Info Scraper** is a high-speed, cost-effective **business lead scraper** and **contact details scraper** designed to extract business emails with built-in DNS MX verification (**website email scraper**), validated E.164 phone numbers (**phone number extractor**), WhatsApp links, 15 platforms of social profiles (**social media links extractor**), and Schema.org company metadata (**domain contact extractor**) directly from corporate websites.

***

### What It Does

Extracting verified business contacts from corporate websites manually is tedious and expensive. This actor automates the entire discovery workflow:

- **Smart Subpage Discovery**: Crawls the homepage and automatically navigates to high-priority contact subpages (`/contact`, `/kontakt`, `/about`, `/team`, `/support`, `/impressum`, `/legal`).
- **Email Extraction & Built-in Verification**: Finds emails from `mailto:` links, Cloudflare obfuscation (`data-cfemail`), and obfuscated text (`name [at] domain.com`). Automatically runs live DNS MX checks and classifies each mailbox (`isRole`, `isFreeMail`, `isDisposable`, `isPersonal`).
- **Validated Phone Numbers (E.164)**: Extracts international and national phone numbers from `tel:` links, text, and Schema.org JSON-LD. Normalizes numbers to standard E.164 format (with automatic country deduction, e.g., `.pl` → `PL`), while strictly rejecting false positives like tax IDs (NIP/VAT), dates, and software version numbers.
- **WhatsApp Integration**: Captures direct WhatsApp chat links (`wa.me/...`, `api.whatsapp.com/send?phone=...`).
- **15 Social Media Platforms**: Identifies and normalizes clean profile links across 15 platforms (LinkedIn, Facebook, Instagram, X/Twitter, YouTube, TikTok, Pinterest, Discord, Telegram, Threads, Snapchat, Reddit, GitHub, Linktree), automatically stripping tracking query parameters (`utm_*`, `fbclid`, `igshid`).
- **Linktree Traversal**: Optionally follows public `linktr.ee/<profile>` links to discover external contact channels.
- **Schema.org Structured Data**: Extracts structured corporate data from JSON-LD (`Organization`, `LocalBusiness`) including official company name, description, address, telephone, and business hours.
- **Transparent Status Reporting**: Reports an explicit status and reason for every domain (`ok`, `no_contacts`, `blocked_by_robots`, `robots_unreachable`, `unreachable`).

***

### Why Us — Advantages Over Market Alternatives

Compared to standard contact scrapers on the market, our actor provides five tangible advantages:

1. **Pay Per Domain, Not Per Subpage**: Typical scrapers charge per visited page (costing ~$3.15 per 1,000 domains at an average of 3 pages per domain). We charge a flat fee per successful domain ($0.0008), regardless of whether 1, 3, or 5 pages were crawled to find the contact details.
2. **Built-in DNS MX Verification at No Extra Cost**: While others charge extra for email verification add-ons, live DNS MX validation and role/disposable/free-mail categorization are included in the base price.
3. **Strict E.164 Phone Validation**: Phone numbers are validated against international standards and contextual keywords, eliminating junk records filled with tax IDs, invoice numbers, or software build versions.
4. **Priority-First HTTP Crawling**: Crawls static and server-rendered HTML using lightweight, high-performance HTTP requests, prioritizing contact and about pages for maximum speed and minimal resource consumption.
5. **Deduplicated CRM-Ready Records**: Returns exactly one unified, clean JSON record per domain, perfectly formatted for immediate import into HubSpot, Pipedrive, Salesforce, Make, n8n, Clay, or Google Sheets.

***

### Input

Configure the actor via the Apify Console or API using JSON:

```json
{
  "urls": [
    "gov.pl",
    "https://ovh.pl",
    "https://apify.com/contact"
  ],
  "maxPagesPerDomain": 5,
  "followLinktree": true,
  "verifyEmailsMx": true,
  "defaultCountryCode": "PL"
}
```

#### Input Parameters

| Parameter | Type | Default | Description |
|---|---|---|---|
| `urls` | `array` or `string` | *(required)* | List of domain names (`example.com`) or full URLs (`https://example.com/contact`). Normalized automatically. |
| `maxPagesPerDomain` | `integer` | `5` | Maximum number of pages to crawl per domain (homepage + priority contact subpages). Capped at 10. |
| `followLinktree` | `boolean` | `true` | When true, fetches public Linktree profiles discovered on the website to collect further contact links. |
| `verifyEmailsMx` | `boolean` | `true` | When true, performs live DNS MX record checks and classifies role/disposable/freemail addresses. |
| `defaultCountryCode` | `string` | `null` | Optional ISO 3166-1 alpha-2 country code (e.g., `PL`, `US`, `DE`) for phone normalization. When omitted, inferred from domain TLD (e.g., `.pl` → `PL`). |

***

### Output

Every processed domain outputs a single consolidated record. Here is an example:

```json
{
  "domain": "gov.pl",
  "startUrl": "https://gov.pl",
  "scrapedPagesCount": 2,
  "status": "ok",
  "statusReason": "Found 2 email(s), 3 phone(s), 0 WhatsApp(s), and 6 social link(s) across 2 page(s).",
  "emails": [
    "kontakt@gov.pl",
    "media@gov.pl"
  ],
  "emailsVerified": [
    {
      "email": "kontakt@gov.pl",
      "hasValidMx": true,
      "isRole": true,
      "isDisposable": false,
      "isFreeMail": false,
      "isPersonal": false
    },
    {
      "email": "media@gov.pl",
      "hasValidMx": true,
      "isRole": true,
      "isDisposable": false,
      "isFreeMail": false,
      "isPersonal": false
    }
  ],
  "phones": [
    "+48221234567",
    "+48223100200",
    "+48601234567"
  ],
  "whatsapps": [],
  "socials": {
    "facebook": [
      "https://www.facebook.com/KancelariaPremiera"
    ],
    "x": [
      "https://x.com/govpl"
    ],
    "linkedin": [
      "https://www.linkedin.com/company/ministerstwo-cyfryzacji"
    ],
    "instagram": [
      "https://www.instagram.com/govpl"
    ],
    "youtube": [
      "https://www.youtube.com/@KancelariaPremiera"
    ],
    "tiktok": [
      "https://www.tiktok.com/@govpl"
    ]
  },
  "companyInfo": {
    "name": "Serwis Rzeczypospolitej Polskiej",
    "description": "Oficjalne informacje rządowe, usługi publiczne, wnioski i formularze.",
    "telephone": "+48 22 123 45 67",
    "email": "kontakt@gov.pl",
    "address": "Al. Ujazdowskie 1/3, 00-583 Warszawa, Polska",
    "openingHours": null,
    "sameAs": [
      "https://www.facebook.com/KancelariaPremiera",
      "https://x.com/govpl"
    ]
  }
}
```

***

### Pricing

We believe in fair, predictable pricing:

- **Pay Per Event (PPE)**: **$0.0008 per domain** delivered with verified contacts ($0.80 per 1,000 domains).
- **Zero-Waste Guarantee**: Domains where no contacts could be found (`no_contacts`) or domains blocked by `robots.txt` are logged with the reason in your dataset at **no charge**.
- **Market Comparison**: Standard market solutions charge around **$1.05 per 1,000 pages** (amounting to **~$3.15 per 1,000 domains** when crawling 3 pages per site) or $2.00+ flat per 1,000 domains. Our solution offers up to **75% cost savings** with superior data quality and email verification included.

***

### Frequently Asked Questions (FAQ)

#### 1. Am I charged if a domain has no contacts or is blocked by robots.txt?

No. You are charged only for domains where the status is `ok` (at least one email, phone, WhatsApp, or social profile was discovered). Unreachable domains, empty sites (`no_contacts`), or sites restricted by `robots.txt` are included in your dataset with a clear `statusReason`, but are completely free.

#### 2. How does built-in email verification work?

For every extracted email address, the scraper performs live DNS MX lookups to confirm that the receiving mail server is active and configured. It also flags role addresses (`info@`, `contact@`, `sales@`), disposable temporary inboxes, and free email providers (`gmail.com`, `wp.pl`, `yahoo.com`), making list segmentation effortless.

#### 3. How does the scraper handle phone numbers and country codes?

Phone numbers are normalized to standard international E.164 format (e.g., `+48...`, `+1...`). If a national number is written without a country code, the scraper uses `defaultCountryCode` or automatically deduces the country from the domain TLD (for instance, `.pl` domains default to Poland `+48`).

#### 4. Which social media platforms are detected?

The scraper extracts and normalizes profiles from 15 major platforms: LinkedIn, Facebook, Instagram, X (Twitter), YouTube, TikTok, Pinterest, Discord, Telegram, Threads, Snapchat, Reddit, GitHub, and Linktree. Share buttons and generic links (`facebook.com/sharer`, `twitter.com/intent`) are automatically filtered out.

#### 5. What subpages are crawled and how are they selected?

The actor first inspects the homepage. If contacts are incomplete, it evaluates navigation menus, footers, and internal links using prioritized scoring (contact pages first, followed by imprint/legal, customer support, and about/team pages) up to your `maxPagesPerDomain` limit.

***

### Legality & Compliance

- **Public Data Only**: This scraper gathers strictly publicly accessible business contact information displayed on company websites, contact forms, and public footers.
- **Strict robots.txt Adherence**: The actor automatically fetches and honors the `robots.txt` file of every host (including crawl delays). Any path disallowed by `robots.txt` is skipped without scraping.
- **No Circumvention of Controls**: The actor does not attempt to bypass paywalls, captchas, or login screens (returning explicit status codes instead).
- **GDPR & ePrivacy Responsibility**: Generic business email addresses (`info@company.com`, `sales@company.com`) represent corporate contact details. Personal business addresses (`firstname.lastname@company.com`) are flagged via `isPersonal` and `isRole`. Users of this actor are responsible for ensuring that their outreach complies with applicable data protection regulations (e.g., GDPR, ePrivacy Directive, CAN-SPAM).

***

### Feedback & Reviews

If this actor saved you time and helped your lead generation workflow, please consider leaving a ⭐️⭐️⭐️⭐️⭐️ review on the [Apify Store](https://apify.com/store). Your feedback helps us continually improve and support new features!

# Actor input Schema

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

List of company domains (e.g. 'ovh.pl') or full URLs (e.g. 'https://www.gov.pl'). Normalized to HTTPS and deduplicated per domain so you get one clean, merged contact record per company. Note: robots.txt is always honored for every domain and subpage.

## `maxPagesPerDomain` (type: `integer`):

Maximum number of pages to fetch per domain (homepage + priority contact/about/impressum/team/support/legal subpages). You pay per domain with contacts found, not per page.

## `followLinktree` (type: `boolean`):

When a public linktr.ee/<handle> link is discovered on the website, also fetch it (honoring linktr.ee/robots.txt) to extract additional mailto:, tel:, WhatsApp, and social links.

## `verifyEmailsMx` (type: `boolean`):

Perform DNS MX record verification for each extracted email domain (included at no extra cost), alongside role, free-mail, disposable, and personal email flags.

## `defaultCountryCode` (type: `string`):

Optional two-letter ISO country code (e.g. 'PL', 'US', 'DE', 'GB') used to parse local phone numbers without an international prefix into E.164 format.

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

Optional proxy configuration for fetching target websites.

## Actor input object example

```json
{
  "urls": [
    "https://apify.com",
    "https://ovh.pl"
  ],
  "maxPagesPerDomain": 5,
  "followLinktree": true,
  "verifyEmailsMx": true,
  "defaultCountryCode": ""
}
```

# Actor output Schema

## `allDomains` (type: `string`):

One deduplicated record per input domain with status, statusReason, emails, MX verification flags, E.164 phones, WhatsApp, 15 social platforms, and Schema.org companyInfo.

## `contactsFound` (type: `string`):

Compact view with domain, status, emails, emailsVerified, phones, whatsapps, and socials.

# 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://apify.com",
        "https://ovh.pl"
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("eszetael_lab/contact-details-scraper").call(input);

// Fetch and print Actor results from the run's dataset (if any)
console.log('Results from dataset');
console.log(`💾 Check your data here: https://console.apify.com/storage/datasets/${run.defaultDatasetId}`);
const { items } = await client.dataset(run.defaultDatasetId).listItems();
items.forEach((item) => {
    console.dir(item);
});

// 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/js/docs

```

## Python example

```python
from apify_client import ApifyClient

# Initialize the ApifyClient with your Apify API token
# Replace '<YOUR_API_TOKEN>' with your token.
client = ApifyClient("<YOUR_API_TOKEN>")

# Prepare the Actor input
run_input = { "urls": [
        "https://apify.com",
        "https://ovh.pl",
    ] }

# Run the Actor and wait for it to finish
run = client.actor("eszetael_lab/contact-details-scraper").call(run_input=run_input)

# Fetch and print Actor results from the run's dataset (if there are any)
print(f"💾 Check your data here: https://console.apify.com/storage/datasets/{run.default_dataset_id}")
for item in client.dataset(run.default_dataset_id).iterate_items():
    print(item)

# 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/python/docs/quick-start

```

## CLI example

```bash
echo '{
  "urls": [
    "https://apify.com",
    "https://ovh.pl"
  ]
}' |
apify call eszetael_lab/contact-details-scraper --silent --output-dataset

```

## MCP server setup

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

The hosted server signs you in with OAuth on first connect, so no API token belongs in this config. Clients without OAuth support can send an `Authorization: Bearer <APIFY_API_TOKEN>` header instead, using a token from API & Integrations in Apify Console (https://console.apify.com/settings/integrations).

## OpenAPI specification

Download the OpenAPI definition: https://api.apify.com/v2/actors/TzchmaaK43wYwc3lO/builds/zFiGrbt4kBJP5KLUY/openapi.json
