# Contact Details Extractor - Emails, Phones & Social Links (`eliai/contact-details-extractor`) Actor

Pass website URLs or domains; get emails, phone numbers and social profile links as structured JSON, one record per URL. Each URL also scans /contact, /contact-us and /about on the same site, all for one $0.03 charge. Failed fetches are free. Built for lead gen, CRM enrichment and AI agents.

- **URL**: https://apify.com/eliai/contact-details-extractor.md
- **Developed by:** [Broke to Built](https://apify.com/eliai) (community)
- **Categories:** Lead generation, Automation
- **Stats:** 2 total users, 0 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $24.00 / 1,000 contact extractions

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?

Actors are web data automations that power AI and operations. They run on the Apify platform to scrape websites, process data, connect APIs, and automate workflows.
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.
Actors are written with capital "A".

## 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.
The best way to integrate Actors is as follows.

- **AI agents and MCP clients** — the [Apify MCP server](https://docs.apify.com/integrations/mcp.md) at `https://mcp.apify.com` (remote, streamable HTTP, OAuth on first use).
- **Agentic workflows and local Actor development** — [Agent Skills](https://apify.com/.well-known/agent-skills/index.json) with the [Apify CLI](https://docs.apify.com/cli/docs.md): `npm install -g apify-cli`, then `apify login`.
- **JavaScript/TypeScript projects** — the official [JS/TS client](https://docs.apify.com/api/client/js/docs.md): `npm install apify-client`.
- **Python projects** — the official [Python client](https://docs.apify.com/api/client/python/docs.md): `pip install apify-client`.
- **Any other language** — the [REST API](https://docs.apify.com/api/v2.md).

For usage examples, see the [API](#api) section below.

For more details, see Apify documentation as [Markdown index](https://docs.apify.com/llms.txt) and [Markdown full-text](https://docs.apify.com/llms-full.txt).

# README

## Contact Details Extractor - emails, phone numbers and social profiles from any website

Give it a website URL. Get back every public email address, phone number and social profile link on
that site's contact surface, as one clean JSON record per URL. No API key, no proxy setup, no crawler
to configure.

**One URL in = up to 3 pages scanned.** For each URL you pass, this actor also tries `/contact`,
`/contact-us` and `/about` on the same origin and merges everything it finds into a single record.
That is where business contact details actually live, so you do not have to guess which page to point
it at, and you are not billed per page it touches.

### What you get (exact output fields)

| Field | Type | What it is |
|---|---|---|
| `url` | string | The URL you passed in |
| `finalUrl` | string | Where it landed after redirects |
| `statusCode` | number | HTTP status of the main page |
| `pagesScanned` | string\[] | Every page actually fetched for this record (1 to 3) |
| `pageTitle` | string | null | `<title>` of the main page |
| `emails` | string\[] | Lowercased, de-duplicated, up to 50. Includes `mailto:` links |
| `phones` | string\[] | De-duplicated, up to 25. `tel:` links plus international-format numbers |
| `socialProfiles` | object | One profile URL per network, keyed by network name |
| `found` | object | `{ emails, phones, social }` counts, handy for filtering |
| `error` | string | Present instead of the above when the site could not be fetched |

Networks detected in `socialProfiles`: `twitter` (x.com and twitter.com), `linkedin`
(`/company/` and `/in/`), `facebook`, `instagram`, `youtube` (`@handle`, `/channel/`, `/c/`),
`github`, `tiktok`. Share/intent/login links are filtered out, so you get real profiles, not
"share this page on Facebook" buttons.

### Example 1 - a single company site

Input:

```json
{ "url": "https://www.apify.com/" }
```

Output (real run, 2026-08-15):

```json
{
  "url": "https://www.apify.com/",
  "finalUrl": "https://apify.com/",
  "statusCode": 200,
  "pagesScanned": [
    "https://apify.com/",
    "https://www.apify.com/contact",
    "https://www.apify.com/about"
  ],
  "pageTitle": "Apify: The largest marketplace of trusted tools for AI",
  "emails": ["support@apify.com", "hello@apify.com"],
  "phones": [],
  "socialProfiles": {
    "twitter": "https://x.com/apify",
    "linkedin": "https://linkedin.com/company/apify",
    "github": "https://github.com/apify",
    "tiktok": "https://tiktok.com/@apifytech"
  },
  "found": { "emails": 2, "phones": 0, "social": 4 }
}
```

### Example 2 - bulk enrichment of a lead list

Input:

```json
{
  "urls": ["stripe.com", "https://vercel.com/", "linear.app"],
  "maxUrls": 3
}
```

You get three records, one per input URL, in the same order. Bare domains are accepted - `stripe.com`
becomes `https://stripe.com` automatically. This is the shape you want when enriching a CRM export:
one row in, one row out, join on `url`.

### Example 3 - a site that cannot be fetched

Input:

```json
{ "url": "https://does-not-exist.invalid/" }
```

Output (real run, 2026-08-15):

```json
{
  "url": "https://does-not-exist.invalid/",
  "error": "fetch failed: fetch failed"
}
```

Failed fetches are recorded as a diagnostic record and are **not charged** - that run reported
`chargedEventCounts: { "contact-extracted": 0 }`. Filter on the presence of `error` to separate
diagnostics from results.

### Pricing

**$0.03 per URL processed** - billed as the `contact-extracted` event, once per URL in your input,
whether that URL yields 40 emails or none. The extra `/contact`, `/contact-us` and `/about` pages
scanned for that URL are included in the same single charge.

- A URL that fails to fetch is **free** (it comes back as an `error` record).
- `maxUrls` is your hard budget cap: the input list is truncated to it before anything runs, so
  `maxUrls: 10` can never cost more than $0.30.
- There is no per-run start fee.

Cheaper per-URL contact scrapers exist on the Store (measured 2026-08-15: `$0.003` to `$0.006` per
page or dataset item is common). They typically bill per **page** or per **dataset item**; this actor
bills per **input URL** and folds the contact/about pages into that one charge, and it returns a
fixed, documented record shape rather than a variable number of rows. If you are extracting from a
single known page and want the lowest possible unit price, one of those is the better buy - see
"When not to use this" below.

### FAQ

**How do I extract email addresses from a website?**
Pass the site URL as `url` (or a list as `urls`). Every address found on the home page plus the
`/contact`, `/contact-us` and `/about` pages comes back in the `emails` array, lowercased and
de-duplicated, including addresses that only appear inside `mailto:` links.

**Can it get phone numbers too?**
Yes. `phones` collects `tel:` links and international-format numbers found in the page text, keeping
only 8-to-15-digit candidates and rejecting dates and version strings. Up to 25 per record.

**Which social networks does it find?**
Twitter/X, LinkedIn (company and personal), Facebook, Instagram, YouTube, GitHub and TikTok - one
profile URL per network in `socialProfiles`.

**Does it crawl the whole website?**
No, and that is deliberate - a crawler is where costs explode. It fetches the URL you gave it plus at
most two well-known contact paths on the same origin (`/contact`, `/contact-us`, `/about`), which
`pagesScanned` reports exactly. If a business hides its email on `/impressum` or `/team`, pass that
URL directly.

**How many URLs can I process in one run?**
As many as you set `maxUrls` to; it defaults to 25 and truncates the list. Each URL is fetched
sequentially, so plan roughly a few seconds per URL for large batches.

**Can an AI agent call this as a tool?**
Yes. It is exposed through the Apify MCP server (`mcp.apify.com`), and the output record shape is
fixed and documented above, so a model can rely on the field names.

**Is scraping contact details legal?**
It reads only public contact information already published on the pages you point it at - the same
data any visitor sees. What you then do with that data is governed by your jurisdiction (GDPR,
CAN-SPAM, PECR and friends). Check before cold outreach; that part is on you, not on the tool.

### When not to use this

- **You need to find pages, not read them.** This actor never discovers URLs on its own. If you have
  a company name and no website, use a search or Google Maps scraper first, then feed the URLs here.
- **You need a full-site crawl.** Contact data buried on `/team/jane-doe` will not be found unless you
  pass that URL. Use a crawler, or generate the URL list yourself.
- **The contact details are rendered by JavaScript.** There is no headless browser here - it reads the
  HTML the server returns. Single-page apps that inject the footer client-side will look empty.
- **You need verified, deliverable emails.** This extracts what is published; it does not do SMTP or
  MX verification. Pair it with an email verifier if bounce rate matters.
- **You are extracting one known page and unit price is everything.** At $0.03 per URL this is priced
  for the "one URL covers the whole contact surface" case, not for cheapest-per-page bulk scraping.

### Honest limits

- Fetch timeout is 15 seconds per page; slow sites come back as `error` records (free).
- Sites that block non-browser user agents at the CDN return their block page, which usually means
  empty arrays rather than an error.
- Emails are matched by pattern. Obfuscated addresses (`name [at] example.com`, images, JS assembly)
  are not decoded.
- `socialProfiles` records the **first** matching link per network, not every profile on the page.
- Input URLs are not de-duplicated, so passing the same URL twice costs twice.

### Run it from code

```bash
curl -X POST "https://api.apify.com/v2/acts/EliAI~contact-details-extractor/run-sync-get-dataset-items?token=YOUR_APIFY_TOKEN" \
  -H 'content-type: application/json' \
  -d '{"url":"https://www.apify.com/"}'
```

Callable as an agent tool through the Apify MCP server (`mcp.apify.com`).

### For AI agents

This Actor is built to be called by software, not just by people.

- **Mount it directly as an MCP tool** — no Store search, no ranking, just this one tool:
  `https://mcp.apify.com/?actors=eliai/contact-details-extractor`
- **Or call it over HTTP** and get the results in the same request:
  `POST https://api.apify.com/v2/acts/eliai~contact-details-extractor/run-sync-get-dataset-items`
- **Pay with x402, without an Apify account.** This Actor is whitelisted for agentic payments, so an agent holding USDC on Base can buy a prepaid token and spend it here. The minimum purchase is $1, the token balance is an absolute spending cap, and it expires 14 days after purchase.
- **Costs are predictable before you call.** Pricing is pay-per-event (see Pricing above), so an agent can budget a run in advance instead of discovering the bill afterwards.
- **Send only the field you mean.** If you pass the bulk field, it is used on its own; the single-value field is a fallback, never merged into your request. You are charged for the items you sent and nothing else.

# Actor input Schema

## `url` (type: `string`):

A page or domain to extract contact details from (e.g. example.com/contact)

## `urls` (type: `array`):

Multiple pages/domains to process in one run

## `maxUrls` (type: `integer`):

Safety cap on how many URLs to process

## Actor input object example

```json
{
  "url": "https://apify.com/",
  "urls": [],
  "maxUrls": 25
}
```

# Actor output Schema

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

Every item this run produced, as JSON.

## `resultsCsv` (type: `string`):

The same items as a spreadsheet-ready CSV.

# 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 = {
    "url": "https://apify.com/",
    "urls": []
};

// Run the Actor and wait for it to finish
const run = await client.actor("eliai/contact-details-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 = {
    "url": "https://apify.com/",
    "urls": [],
}

# Run the Actor and wait for it to finish
run = client.actor("eliai/contact-details-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 '{
  "url": "https://apify.com/",
  "urls": []
}' |
apify call eliai/contact-details-extractor --silent --output-dataset

```

## MCP server setup

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