# Website Contact Enricher: Emails, Phones, Socials per Domain (`angadseth/website-contact-enricher`) Actor

One clean lead row per company domain: role and personal emails (MX-checked), phones in E.164, LinkedIn and 8 more socials, address, contact/about/careers pages, contact form, tech stack, ecommerce platform, email provider and domain age. Reads up to 20 pages per site.

- **URL**: https://apify.com/angadseth/website-contact-enricher.md
- **Developed by:** [Angad Jangir](https://apify.com/angadseth) (community)
- **Categories:** Lead generation, Business
- **Security:** Limited permissions
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

$5.00 / 1,000 enriched domains

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 Enricher: emails, phones and socials per domain

**Give it a list of company domains. Get back one finished lead row per company**: the best email to write to, every published email tagged role or personal and MX-checked, phones in E.164, LinkedIn and 8 more social profiles, postal address, contact / about / careers page links, whether there is a contact form, the tech stack, the ecommerce platform, the email provider and the domain's age.

No more de-duplicating ten messy page-level records per website. One domain in, one clean row out, ready for your CRM.

### Who uses it

- **Sales and lead-gen teams** turning a list of target accounts (from a trade show, a directory, Google Maps or a CSV) into contactable leads.
- **Agencies** qualifying prospects: "Shopify stores on Google Workspace with a contact form and a LinkedIn page".
- **Data and RevOps teams** filling empty fields in a CRM (HubSpot, Salesforce, Pipedrive, Attio) on a schedule.
- **AI agents** that need a company's contact details in one tool call (see MCP below).

### Why this one

| | Page-by-page contact scrapers | **Website Contact Enricher** |
|---|---|---|
| Output | One record per crawled **page**; you merge them yourself | **One record per domain**, already merged |
| Best email to use | Not chosen | `primaryEmail`: own-domain role mailbox first (info@, contact@, sales@ ...), never privacy@/abuse@ when a better one exists |
| Email quality | Raw strings, including `logo@2x.png`, `user@example.com`, Sentry/Wix keys | Junk removed; each email typed `role` / `personal`, `mxValid` from live MX records, `sourceUrl` |
| Hidden emails | Often missed | `name [at] domain [dot] com`, `(at)/(dot)`, Cloudflare email protection, HTML entities, mailto lists |
| Phones | Any digit run in the text | Click-to-call links, schema.org and text numbers next to "phone/call/tel"; validated with libphonenumber, **E.164**, country and line type (mobile, toll-free ...), fax numbers dropped |
| Socials | Share buttons and post links mixed in | Company profiles only, normalised: LinkedIn company page, X/Twitter, Facebook, Instagram, YouTube, TikTok, GitHub, Pinterest, WhatsApp |
| Which pages | Crawls whatever it finds | Goes straight to contact, impressum, about, team, careers and privacy pages (from the site's own links, then /contact and /about) |
| Company data | No | Company name, legal name, description, logo, favicon, language, country (with source), address |
| Firmographics | No | Tech stack (400+ technologies), ecommerce platform, email provider from MX, domain registration date and age (RDAP) |
| Price | Per page: about $0.002 x 5-10 pages = **$10-20 per 1,000 sites** | **$3 per 1,000 domains**, flat |

### Input

| Field | Default | What it does |
|---|---|---|
| `domains` | 2 examples | Domains, URLs or even email addresses. Duplicates and `www.` merge into one row per company. |
| `maxPagesPerDomain` | 8 | Pages per company, homepage included (1-20). |
| `maxItems` | 1000 | Stop after this many company rows. |
| `includePersonalEmails` | true | Off = keep only role mailboxes (info@, sales@ ...). |
| `checkMx` | true | Check each email domain's MX records and fill `mxValid`. |
| `includeTechStack` | true | Detect CMS, ecommerce, analytics, pixels, chat, CDN, CRM ... |
| `includeDomainAge` | true | Registration date and age from RDAP. |
| `includeFailedDomains` | true | Keep a row (status `unreachable`/`blocked`) for sites that could not be read. |

```json
{
  "domains": ["allbirds.com", "https://sproutsocial.com/contact-us/", "jane@zerodha.com"],
  "maxPagesPerDomain": 5,
  "includePersonalEmails": false
}
```

### Output: one row per domain (real example)

Shortened from a real run (`techStack` cut to 6 of 29 entries):

```json
{
  "domain": "sproutsocial.com",
  "input": "sproutsocial.com",
  "finalUrl": "https://sproutsocial.com/",
  "status": "ok",
  "error": null,
  "companyName": "Sprout Social",
  "legalName": "Sprout Social, Inc.",
  "description": "Maximize your social ROI with Sprout Social, trusted by 30k+ brands. Power everything from publishing and engagement to analytics and influencer marketing. Try 30 days free.",
  "logoUrl": "https://brandfolder.sproutsocial.com/VXTUTVL1/at/8vwz9b7w83rhgfc8xvm8t8/lgo_sprout_lockup.png",
  "faviconUrl": "https://media.sproutsocial.com/uploads/favicon.ico",
  "language": "en-US",
  "country": "US",
  "countrySource": "address",
  "primaryEmail": "info@sproutsocial.com",
  "primaryPhone": "+18668783231",
  "emails": [
    { "email": "info@sproutsocial.com", "type": "role", "sourceUrl": "https://sproutsocial.com/", "mxValid": true },
    { "email": "pr@sproutsocial.com", "type": "role", "sourceUrl": "https://sproutsocial.com/contact-us/", "mxValid": true },
    { "email": "support@sproutsocial.com", "type": "role", "sourceUrl": "https://sproutsocial.com/legal/privacy-policy/", "mxValid": true }
  ],
  "phones": [
    { "raw": "+1-866-878-3231", "e164": "+18668783231", "country": "US", "type": "toll_free", "sourceUrl": "https://sproutsocial.com/contact-us/" }
  ],
  "socials": {
    "linkedin": "https://www.linkedin.com/company/sprout-social-inc-",
    "twitter": "https://x.com/SproutSocial",
    "facebook": "https://www.facebook.com/SproutSocialInc",
    "instagram": "https://www.instagram.com/sproutsocial",
    "youtube": "https://www.youtube.com/user/SproutSocial",
    "tiktok": "https://www.tiktok.com/@sproutsocial",
    "github": null,
    "pinterest": "https://www.pinterest.com/sproutsocial",
    "whatsapp": null
  },
  "address": { "full": "131 S. Dearborn St., Ste. 700, Chicago, IL, 60603, USA", "street": "131 S. Dearborn St., Ste. 700", "city": "Chicago", "region": "IL", "postalCode": "60603", "country": "US" },
  "contactPageUrl": "https://sproutsocial.com/contact-us/",
  "aboutPageUrl": "https://sproutsocial.com/about/",
  "careersPageUrl": "https://sproutsocial.com/careers/",
  "hasContactForm": false,
  "techStack": [
    { "name": "Next.js", "category": "JavaScript framework" },
    { "name": "React", "category": "JavaScript framework" },
    { "name": "Amazon CloudFront", "category": "CDN" },
    { "name": "Amazon Web Services", "category": "Hosting" },
    { "name": "Adobe Experience Platform Launch", "category": "Tag manager" },
    { "name": "Salesforce", "category": "CRM" }
  ],
  "ecommercePlatform": null,
  "emailProvider": "Google Workspace",
  "domainCreatedAt": "2009-09-22T04:35:18.000Z",
  "domainAgeYears": 17.1,
  "pagesCrawled": 5,
  "crawledUrls": ["https://sproutsocial.com/", "https://sproutsocial.com/contact-us/", "https://sproutsocial.com/about/", "https://sproutsocial.com/careers/", "https://sproutsocial.com/legal/privacy-policy/"],
  "scrapedAt": "2026-11-06T01:33:57.612Z"
}
```

Every field is always present; it is `null` (or `[]`) when the site does not publish it.

- `status`: `ok`, `unreachable` (DNS, timeout, server error) or `blocked` (bot protection, or robots.txt does not allow the homepage). Blocked and unreachable rows still carry `emailProvider` and the domain age.
- `ecommercePlatform`: Shopify / Shopify Plus, WooCommerce, Magento, BigCommerce, PrestaShop, OpenCart, Salesforce Commerce Cloud, Shopware, Wix, Squarespace, Webflow and more.
- `emailProvider`: Google Workspace, Microsoft 365, Zoho Mail, Proton Mail, GoDaddy, Titan, Mimecast, Proofpoint ..., `Self-hosted`, or `Other`.
- `countrySource`: `address`, `domain` (country-code domain such as .de or .in), `phone` or `language`.

The **Leads** table view shows domain, company, email, phone, LinkedIn, ecommerce platform, email provider, country and domain age. A **SUMMARY** record in the key-value store gives counts (ok / unreachable / blocked, with email, with phone, with LinkedIn), failures and warnings.

### Pricing

**$3 per 1,000 domains** ($0.003 per company row), charged per row saved, however many pages were read for it. Turn `includeFailedDomains` off to pay only for sites that could be read. Platform usage is small: plain HTTP requests, no browser.

### Use it with your tools

- **Google Sheets**: export the dataset as CSV or connect the Apify integration for Google Sheets; one row per company maps straight onto a sheet.
- **Make, Zapier, n8n**: trigger a run with new domains from a form, a CRM or a sheet, then push each row to HubSpot, Salesforce, Pipedrive, Airtable or Slack.
- **Schedules and webhooks**: re-enrich your account list every month and get a webhook when the run is done.
- **API**: `POST https://api.apify.com/v2/acts/<this-actor>/run-sync-get-dataset-items` with `{"domains": [...]}` returns the rows directly.
- **MCP for AI agents**: add this Actor to the Apify MCP server (`https://mcp.apify.com`) and Claude, ChatGPT or Cursor can call it as a tool: "find the contact email and LinkedIn of these 20 companies".

### How it works

1. Reads robots.txt and the homepage (https, then www., then http).
2. Finds the contact, impressum, about, team, careers and privacy pages from the site's own links (any language: Kontakt, Contacto, Über uns ...), tries `/contact` and `/about` when none is linked, and stays on the company's own domain.
3. Reads them politely: one page at a time per site with a pause between pages, at most 5 sites in parallel, 15 s timeout, one retry.
4. Merges what every page shows into one row, then adds MX, tech-stack and RDAP data.

### Limits

- Sites that render all content with JavaScript, or sit behind bot protection (Cloudflare challenge, 403), give little or nothing and are marked `blocked`. The Actor never tries to bypass a protection.
- It reads only what the website itself publishes. It does not guess email patterns (first.last@) or buy data from third parties.
- Country is a best guess; `countrySource` tells you how strong it is.
- RDAP dates are not published by every registry (some country-code domains), so `domainCreatedAt` can be `null`.

### FAQ

**Is this legal?** The Actor reads public pages the way a visitor's browser does, obeys robots.txt, and collects only business contact details that each company chose to publish on its own website. It does not log in, solve captchas or get around blocks.

**What about GDPR and other privacy laws?** Role mailboxes (info@, sales@) are company contact points. Personal addresses (jane.doe@) are personal data in the EU and UK even when published. Set `includePersonalEmails` to false to keep role mailboxes only. You are responsible for having a lawful basis for any outreach, honouring opt-outs and following anti-spam rules (GDPR, ePrivacy, CAN-SPAM, India's DPDP Act).

**Does `mxValid` mean the mailbox exists?** It means the email's domain has mail servers and can receive mail. No email is sent and no mailbox is probed.

**Why did a site give no email?** Many companies publish only a contact form (see `hasContactForm`) or load their contact page with JavaScript. Try a higher `maxPagesPerDomain`, or use the `contactPageUrl` the row gives you.

**Can I paste URLs or emails instead of domains?** Yes. `https://acme.com/en/contact`, `www.acme.com` and `jane@acme.com` all become one row for `acme.com`.

### More Actors by angadseth

- [GeM Tenders Scraper (India Government e-Marketplace Bids)](https://apify.com/angadseth/gem-tenders-scraper): Get live GeM bids and reverse auctions from India's Government e-Marketplace
- [India eProcurement Tenders Scraper (CPPP + 30 State Portals)](https://apify.com/angadseth/india-eprocurement-tenders): Live government tenders from India's central eProcure (CPPP), central PSU, defence and 30+ state NIC eProcurement portals in one run.
- [Apple App Store Scraper: apps, ratings and reviews](https://apify.com/angadseth/app-store-scraper): Get Apple App Store apps, ratings and customer reviews from many countries in one run
- [ATS Jobs Feed: Greenhouse, Lever, Ashby, Workday + 5 more](https://apify.com/angadseth/ats-jobs-feed): Get every open job from a company's own careers board
- [Bluesky Scraper: Posts, Profiles, Followers and Feeds](https://apify.com/angadseth/bluesky-scraper): Scrape Bluesky without a login
- [Website Screenshot & PDF: full page, mobile, element, WebP](https://apify.com/angadseth/website-screenshot): Screenshots (PNG, JPEG, WebP) and PDFs of any list of URLs.
- [Remote Jobs Aggregator: RemoteOK, Jobicy, Arbeitnow + 2 more](https://apify.com/angadseth/remote-jobs-aggregator): Remote jobs from RemoteOK, Jobicy, Arbeitnow, Working Nomads and 4 Day Week in one clean feed.
- [PDF & Document Text Extractor: PDF, DOCX, PPTX, XLSX to Text](https://apify.com/angadseth/pdf-text-extractor): Turn public document URLs (PDF, DOCX, PPTX, XLSX, CSV, HTML, TXT) into clean text

# Actor input Schema

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

One company per line: a domain (acme.com), a URL (https://www.acme.com/contact) or even an email address (jane@acme.com). Duplicates and www. variants are merged, so each company gives exactly one row. Subdomains (shop.acme.com) are read from that subdomain but reported under the company domain.

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

Pages read per company, homepage included. The Actor follows the site's own links to the contact, impressum, about, team, careers and privacy pages (in that order) and tries /contact and /about when no link is found. 8 covers almost every site; 3 is faster and still finds most contact pages.

## `maxItems` (type: `integer`):

Stop after this many company rows. You pay per company row saved.

## `includePersonalEmails` (type: `boolean`):

On: keep every email the company publishes, each tagged "role" (info@, sales@, support@ ...) or "personal" (jane.doe@). Off: keep only role mailboxes, which is the safer choice for GDPR-sensitive outreach.

## `checkMx` (type: `boolean`):

Look up the MX records of every email's domain and set mxValid (true = the domain accepts mail). Emails on domains without mail servers are never picked as primaryEmail. No email is sent.

## `includeTechStack` (type: `boolean`):

Detect the site's technologies (CMS, ecommerce, analytics, ads pixels, chat, CDN, marketing tools ...) from its HTML, headers and DNS. ecommercePlatform and emailProvider are filled either way.

## `includeDomainAge` (type: `boolean`):

Add the domain's registration date and age in years from RDAP, the registries' official successor of WHOIS. Useful to filter out brand-new or very old companies.

## `includeFailedDomains` (type: `boolean`):

On: every input domain gets a row, with status "unreachable" or "blocked" when the site could not be read (it still has email provider and domain age). Off: only readable sites are saved, so you do not pay for rows without website data.

## Actor input object example

```json
{
  "domains": [
    "allbirds.com",
    "sproutsocial.com",
    "zerodha.com"
  ],
  "maxPagesPerDomain": 5,
  "maxItems": 1000,
  "includePersonalEmails": true,
  "checkMx": true,
  "includeTechStack": true,
  "includeDomainAge": true,
  "includeFailedDomains": true
}
```

# Actor output Schema

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

One row per company domain, as a dataset you can download in JSON, CSV or Excel.

## `summary` (type: `string`):

How many domains were read, how many had an email, phone and LinkedIn, and which sites were unreachable or blocked.

# 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 = {
    "domains": [
        "allbirds.com",
        "sproutsocial.com",
        "zerodha.com"
    ],
    "maxPagesPerDomain": 5
};

// Run the Actor and wait for it to finish
const run = await client.actor("angadseth/website-contact-enricher").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 = {
    "domains": [
        "allbirds.com",
        "sproutsocial.com",
        "zerodha.com",
    ],
    "maxPagesPerDomain": 5,
}

# Run the Actor and wait for it to finish
run = client.actor("angadseth/website-contact-enricher").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 '{
  "domains": [
    "allbirds.com",
    "sproutsocial.com",
    "zerodha.com"
  ],
  "maxPagesPerDomain": 5
}' |
apify call angadseth/website-contact-enricher --silent --output-dataset

```

## MCP server setup

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

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/K6rqZldghiPIUZFyn/builds/03LE1Zi07TqBOtoB8/openapi.json
