# Website Contact Scraper - Emails, Phones & Socials (`automa-flow/website-contact-enricher`) Actor

Paste company domains or website URLs. Get one contact record per company with public emails, phones, social profiles, MX status and the page where each email was found.

- **URL**: https://apify.com/automa-flow/website-contact-enricher.md
- **Developed by:** [Vadim Bezrukov](https://apify.com/automa-flow) (community)
- **Categories:** Lead generation, Automation, Business
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $2.00 / 1,000 domain inspecteds

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 Scraper - Emails, Phones & Socials

Paste company domains. You get one contact record per company: public emails, phone numbers and social profiles, with an MX check and the page where each email was found.

Use it after a Google Maps, directory or CRM export, when the next step is a spreadsheet, a CRM or an outreach workflow. You do not need a second job to merge the homepage, contact page and about page into one row, and you do not need a separate MX lookup for the addresses the site actually published.

This Actor reads public HTML over HTTP. It does not open a browser, log in, guess addresses, or build emails from people's names. `HAS_MX` means the email domain publishes mail servers. It does not mean the mailbox exists, and a role label such as `sales` does not prove who reads that inbox.

### Run it

```json
{
  "websites": ["python.org", "https://www.iana.org/"],
  "maxPagesPerSite": 5,
  "includePhones": true,
  "includeSocials": true,
  "checkMx": true
}
```

The default input is `python.org`. The run returns one Dataset row for that domain with each public email it found, the page the email came from and its MX status. On another domain, `NO_CONTACTS` is a finished inspection with nothing public to keep. It is not a failed download.

For an agent or API run, set `maxTotalChargeUsd` to at least $0.01 plus $0.002 for each domain you expect to inspect. At the default 512 MB the start event is $0.01. That cap is the event bill. Platform usage is included.

### What you get

Each row is one registrable domain. `example.com`, `www.example.com` and `https://example.com/?utm_source=x` are the same target. The original string stays in `input`. `domain` is the public-suffix domain. `homepageUrl` is the final homepage after ordinary redirects.

Emails include `sourceUrl`, `sourceType`, `role` and `mxStatus`. `mxStatus` is `HAS_MX`, `NO_MX`, `DNS_FAILED` or `NOT_CHECKED`. Phones are parsed numbers, not raw digit strings. Social profiles are organization links the site published. Share, intent and login URLs are left out. `address` is the postal address from the site's Organization or LocalBusiness structured data, or its first HTML address block, and is null when the site publishes none.

### Status

| Status | Meaning | Charged |
| --- | --- | --- |
| SUCCESS | Pages loaded and at least one email, phone or social profile was found | Yes |
| NO\_CONTACTS | Pages loaded and none of those were found | Yes |
| PARTIAL | At least one page loaded, and a later page failed | Yes |
| BLOCKED | HTTP 401, 403, 429, a challenge page, or robots.txt disallows the homepage or the page a redirect landed on. A disallowed subdomain page is skipped and does not block the domain | No |
| DOWNLOAD\_FAILED | DNS, timeout, or HTTP 5xx after retries | No |
| INVALID\_DOMAIN | Not a public http(s) website, including private and metadata addresses | No |
| NOT\_INSPECTED | The spending limit or the run's time limit was reached before this domain was saved. `error` says which | No |

One bad domain does not fail the batch. The run fails only when the input envelope is invalid, the Dataset cannot be written, or billing cannot be reconciled.

### Run it again

Run the same saved list next week when you want a fresh observation, or paste the next batch of domains from your CRM. `fingerprint` changes when the published contacts change. An MX result that flips between `HAS_MX` and `DNS_FAILED` does not change it. It is set only for `SUCCESS` and `NO_CONTACTS`, where the selected pages were read. `PARTIAL`, `BLOCKED`, `DOWNLOAD_FAILED`, `INVALID_DOMAIN` and `NOT_INSPECTED` rows have `fingerprint: null`, so a week the site could not be read never looks like a contact change. Keep the last complete row in that case. A weekly schedule of a domain list is the usual second run. There is no separate monitor mode in this version.

```json
{
  "websites": ["python.org", "https://www.iana.org/"]
}
```

In Apify, open Schedules and run that input once a week. On the run's webhook, choose a successful finish and post the Dataset to your CRM or sheet. Compare `fingerprint` with the previous row for the same `domain`.

What changed is in the [changelog](CHANGELOG.md).

### Limits

Up to 2,000 domains per run. The default spending limit of $1 covers about 495 inspected domains. For a full 2,000-domain list, raise `maxTotalChargeUsd` to $4.01. Domains past the limit are `NOT_INSPECTED` and not charged. A run also stops starting new domains two minutes before its timeout, so a long list ends with the inspected rows saved and the rest `NOT_INSPECTED`, and `timeLimitReached` is true in `RUN_SUMMARY`. On staging, 100 large organization sites took about 2.5 minutes at the default 512 MB. For lists of more than 1,000 large sites, give the run more memory or time, or split the list. Each site is the homepage plus a few same-site contact, about, impressum or support links, at most 8 pages and 5 by default. The Actor does not crawl the whole site.

It follows redirects, including http to https. If the bare domain does not serve a page, the www host is tried, and a www host that redirects to the apex is followed. Redirects into private, loopback or link-local addresses are refused. If the homepage lands on a different registrable domain, only that landing page is read. A later link that leaves the domain is skipped. Residential proxy is not part of the product. Leave proxy disabled unless a datacenter Apify Proxy is specifically required. When that proxy is on, the connection uses the address from the DNS check, not a later answer for the same name. If the proxy cannot be created, the run continues over a direct connection and says so.

If `robots.txt` disallows a URL for a normal browser user agent, that URL is not read. This is checked for the homepage, for a host reached by a redirect, and separately for a subdomain. A disallow of the homepage or of the landing page is `BLOCKED`, and that row is not charged. A disallow of a later subdomain page skips that page only. The skipped URL is listed in `robotsSkipped`, and the row stays `SUCCESS` or `NO_CONTACTS`.

### Pricing

| Event | Price | When |
| --- | --- | --- |
| Actor start | $0.01 | Once per run at 512 MB |
| Domain inspected (`domain-processed`) | $0.002 | Once per unique domain that was actually inspected |

`NO_CONTACTS` is charged because the pages were fetched and read. Duplicates, invalid inputs, blocked sites, download failures, retries and spending-limit leftovers are not charged. The event price includes platform usage.

### Privacy and lawful use

The Actor collects contact details that the submitted website displays in public HTML, `mailto:` and `tel:` links, or structured data. It does not enter accounts, bypass access controls, or enrich people from a third-party database.

You are responsible for the lawful basis of any later outreach and for anti-spam and privacy rules that apply to you. A public page does not, by itself, make every later use lawful. Do not submit websites you are not allowed to request.

### MCP

Agents can call this Actor directly:

`https://mcp.apify.com?tools=automa-flow/website-contact-enricher`

Ask it to turn a list of company domains into one contact record per domain, including the source page for each email and the MX status. Read `RUN_SUMMARY` before treating a partial batch as complete. Direct execution uses the caller's Apify account. Anonymous search is a separate discovery check and does not run the Actor.

### Fields

`examples/sample_output.json` shows the row shape. It is an illustration of the fields, not a captured live run. `schemaVersion` is 1. Added fields will not rename these.

# Changelog

This Actor's version history is a separate document: https://apify.com/automa-flow/website-contact-enricher/changelog.md

# Actor input Schema

## `websites` (type: `array`):

Company domains or website URLs, up to 2,000 per run. www, https and tracking parameters on the same registrable domain are one target.

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

Homepage plus the highest-scoring contact, about and impressum links. 1 to 8. Default 5.

## `includePhones` (type: `boolean`):

Keep publicly displayed phone numbers that a telephone parser accepts.

## `includeSocials` (type: `boolean`):

Keep organization profile links published on the site. Share and login URLs are ignored.

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

Look up DNS MX records for each email domain. This is not mailbox verification.

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

Leave disabled. Direct HTTP is the normal path. A datacenter Apify Proxy is used only when you turn it on. Residential groups and custom proxy URLs are ignored.

## Actor input object example

```json
{
  "websites": [
    "python.org"
  ],
  "maxPagesPerSite": 5,
  "includePhones": true,
  "includeSocials": true,
  "checkMx": true,
  "proxyConfiguration": {
    "useApifyProxy": false
  }
}
```

# Actor output Schema

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

No description

## `runSummary` (type: `string`):

No description

## `billingReceipt` (type: `string`):

No description

# 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 = {
    "websites": [
        "python.org"
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("automa-flow/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 = { "websites": ["python.org"] }

# Run the Actor and wait for it to finish
run = client.actor("automa-flow/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 '{
  "websites": [
    "python.org"
  ]
}' |
apify call automa-flow/website-contact-enricher --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,automa-flow/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/Yi8ROSL3lgvOnCUVT/builds/m8be2IkQjfZoG8d4u/openapi.json
