# Website Contact Extractor - Emails, Phones & Socials (`automatic/website-contact-extractor`) Actor

Turn a list of up to 1,000 organization websites into contact records: published emails and phone numbers with their source page, company social links and postal address. Reads up to 20 pages per site and respects robots.txt. $1 per 1,000 pages read.

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

## Pricing

from $1.00 / 1,000 website pages

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

Turn a list of organization websites into source-linked contact records. Collect every email address and phone number the website publishes, including staff and team contacts, company social links, and postal addresses in structured website data. Useful for supplier research, business directories and checking your own organization's contact information.

### What you get

One row per input domain, with `domain`, `inputUrl`, `emails`, `phones`, `socials`, `address`, `pagesVisited`, `status`, and `error`. Emails include their published value, first source URL and all `sourceUrls`. Phones and emails are deduplicated across pages. Phones preserve their published value and include `e164` when the country is clear. Social fields are `facebook`, `instagram`, `x`, `linkedinCompany`, `youtube`, and `tiktok`; absent values are null. Addresses include their source URL.

Statuses are `ok` (email or phone found), `no-contacts` (accessible pages checked and absence confirmed), `blocked` (access or robots restrictions), `unreachable` (request failure or incomplete empty result), and `refused` (social or link-in-bio platform host such as linkedin.com or linktr.ee; no requests or page charges). A row can contain contacts and an error when another page could not be checked. `pagesVisited` counts successful HTML fetches, including an empty-result confirmation; it excludes robots, redirects and failed requests.

### Input

```json
{
  "startUrls": ["https://plausible.io/", "https://www.monmouthcoffee.co.uk/"],
  "maxPagesPerDomain": 5,
  "countryCode": "GB"
}
```

`startUrls` accepts 1–1,000 domains or website URLs; repeated domains are merged (a domain listed with and without `www`, or twice, is read once) and every organization becomes exactly one row. `maxPagesPerDomain` defaults to 5 (maximum 20) unique pages per organization. The first page and linked contact, about and imprint pages are prioritized. An additional request confirms an empty result. `countryCode` is optional: supply a two-letter ISO country code to normalize local phone numbers. Validation checks only the two-letter alphabetic shape, not whether the code is assigned; unknown codes may leave `e164` null. Certain unambiguous country domains also provide a region hint; ambiguous numbers retain a null `e164`.

Paste a list of any size, one domain or URL per line: `example.org`, `https://example.org/contact` and `www.example.org` are all accepted and count as one target. An empty input reads the ten sample organizations above, one row each, so a first run always shows you what a row looks like.

#### Reading many domains cheaply

Page reads are what you pay for, so set `maxPagesPerDomain` to what your list needs:

| `maxPagesPerDomain` | Reads per organization | Cost per organization | Best for |
| --- | --- | --- | --- |
| `1` | the homepage | $0.001 | large lists where the homepage email is enough |
| `2`–`3` | homepage plus its contact page | $0.002–0.003 | most lists: the contact page usually carries email and phone |
| `5` (default) | homepage, contact, about and imprint pages | up to $0.005 | small lists where you want everything published |

At `1` page each, 1,000 domains cost about $1.00 plus the run start; at 3 pages each, about $3.00.

### Output example

```json
{
  "domain": "plausible.io",
  "inputUrl": "https://plausible.io/",
  "emails": [
    {
      "value": "hello@plausible.io",
      "sourceUrl": "https://plausible.io/contact",
      "sourceUrls": [
        "https://plausible.io/contact"
      ]
    }
  ],
  "phones": [],
  "socials": {
    "facebook": null,
    "instagram": null,
    "x": "https://x.com/PlausibleHQ",
    "linkedinCompany": "https://www.linkedin.com/company/plausible-analytics",
    "youtube": null,
    "tiktok": null
  },
  "address": {
    "value": {
      "@type": "PostalAddress",
      "streetAddress": "Västriku tn 2",
      "postalCode": "50403",
      "addressLocality": "Tartu",
      "addressCountry": "EE"
    },
    "sourceUrl": "https://plausible.io/"
  },
  "pagesVisited": 5,
  "status": "ok",
  "error": null
}
```

### Scope and limits

