# Contact Details Scraper: Emails, Phones & Socials (`openkrill/contact-details-scraper`) Actor

Crawl company websites and get emails, phone numbers and 13 social profile links (LinkedIn, X, Instagram, Facebook, YouTube, TikTok and more) per site, deduplicated, with a mail-ready flag per email domain. Obeys robots.txt. $1.50 per 1,000 pages, failed pages free, MCP-ready.

- **URL**: https://apify.com/openkrill/contact-details-scraper.md
- **Developed by:** [Nguyen Vu Trung Hieu](https://apify.com/openkrill) (community)
- **Categories:** Lead generation, Social media, AI
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

$1.50 / 1,000 page scrapeds

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

## Contact Details Scraper: Emails, Phones and Social Links from Any Website

Give it company websites and it returns the emails, phone numbers and social profile links each site publishes, one clean row per site.
You pay per use: $1.50 per 1,000 pages read. Pages that fail to load are free, and so is the mail-ready check.

It reads the start page, then the contact, about and other same-site pages it links to, up to the depth and page limits you set.
Each email domain gets a mail-ready flag from public DNS (MX, SPF and DMARC), so you know which addresses can receive mail before you write.

Use it to build a lead list from company domains, to enrich a CRM with contact pages and socials, or to find the right inbox for a partnership email.

### Use it from an AI agent (MCP)

The same finder is available as a hosted MCP server at `https://contacts.openkrill.app/mcp` (tool name `find_contact_details`, overview at [tools.openkrill.app](https://tools.openkrill.app)).
It reads up to 20 pages of one site per call, with no sign-up and no key.
On Apify you can also call this Actor from any MCP client through the [Apify MCP server](https://docs.apify.com/integrations/mcp).

### What you get

For every start URL (or every page, with `mergeContacts` off):

- `emails`: from `mailto:` links and page text, lowercased and sorted. Emails written as `name [at] site [dot] com` and Cloudflare-protected emails are decoded.
- `phones`: from `tel:` links. Numbers seen only in page text go to `phonesUncertain`. Dates, order numbers and years are left out.
- Social profile links for 13 networks, each in one canonical `https` form: LinkedIn, X (Twitter), Instagram, Facebook, YouTube, TikTok, Pinterest, Discord, Snapchat, Threads, Telegram, Reddit and WhatsApp. Share buttons, single posts and videos are left out.
- `scrapedUrls`: the pages read, in the order they were read.
- `emailDomains` (optional): for each email domain, `mailReady`, `mx`, `spf`, `dmarc` and a short note.
- Error rows: a start URL that can't be read gives one row with `error` (for example `http 503` or `robots.txt disallows`), so one broken site never fails the run.

Linktree pages are followed to the sites they list, and robots.txt is obeyed for the user agent `OpenKrillContactBot`.

### Input

| Field | Description |
| --- | --- |
| `startUrls` | Websites to crawl, such as company home pages. A bare domain means `https`. |
| `maxRequestsPerStartUrl` | Pages to read at most per start URL, the start page included. Default 20. |
| `maxDepth` | How many links away from the start page to go. `0` reads only the start page. Default 2. |
| `maxRequests` | Cap on pages read over the whole run. Use it to cap cost. Default 1000. |
| `sameDomain` | Follow only links on the same site (`www.` does not matter). Default on. |
| `mergeContacts` | One merged row per start URL (default), or one row per page with its depth and referrer. |
| `checkMailDomains` | Add the mail-ready flag per email domain. Default on. |

```json
{
  "startUrls": [{ "url": "https://www.python.org/" }, { "url": "https://www.mozilla.org/" }],
  "maxRequestsPerStartUrl": 10,
  "maxDepth": 1
}
```

### Example output

```json
{ "originalStartUrl": "https://shop.example/", "domain": "shop.example",
  "scrapedUrls": ["https://shop.example/", "https://shop.example/contact"],
  "emails": ["sales@shop.example", "support@shop.example"], "phones": ["+14155550134"], "phonesUncertain": ["02079460958"],
  "linkedIns": ["https://linkedin.com/company/shop-example"], "twitters": ["https://x.com/shopexample"],
  "instagrams": [], "facebooks": [], "youtubes": [], "tiktoks": [], "pinterests": [], "discords": [],
  "snapchats": [], "threads": [], "telegrams": [], "reddits": [], "whatsapps": ["https://wa.me/14155550134"],
  "emailDomains": [{ "domain": "shop.example", "mailReady": true, "mx": true, "spf": true, "dmarc": true,
    "note": "Accepts mail; publishes SPF and DMARC." }] }
```

### Pricing

You pay per event: $1.50 per 1,000 pages ($0.0015 per `page-scraped` event, one event per page read).
Pages that fail to load, robots.txt files and the mail-ready check are free.
Your spending limit caps the crawl itself, so a run never reads more pages than the limit can pay for.

### FAQ

**Does it run JavaScript?**
No. It reads the HTML a site serves. Contacts drawn only by JavaScript are not seen, which keeps runs fast and cheap.

**Does it find a person's private email?**
No. It reads only what a website publishes on its own pages. It doesn't guess addresses or look people up by name.

**Why is a phone number in phonesUncertain?**
It was seen in page text, not in a `tel:` link. Check it before you call.

**What does mailReady false mean?**
The domain has no MX record (or a null MX), so mail to it is likely to bounce.

### Limits

- Static HTML only, no JavaScript and no logins.
- Same-site links only by default. Other sites are not followed, except from Linktree pages.
- robots.txt is obeyed. A site that disallows the bot gives an error row and costs nothing.
- A page is read up to 2 MB and 20 seconds.

### Credits

Written from scratch from the public description of what a contact details scraper does.
No code from any other scraper was read or used.

# Actor input Schema

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

Websites to crawl, such as a company home page. One merged row per start URL. Linktree pages work too: every site they link to is crawled.

## `maxRequestsPerStartUrl` (type: `integer`):

How many pages to queue for one start URL, the start page included. Contact, about and footer pages are usually within 20.

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

How many links away from the start URL to go. 0 reads only the start page.

## `maxRequests` (type: `integer`):

Cap on page fetches over all start URLs. Use it to cap cost: pages are $0.0015 each.

## `sameDomain` (type: `boolean`):

Follow only links to the same site as the page they are on (www. does not matter). Links on Linktree pages are always followed.

## `mergeContacts` (type: `boolean`):

On: one merged, deduplicated row per start URL. Off: one row per page, with depth and the page that linked to it.

## `checkMailDomains` (type: `boolean`):

Look up MX, SPF and DMARC for each email domain found, so you know which addresses can receive mail before you write. Free.

## Actor input object example

```json
{
  "startUrls": [
    {
      "url": "https://www.python.org/"
    }
  ],
  "maxRequestsPerStartUrl": 20,
  "maxDepth": 2,
  "maxRequests": 1000,
  "sameDomain": true,
  "mergeContacts": true,
  "checkMailDomains": true
}
```

# Actor output Schema

## `contacts` (type: `string`):

No description

## `dataset` (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": [
        {
            "url": "https://www.python.org/"
        }
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("openkrill/contact-details-scraper").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": [{ "url": "https://www.python.org/" }] }

# Run the Actor and wait for it to finish
run = client.actor("openkrill/contact-details-scraper").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": [
    {
      "url": "https://www.python.org/"
    }
  ]
}' |
apify call openkrill/contact-details-scraper --silent --output-dataset

```

## MCP server setup

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

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/DX5d9ONDpNhBg7dv9/builds/rVuaomym1gLe9iUH2/openapi.json
