# Website Contact Enrichment (Emails, Phones) (`maniginam/website-contact-enrichment`) Actor

Turn a list of company websites into contact records: emails, phone numbers in E.164, LinkedIn, Facebook, Instagram, X, YouTube, and TikTok links, company name, and schema.org address.  Checks the homepage plus contact, about, and team pages.  One flat price per website.

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

## Pricing

from $6.80 / 1,000 website contact records

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

Turn a list of company websites into one clean contact record per website.  For each domain the Actor reads the homepage and the contact, impressum, about, and team pages it links to, then returns the emails, phone numbers, social profiles, company name, and postal address it found.  You pay one flat price per website, not per page.

It pairs well with lead lists that already have a website column, such as property owner, permit, or business registration leads.  Paste the domains in, and get back who to email or call.

### What does it do?

- Reads each website's robots.txt first and skips any page it asks crawlers not to read.
- Reads the homepage, then up to 19 more pages per website that look like contact, impressum, about, team, or location pages.  The default is 5 pages in total, one link deep.
- Collects emails from mailto links, page text (including `name [at] domain [dot] com` spellings), Cloudflare-protected addresses, and schema.org markup.  Image file names, placeholders like `you@example.com`, and tracking addresses are dropped, and every email is syntax checked.
- Optionally looks up each email domain's mail servers (MX records) and sets aside emails whose domain cannot receive mail.  No email is ever sent.
- Collects phone numbers from tel: links, schema.org markup (including contact points), and page text, and writes them in E.164 form like `+12253893000`.  Numbers that are not valid phone numbers are left out.
- Collects the business's own LinkedIn, Facebook, Instagram, X, YouTube, and TikTok profiles, leaving out share buttons, single posts, and embeds.  YouTube channels are recognized by `@handle`, `/channel/`, `/c/`, and `/user/` links, and by the older `youtube.com/name` address.  Profiles the site declares in schema.org markup come first, then links in the site header, footer, or menu, then the homepage, then other pages.  Links on team, press, author, testimonial, and security acknowledgement pages are ignored, personal LinkedIn `/in/` pages are never returned, and each network keeps at most 3 profiles.
- Takes the company name from schema.org markup, the og:site_name tag, or the homepage title, and the postal address from schema.org markup.
- Reads each website one page at a time, at least one second apart, and never logs in, fills forms, or reads pages behind a login.

### Who is it for?

- Sales and business development teams who have a list of company websites but no contacts.
- Agencies and freelancers who build prospect lists for clients.
- Marketers who want to fill in missing emails, phones, and social profiles in a CRM.
- Researchers who need public contact details for many organizations at once.

### How to use

1. Open the Actor in Apify Console and paste your list of websites, one per line.
2. Optionally change how many pages each site gets and whether mail servers are checked under **Crawl settings**.
3. Click **Start** and wait for the run to finish.
4. Open the **Output** tab to preview one contact record per website.
5. Export the records as JSON, CSV, Excel, or XML, or send them to your CRM through an integration.

### Input

| Field | Type | Description |
| --- | --- | --- |
| `websites` | list of strings | Websites to check, as domains like `acme.com` or full addresses.  Duplicates are checked once.  Up to 10,000 per run. |
| `maxPagesPerDomain` | integer | Most pages to read per website, counting the homepage.  Default 5, up to 20. |
| `maxDepth` | integer | How many links away from the homepage to look for contact pages: 0, 1, or 2.  Default 1. |
| `checkMailServers` | boolean | Look up the mail servers of every email domain found.  Default off. |
| `defaultCountry` | string | Two-letter country code for phone numbers written without one.  Default `US`.  Leave it empty to keep only numbers that carry a country code. |
| `maxConcurrency` | integer | How many websites to check at the same time.  Default 5, up to 20. |

```json
{
  "websites": ["acme-roofing.com", "https://www.example-dental.com/"],
  "maxPagesPerDomain": 5,
  "maxDepth": 1,
  "checkMailServers": true,
  "defaultCountry": "US"
}
```

### Output

