# Website Contact & Tech Extractor: Emails, Phones (`lx112/website-contact-extractor`) Actor

Crawl company websites and get emails, phone numbers (E.164), social profiles, the contact page, company name and tech stack (Shopify, WordPress, HubSpot, Stripe and more). Visits only each site's own pages, respects robots.txt, one result per domain.

- **URL**: https://apify.com/lx112/website-contact-extractor.md
- **Developed by:** [leo xing](https://apify.com/lx112) (community)
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $2.00 / 1,000 domains

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 & Tech Extractor: emails, phones, socials and tech stack

Give it a list of company websites and get back, for each one, **every email address and phone
number published on the site**, its **social profiles**, the **contact page**, the **company name**
and the **technologies** it runs on (Shopify, WordPress, Webflow, HubSpot, Google Analytics,
Stripe, Intercom, Next.js and more: about 60 in all). One clean result per domain.

- **Only the company's own website.** The actor crawls pages on the site's own domain (including
  subdomains like `shop.` or `blog.`), never social networks or third-party sites. Social profile
  links are read from the site's pages, not visited.
- **Contact pages first.** Links like Contact, About, Team, Impressum and Careers are fetched before
  anything else, so a 10-page budget is usually enough.
- **Decodes what simple scrapers miss:** `name [at] company [dot] com`, `jobs(at)company(dot)io`,
  HTML-entity addresses and Cloudflare-protected emails. Image names like `logo@2x.png`, template
  placeholders and error-tracker keys are dropped.
- **Phone numbers in E.164** (`+14165550123`) when the country is clear from the domain, the page
  language or the company's structured data; otherwise the number as written.
- **Polite.** Follows `robots.txt` (including crawl delays), identifies itself honestly as
  `WebsiteContactExtractor`, and sends at most two requests at a time to any site.

### Who uses this

- **Sales and lead gen:** turn a list of target accounts into emails, phone numbers and LinkedIn
  pages, and segment them by tech stack ("Shopify stores using Klaviyo", "sites without HubSpot").
- **Agencies and SaaS vendors:** find companies running a competitor's product or an outdated CMS.
- **CRM enrichment:** fill in missing contact fields and social links for existing accounts.
- **Research and AI agents:** a structured snapshot of what a company publishes about itself.

### Input

| Field | Example | Notes |
|---|---|---|
| Websites or domains | `stripe.com`, `https://www.acme.co.uk` | One result per domain; duplicates merged |
| Max pages per domain | `10` | Includes the start page |
| Crawl depth | `2` | Clicks away from the start page |
| Visit contact-style pages first | `true` | Contact, About, Team, Impressum, Careers |
| Detect technologies | `true` | |
| Sites crawled in parallel | `5` | Each site still gets at most 2 requests at once |

### Output (one dataset item per domain)

```json
{
  "input": "maplewidgets.ca",
  "domain": "maplewidgets.ca",
  "url": "https://maplewidgets.ca/",
  "companyName": "Maple Widgets",
  "emails": ["info@maplewidgets.ca", "jobs@maplewidgets.ca", "press@maplewidgets.ca", "sales@maplewidgets.ca", "support@maplewidgets.ca"],
  "phones": [
    { "raw": "+1-416-555-0199", "e164": "+14165550199" },
    { "raw": "(416) 555-0123", "e164": "+14165550123" },
    { "raw": "1-800-555-0177", "e164": "+18005550177" }
  ],
  "socials": {
    "linkedin": ["https://linkedin.com/company/maple-widgets"],
    "twitter": ["https://x.com/maplewidgets"],
    "facebook": ["https://facebook.com/maplewidgets"],
    "instagram": ["https://instagram.com/maplewidgets"],
    "youtube": ["https://youtube.com/@maplewidgets"],
    "tiktok": ["https://tiktok.com/@maplewidgets"],
    "github": ["https://github.com/maple-widgets"]
  },
  "contactPageUrl": "https://maplewidgets.ca/contact/",
  "technologies": [
    { "name": "Google Analytics", "category": "Analytics" },
    { "name": "WordPress", "category": "CMS" },
    { "name": "HubSpot", "category": "Marketing automation" },
    { "name": "Nginx", "category": "Web server" }
  ],
  "pagesCrawled": 4,
  "crawledUrls": ["https://maplewidgets.ca/", "https://maplewidgets.ca/contact/", "https://maplewidgets.ca/about-us/", "https://maplewidgets.ca/products/"],
  "errors": [{ "url": "https://maplewidgets.ca/history/", "error": "HTTP 404" }],
  "scrapedAt": "2026-10-23T06:00:00.000Z"
}
```

Emails on the company's own domain come first. Sites that can't be reached (or that block the
crawler) still get an item with `pagesCrawled: 0` and the reason in `errors`; those aren't charged.

### Pricing

Pay per event: charged per **domain** with at least one page fetched, whatever the number of pages.
Sites that can't be reached cost nothing beyond Apify's actor start.

### Limits

- Pages that build their content with JavaScript after loading (some single-page apps) may show
  fewer contacts, since the actor reads the HTML the server sends and doesn't run a browser.
- Sites behind bot protection (Cloudflare challenges and the like) may refuse the crawler; the
  item then reports the HTTP status in `errors`.
- Phone numbers are found in `tel:` links, structured data and number-shaped text (international
  `+` numbers and North American formats). Local formats without a country code are reported as
  written when the country can't be told.
- Technology detection uses simple signatures and reports what the fetched pages show; it isn't an
  exhaustive audit.

### Notes

The actor only reads pages that websites publish for anyone to see, and it stores nothing beyond
the run's output. You're responsible for using contact data in line with the laws that apply to
you (e.g. GDPR, CASL, CAN-SPAM).

# Actor input Schema

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

Company websites to crawl, as domains ("stripe.com") or URLs ("https://www.acme.com/about"). Each site gets one result; entries on the same domain are merged.

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

How many pages of each site to fetch, including the start page. Contact details are usually on the home, contact, about and imprint pages, so 10 is plenty for most sites.

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

How many clicks away from the start page to follow links. 0 fetches only the start page; 1 adds pages it links to; 2 adds pages those link to.

## `prioritizeContactPages` (type: `boolean`):

Fetch links like /contact, /about, /team, /impressum and /careers before other pages, so the page budget goes where contact details are.

## `includeTechnologies` (type: `boolean`):

Report the site's tech stack (CMS, e-commerce platform, analytics, marketing, chat, payments, frameworks, hosting) from signatures in the HTML, response headers and script URLs.

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

How many websites to crawl at the same time. Each site still gets at most two requests at once, with a pause between rounds (longer if its robots.txt asks for a crawl delay).

## Actor input object example

```json
{
  "websites": [
    "apify.com",
    "basecamp.com",
    "allbirds.com"
  ],
  "maxPagesPerDomain": 10,
  "maxDepth": 2,
  "prioritizeContactPages": true,
  "includeTechnologies": true,
  "maxConcurrency": 5
}
```

# Actor output Schema

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

One item per website: emails, phone numbers, social profiles, contact page, company name, technologies, pages crawled and errors.

# 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",
        "basecamp.com",
        "allbirds.com"
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("lx112/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",
        "basecamp.com",
        "allbirds.com",
    ] }

# Run the Actor and wait for it to finish
run = client.actor("lx112/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",
    "basecamp.com",
    "allbirds.com"
  ]
}' |
apify call lx112/website-contact-extractor --silent --output-dataset

```

## MCP server setup

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