Only information published on the supplied organization website is collected. There are no guessed email patterns, mail-server checks, third-party lookups or logins. Supported email obfuscations are `[at]`/`(at)` and `[dot]`/`(dot)` (case-insensitive), URL percent encoding, and encoded email protection. JavaScript mailto links are not executed. Company social URLs are recorded as linked by the website; those platforms are never visited. Review linked accounts before use: website authors can mislabel a personal account as a company account. Article-author social links are not treated as company accounts. Emails and phones are returned whether they belong to a general mailbox, a named person, a team page or a structured Person record. Structured addresses retain only postal fields (`streetAddress`, `addressLocality`, `addressRegion`, `postalCode`, `addressCountry`, `postOfficeBoxNumber`, and an optional `PostalAddress` type); emails embedded in an address are returned in `emails`.

Social and link-in-bio platforms (LinkedIn, Facebook, Instagram, X, YouTube, TikTok, Threads, Pinterest, about.me, Linktree), including their subdomains, are refused before any requests or page charges. Sites on shared hosting such as wixsite.com or github.io are crawled like any other website.

Robots restrictions are respected. Downloads are capped at 2 MB per response and 6 MB per domain (including robots and retries). Pages requiring JavaScript may return no contacts. Contact forms are not email addresses. Results reflect a bounded crawl, not every page on a website. Use collected data in accordance with applicable privacy and communication rules.

Rows are saved as each organization finishes. The run stops early to preserve time to save results; the run status names failed or incomplete domains. A successful run can include refused, blocked or unreachable rows; if every target is refused or failed, the run fails unless it stopped at its maximum charge. A run that reaches its maximum charge returns normally even when no target succeeded. The ten-organization example input is bounded to a five-minute run.

Download results from the default dataset in JSON, CSV, Excel or other supported formats.

### Billing

$0.005 per run start + $0.001 per website page fetched (up to maxPagesPerDomain per domain)

An empty-result confirmation may fetch the last page once more without a page charge. The run stops fetching when its maximum charge is reached and saves collected rows. Robots, redirects, failed requests and oversized pages are not charged. Dataset rows carry no charge. Charged page events equal `pagesVisited` minus free empty-result confirmations only when the maximum charge is not reached. At the charge limit, a fetched and saved page may not generate a charged page event, so that equality does not hold.

The platform run-start event is accounted per GB of memory, with a minimum of one: a default 512 MB run is one start event.

# Actor input Schema

## `startUrls` (type: `array`):

Domains or URLs for organizations whose own published contacts you need, one per line; repeated domains (with or without www) are merged and each organization is one row. No third-party enrichment. Empty input reads the ten sample organizations.

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

Prioritize linked contact/about/imprint pages. Redirects, robots, retries and one empty-result confirmation are additional requests.

## `countryCode` (type: `string`):

Optional ISO country code for local phone numbers. International numbers normalize without it.

## Actor input object example

```json
{
  "startUrls": [
    "https://www.benscookies.com/",
    "https://www.monmouthcoffee.co.uk/",
    "https://plausible.io/",
    "https://basecamp.com/",
    "https://www.hetzner.com/",
    "https://www.netcup.com/",
    "https://nubank.com.br/",
    "https://www.cloudflare.com/",
    "https://www.notion.com/",
    "https://www.figma.com/"
  ],
  "maxPagesPerDomain": 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 = {
    "startUrls": [
        "https://www.benscookies.com/",
        "https://www.monmouthcoffee.co.uk/",
        "https://plausible.io/",
        "https://basecamp.com/",
        "https://www.hetzner.com/",
        "https://www.netcup.com/",
        "https://nubank.com.br/",
        "https://www.cloudflare.com/",
        "https://www.notion.com/",
        "https://www.figma.com/"
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("automatic/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 = { "startUrls": [
        "https://www.benscookies.com/",
        "https://www.monmouthcoffee.co.uk/",
        "https://plausible.io/",
        "https://basecamp.com/",
        "https://www.hetzner.com/",
        "https://www.netcup.com/",
        "https://nubank.com.br/",
        "https://www.cloudflare.com/",
        "https://www.notion.com/",
        "https://www.figma.com/",
    ] }

# Run the Actor and wait for it to finish
run = client.actor("automatic/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 '{
  "startUrls": [
    "https://www.benscookies.com/",
    "https://www.monmouthcoffee.co.uk/",
    "https://plausible.io/",
    "https://basecamp.com/",
    "https://www.hetzner.com/",
    "https://www.netcup.com/",
    "https://nubank.com.br/",
    "https://www.cloudflare.com/",
    "https://www.notion.com/",
    "https://www.figma.com/"
  ]
}' |
apify call automatic/website-contact-extractor --silent --output-dataset

```

## MCP server setup

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