One record per website.  A website that cannot be read, for example because it is down or its robots.txt blocks crawlers, still gets a record with `status` set to `failed` and the reason in `error`, and that record is not charged.

```json
{
  "domain": "acme-roofing.com",
  "inputUrl": "acme-roofing.com",
  "websiteUrl": "https://www.acme-roofing.com/",
  "status": "ok",
  "error": null,
  "companyName": "Acme Roofing LLC",
  "companyNameSource": "schema.org",
  "pageTitle": "Acme Roofing | Baton Rouge Roof Repair",
  "description": "Family-owned roofing contractor serving Baton Rouge since 1998.",
  "emails": ["info@acme-roofing.com", "estimates@acme-roofing.com"],
  "emailsWithoutMailServer": [],
  "mailServersChecked": true,
  "phones": ["+12255550134"],
  "linkedin": ["https://www.linkedin.com/company/acme-roofing"],
  "facebook": ["https://www.facebook.com/acmeroofingbr"],
  "instagram": ["https://www.instagram.com/acmeroofingbr"],
  "x": [],
  "youtube": [],
  "tiktok": [],
  "address": {
    "street": "123 Main St",
    "city": "Baton Rouge",
    "region": "LA",
    "postalCode": "70801",
    "country": "US"
  },
  "fullAddress": "123 Main St, Baton Rouge, LA 70801, US",
  "pagesChecked": ["https://www.acme-roofing.com/", "https://www.acme-roofing.com/contact-us"],
  "pagesSkippedByRobots": 0,
  "checkedAt": "2026-11-01T18:00:00+00:00"
}
```

### Pricing

This Actor uses pay-per-event pricing.

- **Domain record:** $8.00 per 1,000 websites checked ($0.008 each), whether contacts were found or not.  Websites that could not be read at all, and input lines that are not websites, are delivered as `failed` records free of charge.
- **Store discounts:** Bronze $7.60, Silver $7.20, and Gold $6.80 per 1,000 websites.
- **Actor start:** Apify's standard start event ($0.00005).
- There is no charge per page, so checking 5 or 20 pages on a website costs the same.

Checking 1,000 websites costs $8, and 100 websites cost $0.80.  You can cap any run with Apify's maximum charge setting.  The Actor stops starting new websites once your limit can no longer pay for them, so you are only charged for records that actually reach your dataset.

#### Estimated compute cost

Default memory is 512 MB.  Most of the run time is spent waiting politely between pages, so compute is small next to the event charges.  Measured on Apify at 512 MB with the default settings, 30 company websites took 59 seconds and about $0.0012 in platform usage, and 11 websites took 11 seconds and about $0.0001.  That works out to roughly $0.004 per 100 websites, while the same 100 websites cost $0.80 in domain records.

### How it handles websites

- The Actor goes straight to each website from Apify's servers, without proxies, the way a search engine crawler would.  A site that blocks crawlers gets a `failed` record instead of being forced.
- A robots.txt that cannot be read because the server errors or answers 429 Too Many Requests counts as a request not to crawl.  A missing robots.txt allows everything.
- Addresses that point to private networks, and links to other websites, are never followed.
- Pages larger than 2 MB are cut off, and each request gives up after 15 seconds and one retry.
- A record keeps at most 100 emails, with the website's own first, and 50 phone numbers.  The mail server check looks up at most 20 email domains per website.
- If the platform moves a run to another server, it resumes where it stopped and never delivers or charges a website twice.

### Is it legal to use this data?

The Actor reads only public pages that a website shows to every visitor, and it follows each site's robots.txt.  Business contact details published on a company website are generally fine to collect, but emails and phone numbers that belong to people are personal data in many places.  You are responsible for how you use the results, including the CAN-SPAM Act, the Telephone Consumer Protection Act, Do Not Call registries, the GDPR, and any other privacy or marketing laws that apply to you.  This Actor is an independent tool and is not affiliated with any of the websites it reads.

### API and integrations

