# Website Contact & Tech Stack Scraper - Company Enrichment (`thedeadpoet/website-contact-tech-scraper`) Actor

Give it a list of company domains. Get back emails, phone numbers, social profiles, the tech stack, and the company's ATS careers board - one clean row per domain. Obeys robots.txt.

- **URL**: https://apify.com/thedeadpoet/website-contact-tech-scraper.md
- **Developed by:** [Pablo D](https://apify.com/thedeadpoet) (community)
- **Categories:** Lead generation, Business
- **Stats:** 2 total users, 1 monthly users, 0.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

$5.00 / 1,000 domain enricheds

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?

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

## Website Contact & Tech Stack Scraper — company enrichment from a domain

Paste a list of company domains. Get back **one clean row per company**: email addresses, phone numbers, social profiles, the technologies the site runs on, and — uniquely — **which ATS the company recruits with and its board token**.

It reads only public pages and it **obeys `robots.txt` by default**.

### What it does

| | |
|---|---|
| **Input** | A list of domains or URLs (`apify.com`, `https://example.com/about`) |
| **Output** | One dataset row per domain — contacts, socials, tech stack, ATS board, page meta |
| **Pages** | Homepage plus up to N ranked contact/about/careers/imprint pages |
| **Speed** | ~1–3 s per domain at 4 pages; 5 domains in parallel by default |

### Use cases

- **Lead enrichment** — turn a list of domains from a CRM export into contactable rows.
- **Sales qualification by tech stack** — find every prospect running Shopify, HubSpot or Next.js.
- **Recruiting-tech mapping** — the `atsProvider` / `atsToken` fields tell you which companies use Greenhouse, Lever, Ashby, Workable, Personio, Workday and eight others. Feed `atsToken` straight into an ATS job-board scraper to pull their live openings.
- **Data hygiene** — check that the contact details on a list of company sites are still live.

### Input

```json
{
  "domains": ["apify.com", "https://ashbyhq.com", "stripe.com"],
  "maxPagesPerDomain": 4,
  "extract": ["emails", "phones", "socials", "techStack", "atsBoard", "meta"],
  "respectRobotsTxt": true,
  "requestDelaySeconds": 1,
  "concurrency": 5,
  "excludeGenericEmails": true
}
```

`maxPagesPerDomain: 1` is homepage-only — the fastest and cheapest setting, and often enough for tech stack and socials. Contact details usually need 3–5.

### Output

```json
{
  "domain": "ashbyhq.com",
  "finalUrl": "https://www.ashbyhq.com/",
  "companyName": "Ashby",
  "title": "Ashby | All-in-one recruiting software",
  "description": "Ashby helps ambitious teams...",
  "language": "en",
  "generator": null,
  "emails": ["support@ashbyhq.com"],
  "otherEmails": [],
  "phones": [],
  "linkedin": "https://www.linkedin.com/company/ashbyhq",
  "twitter": "https://twitter.com/ashbyhq",
  "facebook": null,
  "instagram": null,
  "youtube": null,
  "github": null,
  "tiktok": null,
  "techStack": ["Google Fonts", "Next.js", "React", "Sentry"],
  "atsProvider": "ashby",
  "atsToken": "ashby",
  "atsBoardUrl": "https://jobs.ashbyhq.com/ashby",
  "pagesCrawled": ["https://www.ashbyhq.com/", "https://www.ashbyhq.com/about"],
  "pagesSkippedByRobots": [],
  "status": "ok",
  "error": null,
  "scrapedAt": "2026-09-10T10:44:12.001000+00:00"
}
```

`emails` holds addresses **on the company's own domain**; anything else found on the page (an agency address, a documentation example) goes to `otherEmails`, so your outreach list is not polluted by someone else's inbox.

`status` is one of:

| Status | Meaning |
|---|---|
| `ok` | Pages loaded and something was extracted |
| `no_data` | Pages loaded but nothing matched (common on JS-only or markdown-served sites) |
| `blocked_by_robots` | Every candidate URL is disallowed by the site's `robots.txt` — nothing was fetched |
| `failed` | Site unreachable; the reason is in `error` |

By default every input domain produces exactly one row, so the output lines up 1:1 with your input list — and dead domains are billed like any other result. Turn **Include unreachable domains in the output** off and only domains that actually loaded are pushed, so you do not pay for dead ones. `RUN_SUMMARY` always reports the full status counts either way.

`RUN_SUMMARY` in the key-value store records status counts, unparseable entries, and whether robots.txt was respected.

### What is detected

- **Tech stack** — 40+ signatures: WordPress, Shopify, Wix, Squarespace, Webflow, Next.js, Nuxt, React, Vue, Angular, Svelte, HubSpot, Segment, Intercom, Drift, Zendesk, Stripe, PayPal, Cloudflare, GA4, GTM, Meta Pixel, LinkedIn Insight, Hotjar, Plausible, Matomo, Sentry, Algolia, Contentful, Sanity, Mailchimp, Klaviyo, Calendly, Typeform, Bootstrap, Tailwind, jQuery and more.
- **ATS boards** — Greenhouse, Lever, Ashby, Workable, Personio, Recruitee, Teamtailor, SmartRecruiters, BambooHR, Workday, Breezy, Join, Jobvite.
- **Socials** — LinkedIn, X/Twitter, Facebook, Instagram, YouTube, GitHub, TikTok.

### Honest limitations

- **No JavaScript rendering.** Pages are fetched as HTML. A site that renders its contact details entirely client-side will return fewer emails — the tech-stack and ATS detection still works, because those come from the script tags. If you need rendered DOM, use a browser-based crawler instead.
- **Tech-stack detection is signature-based**, so it finds what is visible in the HTML. It will miss server-side technology, and a stale script tag can produce a false positive.
- **Contact details on the public web are usually role addresses** (`info@`, `sales@`). This Actor does not guess or pattern-generate personal addresses, and it does not verify that an address is deliverable.
- **`robots.txt` is respected by default.** On sites that disallow the paths where contacts live, this Actor will return less than a scraper that ignores robots.txt. Skipped URLs are listed in `pagesSkippedByRobots` so you can see exactly what happened. You can turn the check off, but then it is on you.
- **Personal data.** Email addresses and phone numbers can be personal data under the GDPR and similar laws. You are the controller of anything you collect here; have a lawful basis, and honour deletion requests.
- **Cloudflare-protected sites return 403 to datacenter IPs.** In a 25-domain test, 3 large consumer sites (doordash.com, kickstarter.com, coinbase.com) returned HTTP 403. Enable Apify Proxy for those, or accept the gap.
- **Coverage measured on that same 25-domain test:** 22/25 loaded, 22 gave a tech stack, 20 a LinkedIn page, 13 an email address, 9 an ATS board, 2 a phone number. Most B2B sites route contact through a form rather than publishing an address — this Actor reports what is there, it does not invent it.

### Politeness

Requests to any single site are serialised with your delay, and the delay is raised automatically if the site's `robots.txt` sets a longer `Crawl-delay`. Parallelism happens across different domains, never within one. `robots.txt` is fetched once per host per run.

### Changelog

See `CHANGELOG.md`.

### Support

Found a bug, or need a field this Actor does not return yet? Open an issue on the Actor's **Issues** tab, or email **pablodevigoalertas@gmail.com**. Issues are read first.

# Actor input Schema

## `domains` (type: `array`):

One company per line. Bare domains (example.com), URLs (https://example.com/about) and emails' domains all work.

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

The Actor fetches the homepage first, then follows likely contact/about/careers/imprint links up to this limit. 1 = homepage only (fastest and cheapest).

## `extract` (type: `array`):

Turn off what you do not need - it does not change the number of requests, but it keeps the dataset small.

## `respectRobotsTxt` (type: `boolean`):

Strongly recommended. When on, any URL disallowed by the site's robots.txt is skipped and the domain's row records it. Turning this off is your responsibility.

## `requestDelaySeconds` (type: `number`):

Raised automatically if the site's robots.txt sets a longer Crawl-delay.

## `concurrency` (type: `integer`):

Different domains are fetched in parallel; requests to any single domain are always serialised with the delay above.

## `requestTimeoutSeconds` (type: `integer`):

How long to wait for a single page before giving up on it.

## `excludeGenericEmails` (type: `boolean`):

Filters out noreply@, and strings that are really image or font filenames rather than addresses.

## `proxyConfiguration` (type: `object`):

Optional. Most sites do not need a proxy for a handful of page loads; use one if you are running a large list.

## `pushFailedDomains` (type: `boolean`):

On: every input domain gets a row, including dead sites (status "failed" or "blocked\_by\_robots") - the output lines up 1:1 with your input, and those rows are billed like any other result. Off: only domains that actually loaded are pushed, so you do not pay for dead ones. Either way the counts are in RUN\_SUMMARY.

## Actor input object example

```json
{
  "domains": [
    "apify.com",
    "https://ashbyhq.com"
  ],
  "maxPagesPerDomain": 4,
  "extract": [
    "emails",
    "phones",
    "socials",
    "techStack",
    "atsBoard",
    "meta"
  ],
  "respectRobotsTxt": true,
  "requestDelaySeconds": 1,
  "concurrency": 5,
  "requestTimeoutSeconds": 20,
  "excludeGenericEmails": true,
  "proxyConfiguration": {
    "useApifyProxy": false
  },
  "pushFailedDomains": true
}
```

# Actor output Schema

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

One row per domain with contacts, socials, detected tech stack and the company ATS job board.

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

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

# Run the Actor and wait for it to finish
run = client.actor("thedeadpoet/website-contact-tech-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 '{
  "domains": [
    "apify.com",
    "https://ashbyhq.com"
  ]
}' |
apify call thedeadpoet/website-contact-tech-scraper --silent --output-dataset

```

## MCP server setup

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