# Website Contact Extractor: Emails, Phones & Social Links (`apisamurai/website-contact-extractor`) Actor

Extract emails, phone numbers and social media links from company websites. Paste domains, get one row per site with the page each email was found on. Pay only for websites where contacts are found.

- **URL**: https://apify.com/apisamurai/website-contact-extractor.md
- **Developed by:** [API Samurai](https://apify.com/apisamurai) (community)
- **Features:** Limited permissions
- **Stats:** 3 total users, 2 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

$2.00 / 1,000 website with contacts

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 Extractor: Emails, Phones & Social Links

Paste a list of company websites and get one clean row per site: email addresses (each with the page it was found on), phone numbers in international format, social media profiles, the contact page link and the company name. The Actor reads the home page and the pages where companies put their contact details (contact, imprint or Impressum, legal notice, about and team pages), so you get the contacts without crawling the whole site.

You pay only for websites where at least one email, phone number or social link is found. Dead, blocked and empty websites are free.

### What you can use it for

- **Lead generation**: turn a list of domains from a CRM export, a trade fair list or a directory into emails and phone numbers.
- **CRM enrichment**: add missing emails, phones and LinkedIn company pages to existing accounts.
- **Social media research**: find the official LinkedIn, Facebook, Instagram, X, YouTube, TikTok, Pinterest, WhatsApp and Telegram profiles of brands.
- **List cleaning**: see which domains are dead, parked or for sale, and which email domains cannot receive mail.
- **Agencies and outreach**: get the contact page link to send a message through the site's form.

### How to use it

1. Paste websites into **Websites**, one per line. Domains (`acme.com`), links (`https://acme.com/contact`) and lists copied from a sheet all work.
2. Keep **Max pages per website** at 5 for most sites. Raise it for large sites with many regional contact pages.
3. Click **Start**. A list of 100 websites usually takes about one minute.
4. Download the results as Excel, CSV or JSON, or open the **SUMMARY** record for counts per status.

### Input

| Field | Type | Default | What it does |
|---|---|---|---|
| `websites` | list | | Domains or links, one per line. Each domain is checked once. |
| `maxPagesPerSite` | number | 5 | Pages read per website: home page, then contact, imprint, legal, about and team pages on the same site. |
| `includePhones` | yes/no | yes | Phone numbers from phone links, structured data and text next to words like Phone or Tel. |
| `includeSocials` | yes/no | yes | Links to the company's profiles on nine social networks. |
| `checkEmailDomains` | yes/no | no | DNS lookup that marks emails whose domain has no mail server. |
| `maxTotalItems` | number | | Optional cap on websites checked in one run. |

Example input:

```json
{
    "websites": ["apify.com", "https://www.buffer.com/contact", "heise.de"],
    "maxPagesPerSite": 5,
    "checkEmailDomains": true
}
```

### Output

One row per website. Real example from a run (social networks without links shortened):

```json
{
    "input": "apify.com",
    "domain": "apify.com",
    "url": "https://apify.com/",
    "companyName": "Apify",
    "emails": [
        { "email": "support@apify.com", "foundOn": "https://apify.com/", "domainCanReceiveMail": null },
        { "email": "hello@apify.com", "foundOn": "https://apify.com/contact", "domainCanReceiveMail": null }
    ],
    "phones": [],
    "socials": {
        "linkedin": ["https://www.linkedin.com/company/apify"],
        "tiktok": ["https://www.tiktok.com/@apifytech", "https://www.tiktok.com/@apifyoffice"],
        "facebook": [], "instagram": [], "x": [], "youtube": [], "pinterest": [], "whatsapp": [], "telegram": []
    },
    "contactPageUrl": "https://apify.com/contact",
    "pagesVisited": ["https://apify.com/", "https://apify.com/contact", "https://apify.com/about", "https://apify.com/contact-sales"],
    "status": "ok",
    "statusDetail": null,
    "scrapedAt": "2026-11-10T13:24:42.625Z"
}
```

Phones look like `{ "phone": "+18007787879", "country": "US", "foundOn": "https://buffer.com/legal" }`.

The `status` field tells you what happened on each website:

| Status | Meaning | Charged |
|---|---|---|
| `ok` | At least one email, phone number or social link found | Yes |
| `no contacts found` | The site works but shows no contacts, or the domain is parked or for sale | No |
| `unreachable` | The domain does not exist, times out or returns an error | No |
| `blocked` | The site refuses automated visitors | No |

`statusDetail` adds the reason, for example `domain not found`, `HTTP 403` or `domain parked or for sale`.

### Pricing

**$2.00 per 1,000 websites with contacts found** ($0.002 per website). No start fee. Websites with no contacts, unreachable or blocked websites and duplicates are free. Several pages per website are included in the price.

### Integrations

Run it from the Apify API with the Actor ID `apisamurai~website-contact-extractor`:

```bash
curl -X POST "https://api.apify.com/v2/acts/apisamurai~website-contact-extractor/run-sync-get-dataset-items?token=YOUR_API_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"websites": ["apify.com", "heise.de"]}'
```

Python:

```python
from apify_client import ApifyClient

client = ApifyClient("YOUR_API_TOKEN")
run = client.actor("apisamurai/website-contact-extractor").call(run_input={"websites": ["apify.com"]})
for row in client.dataset(run["defaultDatasetId"]).iterate_items():
    print(row["domain"], [e["email"] for e in row["emails"]])
```

JavaScript:

```js
import { ApifyClient } from 'apify-client';

const client = new ApifyClient({ token: 'YOUR_API_TOKEN' });
const run = await client.actor('apisamurai/website-contact-extractor').call({ websites: ['apify.com'] });
const { items } = await client.dataset(run.defaultDatasetId).listItems();
```

It also works with **Zapier**, **Make** and **n8n** (Apify apps: run the Actor, then read the dataset), with Google Sheets and webhooks, and with AI agents through the **Apify MCP server** (`https://mcp.apify.com`), so an assistant can look up the contacts of a company website for you.

### FAQ

**Which input formats work?**
Domains (`acme.com`), full links (`https://www.acme.com/de/kontakt`), links with tracking parameters, lists pasted from a sheet or a chat (commas, semicolons, bullets, quotes, markdown links) and `{ "url": "..." }` objects. An email such as `sales@acme.com` is read as the website `acme.com`. Field names from other contact scrapers (`startUrls`, `urls`, `domains`, `maxRequestsPerStartUrl`) are read too. Social network pages and free email addresses (Gmail, Outlook) are skipped with a note in the log.

**Which pages does it read?**
The page you give (or the home page), then pages found through the site's own links: contact first, then imprint and legal notice, about, team, locations and support. It stays on the same website and never follows links to other domains.

**How are phone numbers formatted?**
In international E.164 format (`+4930123456`) when the country is clear: from the number itself, the site's country domain (`.de`, `.co.uk`) or its page language (`en-GB`). Otherwise the number is given as written, digits only, with `country: null`. Numbers in the page text are only taken next to a word such as Phone, Tel or Telefon, so order numbers and dates are left out. Fax numbers are skipped.

**Does it verify that an email address exists?**
No. With **Check email domains** on, each email gets `domainCanReceiveMail`: `false` when its domain does not exist or has no mail server, `true` when it has one, `null` when the lookup failed. It does not contact mail servers or test single mailboxes, so it never sends anything.

**Does it find personal emails of employees or LinkedIn profiles of staff?**
No. It returns what the company publishes on its own website. Personal social links found on inner pages (team members, commenters) are left out unless they are the only link to that network on the site. We do not offer lead enrichment from other databases.

**Why is a website blocked or without contacts?**
Some sites refuse automated visitors, and some show contacts only through scripts or images. A blocked page is tried once more through a residential IP. Sites that load their content only with scripts may show no contacts, because the Actor reads the HTML and does not run a browser. You are not charged for these websites.

**What junk is filtered out?**
Image file names that look like emails (`logo@2x.png`), placeholder addresses (`name@example.com`, `your@email.com`), error-tracking and site-builder addresses, tracking IDs, share buttons, and the default social links that site builders leave in footers.

### Fair billing

- One charge per website, only when at least one email, phone number or social link is found.
- Unreachable, blocked, parked and empty websites are free and still get a row, so you can clean your list.
- Duplicate domains in the input are checked and charged once.
- No start fee and no charge per page.
- The run stops cleanly at your spending limit; rows saved so far stay in the dataset.

# Actor input Schema

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

Company websites to check, one per line: domains like apify.com or links like https://apify.com/contact. Pasted lists from sheets work too (commas, bullets, quotes). Each domain is checked once.

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

Pages read on each website: the home page first, then contact, imprint (Impressum), legal, about and team pages found by their links, on the same website only.

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

On: phone numbers from phone links, structured data and text next to words like Phone or Tel. In international format (+14155550123) when the country is clear.

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

On: links to the company's LinkedIn, Facebook, Instagram, X, YouTube, TikTok, Pinterest, WhatsApp and Telegram profiles.

## `checkEmailDomains` (type: `boolean`):

On: a DNS lookup marks each email whose domain has no mail server (domainCanReceiveMail = false). It does not test single mailboxes.

## `maxTotalItems` (type: `integer`):

Optional cap on websites checked in this run (one row each). Leave empty to check every website in the list.

## Actor input object example

```json
{
  "websites": [
    "apify.com",
    "crawlee.dev",
    "basecamp.com",
    "ghost.org",
    "mailchimp.com",
    "nginx.com",
    "buffer.com",
    "zapier.com",
    "calendly.com",
    "hubspot.com"
  ],
  "maxPagesPerSite": 5,
  "includePhones": true,
  "includeSocials": true,
  "checkEmailDomains": false
}
```

# Actor output Schema

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

No description

## `summary` (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",
        "basecamp.com",
        "ghost.org",
        "mailchimp.com",
        "nginx.com",
        "buffer.com",
        "zapier.com",
        "calendly.com",
        "hubspot.com"
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("apisamurai/website-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 = { "websites": [
        "apify.com",
        "crawlee.dev",
        "basecamp.com",
        "ghost.org",
        "mailchimp.com",
        "nginx.com",
        "buffer.com",
        "zapier.com",
        "calendly.com",
        "hubspot.com",
    ] }

# Run the Actor and wait for it to finish
run = client.actor("apisamurai/website-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 '{
  "websites": [
    "apify.com",
    "crawlee.dev",
    "basecamp.com",
    "ghost.org",
    "mailchimp.com",
    "nginx.com",
    "buffer.com",
    "zapier.com",
    "calendly.com",
    "hubspot.com"
  ]
}' |
apify call apisamurai/website-contact-extractor --silent --output-dataset

```

## MCP server setup

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