You can start this Actor and read its results with the [Apify API](https://docs.apify.com/api/v2), the Apify Python and JavaScript clients, or the Apify CLI.  The API returns each run's dataset in the same formats as the Console.

Apify integrations can pass results on when a run finishes, for example to Google Sheets, Slack, Zapier, Make, or your own webhook.  Set them up on the Actor's **Integrations** tab, and pair them with a schedule for a hands-off feed.

### FAQ

**Why are there no emails for a website that clearly has a contact form?**  Many businesses publish only a form.  The Actor never fills in forms, so those websites come back with phones and social links only.

**Why is an email missing from `emails` but listed in `emailsWithoutMailServer`?**  The mail server check found that the email's domain cannot receive mail, which usually means a typo or a retired domain.

**Can I check more than 20 pages per website?**  No.  Contact details almost always sit on the homepage or one click away, and the limit keeps the Actor gentle on small business websites.

**Why is there no phone number for a website?**  Many companies, especially software businesses, publish no phone number at all.  The Actor returns only numbers the website shows in a tel: link, schema.org markup, or page text, and it never guesses one.

**Why are my team's LinkedIn profiles missing?**  The social fields hold the business's own profiles.  Personal LinkedIn pages, and the people listed on team, press, or testimonial pages, are left out on purpose.

**Does it find personal emails of employees?**  Only those the website itself publishes.  It does not guess addresses or look them up anywhere else.

### Related Actors

- [Greenhouse, Lever & Workday Jobs Tracker](https://apify.com/maniginam/career-site-jobs-hiring-tracker): open jobs from Greenhouse, Lever, Ashby, and Workday career sites.
- [AI Brand Visibility: ChatGPT, Perplexity, Gemini](https://apify.com/maniginam/ai-brand-visibility-tracker): how ChatGPT, Perplexity, Gemini, and Claude mention your brand.
- [News & Brand Mention Monitor (Google News + RSS)](https://apify.com/maniginam/news-brand-mention-monitor): news mentions of your brand in Google News and RSS feeds.

### Feedback and custom requests

Found a bug, a wrong value, or a source that changed?  Open an issue in the Issues tab on this Actor's page.  Need another field, filter, or source, or a version built for your team?  Describe it in the Issues tab as a custom request.

# Changelog

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

# Actor input Schema

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

Company websites to check, one per line, as a domain like acme.com or a full address like https://www.acme.com/.  Each website is checked once, even if it is listed twice.  Up to 10,000 per run.

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

Most pages to read on each website, counting the homepage.  The Actor reads the homepage first, then the contact, impressum, about, and team pages it links to.

## `maxDepth` (type: `integer`):

How many links away from the homepage to look for contact pages.  0 reads only the homepage, 1 also reads the contact pages it links to, and 2 also follows contact links found on those pages.

## `checkMailServers` (type: `boolean`):

When on, the Actor looks up the mail servers (MX records) of every email domain it finds.  Emails whose domain cannot receive mail move to emailsWithoutMailServer.  No email is sent.

## `defaultCountry` (type: `string`):

Two-letter country code used to read phone numbers written without a country code, like (225) 389-3000.  Leave it empty to keep only numbers written with a country code in E.164 form.

## `maxConcurrency` (type: `integer`):

How many websites to check at the same time.  Each website is still read one page at a time, at least one second apart.

## Actor input object example

```json
{
  "websites": [
    "apify.com",
    "crawlee.dev"
  ],
  "maxPagesPerDomain": 5,
  "maxDepth": 1,
  "checkMailServers": false,
  "defaultCountry": "US",
  "maxConcurrency": 5
}
```

# Actor output Schema

## `results` (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": [
        "apify.com",
        "crawlee.dev"
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("maniginam/website-contact-enrichment").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": [
        "apify.com",
        "crawlee.dev",
    ] }

# Run the Actor and wait for it to finish
run = client.actor("maniginam/website-contact-enrichment").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": [
    "apify.com",
    "crawlee.dev"
  ]
}' |
apify call maniginam/website-contact-enrichment --silent --output-dataset

```

## MCP server setup

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

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/jDWIxr733c4FRTecM/builds/Fa7QQlYkLg63eo6Wh/openapi.json
