# Company Enrichment API – Domain to Emails, Socials & Tech (`locomint/company-enrichment`) Actor

Company enrichment by domain: paste domains, website URLs or email addresses and get one record per company with what its site says it is, role email addresses, social profiles, WhatsApp, contact page and tech stack. For lead generation and CRM enrichment.

- **URL**: https://apify.com/locomint/company-enrichment.md
- **Developed by:** [Locomint](https://apify.com/locomint) (community)
- **Categories:** Lead generation, Business, Developer tools
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $0.85 / 1,000 result delivereds

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

## Company Enrichment API – Domain to Emails, Socials & Tech

This company enrichment actor turns a list of domains, website URLs or email addresses into one
row per company: the role email addresses, social profiles, WhatsApp number, contact page and
tech stack that the company's own website publishes, with the site's title, description and
language. It is domain enrichment from the source, read live during your run.

### What it does

Each entry is first reduced to one host. `example.com`, `www.example.com`,
`https://example.com/pricing` and `sales@example.com` all become `example.com`, duplicates are
removed in the order given, and internationalised names are converted to ASCII (`münchen.de`
becomes `xn--mnchen-3ya.de`). A column of contact emails exported from a CRM therefore
collapses into a list of companies.

Two kinds of entry are dropped before anything is fetched, named in the run log and not
charged: text that is not a domain, URL or email address, and the 92 free email providers
such as gmail.com, yahoo.com and outlook.com, which belong to no company.

For every remaining domain the actor:

- reads the site's robots.txt and checks the homepage against the rules for all crawlers (the
  `*` group). A disallowed homepage is not fetched;
- fetches `https://<domain>/` over a direct datacenter connection, with no proxy and no
  browser. If the bare domain does not answer, `https://www.<domain>/` is tried once, unless
  the site answered with a 403, 429 or challenge page;
- reads the site's English edition instead when the homepage declares one with `hreflang`, so
  the title and description come back in English where the site offers it;
- reads the contact page as well when the homepage shows no email address or no WhatsApp
  number and links to one, and merges the two. One extra page, no extra charge.

| Field | What goes in it |
|---|---|
| `domain` | The host the entry was reduced to. |
| `input` | What you typed, so the row can be matched back to your sheet. |
| `website` | The address that was read: `https://<domain>/`, or the `www.` form when only that answered. |
| `status` | `ok`, `unreachable`, `parked`, `redirect_social` or `robots_disallowed` (table below). |
| `title`, `description`, `language` | The page title, its meta or Open Graph description, and the language declared in `<html lang>`. |
| `emails` | Up to five role mailboxes (`info@`, `sales@`, `bookings@` and the like) from the homepage and contact page together. Addresses that name a person are dropped. |
| `socials` | The first profile link per network: Facebook, Instagram, LinkedIn, X (Twitter), YouTube and TikTok. Share buttons and personal `linkedin.com/in/` profiles are removed. |
| `whatsapp` | The number in the first WhatsApp click-to-chat link, written as `+` and digits. |
| `contact_form_url` | The first link on the same site whose path contains "contact". |
| `tech_stack` | Technologies matched by signatures in the HTML (for example `/wp-content/` for WordPress, `cdn.shopify.com` for Shopify) and in response headers. |
| `crawled_at` | When the site was read, in UTC. |
| `note` | Why a row looks unusual: the robots.txt rule that stopped it, robots.txt could not be read, or no answer within the time limit. |

Every domain you send, apart from the skipped entries above, comes back as a row with a
`status`, so a site that failed is visible in the dataset instead of missing from it.

### Who it is for

- **Lead generation and sales prospecting.** Turn a list of company domains from a trade-show
  list, a directory export or the website column of the Locomint Google Maps actors into a
  sheet with each company's published mailboxes, WhatsApp number and social profiles.
- **CRM and spreadsheet enrichment.** Export the email or website column and run it as it is.
  Because `sales@example.com` and `https://example.com/about` reduce to the same domain,
  contacts de-duplicate into companies; merge the results back on `input` or `domain`.
- **Agencies** qualifying a niche. The tech stack sits in the same row, so "Shopify stores with
  an Instagram profile and no published email" is a filter, not an afternoon of clicking.
- **Market research.** Count a segment by platform, by declared language, or by how many
  domains in it are parked or only redirect to a social profile.

### How to use it

In the Apify Console:

1. Paste domains, website URLs or email addresses into **Domains, websites or email
   addresses**, one per line, up to 5,000. Mixed formats in one list are fine.
2. Leave **Read the contact page too**, **Honour robots.txt** and **Skip free email
   providers** on unless you have a reason to change them.
3. Set a maximum cost per run if you want a ceiling. The actor stops before the row that would
   pass it.
4. Start the run. The **Companies** view shows domain, status, title, emails, social profiles,
   WhatsApp, contact page and tech stack; **What each site says** shows what you entered, the
   address read, title, description, language and note. Export as CSV, Excel or JSON.

From the API, this call starts a run, waits for it and returns the rows:

```bash
curl -X POST \
  "https://api.apify.com/v2/acts/locomint~company-enrichment/run-sync-get-dataset-items?token=YOUR_APIFY_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"domains": ["coffeecollective.dk", "https://www.monmouthcoffee.co.uk/"]}'
```

The synchronous endpoint waits up to 300 seconds. For a long list, start the run with
`POST https://api.apify.com/v2/acts/locomint~company-enrichment/runs?token=YOUR_APIFY_TOKEN`
and read its dataset when the run finishes. With the Python client (`pip install
apify-client`):

```python
from apify_client import ApifyClient

client = ApifyClient("YOUR_APIFY_TOKEN")
run = client.actor("locomint/company-enrichment").call(
    run_input={"domains": ["coffeecollective.dk", "fassbender-rausch.de"]}
)
for row in client.dataset(run.default_dataset_id).iterate_items():
    print(row["domain"], row["status"], row["emails"], row["tech_stack"])
```

### Input example

```json
{
  "domains": [
    "coffeecollective.dk",
    "https://www.monmouthcoffee.co.uk/",
    "fassbender-rausch.de"
  ],
  "followContactPage": true,
  "respectRobots": true,
  "skipFreeEmailDomains": true,
  "concurrency": 5,
  "siteTimeout": 30
}
```

| Field | Default | Allowed | What it does |
|---|---|---|---|
| `domains` | required | 1 to 5,000 entries | Domains, URLs or email addresses. Through the API, one string with entries separated by new lines, commas or semicolons also works. Over 5,000 after de-duplication, the first 5,000 are used. |
| `followContactPage` | `true` | true / false | Read the contact page when the homepage has no email or no WhatsApp number. |
| `respectRobots` | `true` | true / false | Skip a site whose robots.txt disallows its homepage; the row arrives as `robots_disallowed`. |
| `skipFreeEmailDomains` | `true` | true / false | Leave out gmail.com, yahoo.com, outlook.com and 89 similar providers, uncharged. |
| `concurrency` | 5 | 1 to 10 | Sites read at the same time. |
| `siteTimeout` | 30 | 5 to 90 seconds | A site with no answer by then is delivered as `unreachable`. |

### Output example

A real row, from a run on 11 September 2026 with the input above. The four addresses are
role mailboxes the company publishes (general, web shop, orders and jobs).

```json
{
  "domain": "coffeecollective.dk",
  "input": "coffeecollective.dk",
  "website": "https://coffeecollective.dk/",
  "status": "ok",
  "title": "Coffee Collective",
  "description": "A specialty coffee roastery. We work directly with farmers to ensure quality, transparency, and sustainability - creating experiences from farmer to guest.",
  "language": "en",
  "emails": [
    "mail@coffeecollective.dk",
    "webshop@coffeecollective.dk",
    "ordre@coffeecollective.dk",
    "job@coffeecollective.dk"
  ],
  "socials": {
    "instagram": "https://www.instagram.com/coffeecollectif",
    "linkedin": "https://www.linkedin.com/company/coffeecollective"
  },
  "whatsapp": null,
  "contact_form_url": "https://coffeecollective.dk/policies/contact-information",
  "tech_stack": ["shopify", "google-analytics", "google-tag-manager", "recaptcha", "cloudflare"],
  "crawled_at": "2026-09-11T18:42:08+00:00",
  "note": null
}
```

The other two rows from that run show the common variations. `https://www.monmouthcoffee.co.uk/`
became `monmouthcoffee.co.uk`, read as WordPress with WooCommerce, with an Instagram profile and
no published mailbox, so `emails` is empty. `fassbender-rausch.de` came back with `service@` and
`b2b@` addresses on `rausch.de`, the domain the company's mail actually uses, and the note
`robots.txt could not be read, so no rules were applied`.

#### Status values

| `status` | Meaning | Fields |
|---|---|---|
| `ok` | The homepage was read. | Whatever the site publishes; an empty list means none was found in the HTML. |
| `unreachable` | No connection, a DNS failure, an HTTP error, a 403, 429 or challenge page, or no answer within `siteTimeout`. Not retried from another address. | Empty, with a `note`. |
| `parked` | The homepage matches a parked-domain, for-sale or coming-soon signature. | Empty. |
| `redirect_social` | The domain redirects to a social network profile instead of a website. | `socials` holds that profile. |
| `robots_disallowed` | robots.txt disallows the homepage, so nothing was fetched. | Empty; `note` quotes the rule. |

### Pricing

| Event | Price |
|---|---|
| Company delivered (one dataset row) | $0.001, which is $1.00 per 1,000 until 26 September 2026, then $0.005, which is $5.00 per 1,000 |
| Actor start | $0.00005 per GB of run memory, charged once per run |

Worked example: 10,000 domains is two runs of 5,000, so 10,000 x $0.001 = $10.00 plus two start
events of $0.00005 each for 1 GB runs. From 26 September 2026 the price per domain becomes
$0.005.

You pay only these event prices; Apify compute is not billed to you separately, and the contact
page costs nothing on top of the domain. Every row is charged, including `unreachable`,
`parked` and `robots_disallowed`, because each is an answer about that domain. Skipped entries
(not a domain, or a free email provider) produce no row and no charge. If you set a maximum
cost per run, the actor stops before the row that would pass it.

### FAQ

#### Why is an email missing that I can see on the site?

Only the homepage and one contact page are read, so an address on another page is not found.
Addresses inserted by JavaScript, hidden by Cloudflare's email protection, written as
"info \[at] example.com" or shown as an image are not decoded. Addresses that name a person are
dropped on purpose, and at most five are returned per domain.

#### Can I send email addresses instead of domains?

Yes. `sales@example.com` is reduced to `example.com` and the row describes that company's
website. The address you sent stays in `input` but is not checked; to test whether addresses
accept mail, use the Bulk Email Verifier listed below.

#### Why did some of my entries produce no row?

Entries that are not a domain, URL or email address, and free providers such as gmail.com, are
skipped before the run starts and named in the run log, uncharged. Entries past the first
5,000 unique domains are left out and counted in the log too.

#### Does it try the www address?

Yes, once, when the bare domain does not answer, refuses the connection or returns an error.
`website` shows which address was read. It never tries `www.` after a 403, 429 or challenge
page, because a site that has asked us to stop is not approached under its other name.

#### What happens when a site blocks it?

A 403, 429 or challenge page makes that domain `unreachable`. It is not retried from the same
address or any other, and the host is left alone for 15 minutes. The actor does not solve
CAPTCHAs or rotate IP addresses, so sites behind strict firewalls come back `unreachable` more
often than they would in a browser.

#### Does it honour robots.txt?

Yes, for the homepage. If the `*` group disallows it, the site is not fetched and the row is
`robots_disallowed` with the rule in `note`. A site with no robots.txt has no rules; one whose
robots.txt cannot be read is fetched anyway and `note` says so.

#### Does it return phone numbers, company size or industry?

Phone numbers only when the site has a WhatsApp click-to-chat link. Everything comes from the
company's own website, so there is no headcount, revenue, industry code or founder data. The
Locomint Google Maps actors return each business's listed phone number.

### Limits

- No JavaScript rendering. A site built entirely in the browser returns a title and little else.
- The homepage plus at most one contact page per domain. 5,000 domains per run, 30 seconds per
  site by default (5 to 90), 3 MB of HTML per page, at most 5 redirects, each checked before it
  is followed.
- Up to five role mailboxes per domain. Cloudflare-protected, image and "\[at]" addresses are not
  decoded.
- Social profiles: the first link per network. The actor records the link and never opens the
  profile.
- robots.txt is applied to the homepage. The contact page, and a site's English edition when it
  declares one, are read without a separate check.
- `website` is the address the run started from. When a site redirects to another domain the row
  does not name it, though the email addresses often show it.
- Datacenter connection only. Some firewalls refuse datacenter addresses outright; those sites
  are reported `unreachable` rather than approached another way.
- Nothing is cached between runs; every run reads the sites live.

### Compliance

Business contact points only: role mailboxes published on the company's own site. Mailboxes
that name a person and personal LinkedIn profiles are dropped. You are responsible for using
the data lawfully, for example GDPR, CAN-SPAM and PECR for outreach.

Questions, bug reports and feature requests go on this actor's Issues tab. Business owners can send data-removal requests to info@locomint.io; that address is for removal requests only. This actor keeps no copy of what it reads between runs, so each run returns what the public pages show at that moment.

### Other Locomint actors

- [Google Maps Scraper & Email Extractor – Business Leads](https://apify.com/locomint/google-maps-scraper): Search terms and a city in, business records with website contacts out.
- [Google Maps Scraper – Multi-City Lead Lists with Emails](https://apify.com/locomint/google-maps-multi-city-scraper): Many categories across many cities in one deduplicated run.
- [Google Maps Place Details Scraper – Bulk Place ID Lookup](https://apify.com/locomint/google-maps-place-details-scraper): Place IDs or place-page links in, full records out.
- [Website Email Scraper – Contact Details, Socials & WhatsApp](https://apify.com/locomint/website-content-contact-extractor): Contact points from website URLs you supply.
- [Website Content Crawler – Markdown for AI, Emails & Contacts](https://apify.com/locomint/website-crawler-content-contacts): A whole site as Markdown, with its contact points.
- [Bulk Email Verifier & Validator](https://apify.com/locomint/bulk-email-verifier): Checks whether addresses can receive mail.
- [AI Crawler Checker – robots.txt Rules for GPTBot & ClaudeBot](https://apify.com/locomint/ai-crawler-checker): Which AI crawlers a site's robots.txt allows.
- [Schema Markup Validator & Generator – JSON-LD Checker](https://apify.com/locomint/schema-markup-validator): Checks and generates schema.org markup.

# Actor input Schema

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

One per line. acme.com, https://www.acme.com/about and sales@acme.com all mean the same company, so duplicates are removed. Free email providers (gmail.com and the like) and anything that is not a domain are skipped and not charged. Up to 5,000 per run.

## `followContactPage` (type: `boolean`):

When the homepage shows no email address or WhatsApp number, read the site's contact page as well. One extra page, no extra charge.

## `respectRobots` (type: `boolean`):

Skip a site whose robots.txt disallows its homepage. The row is still delivered with status robots\_disallowed.

## `skipFreeEmailDomains` (type: `boolean`):

Leave out gmail.com, yahoo.com, outlook.com and similar domains, which belong to no company. Skipped entries are not charged.

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

How many sites to read at the same time.

## `siteTimeout` (type: `integer`):

A site that has not answered by then is delivered with status unreachable.

## Actor input object example

```json
{
  "domains": [
    "coffeecollective.dk",
    "https://www.monmouthcoffee.co.uk/",
    "fassbender-rausch.de"
  ],
  "followContactPage": true,
  "respectRobots": true,
  "skipFreeEmailDomains": true,
  "concurrency": 5,
  "siteTimeout": 30
}
```

# Actor output Schema

## `companies` (type: `string`):

One row per domain: status, what the site says it is (title, description, language), role email addresses, social profiles, WhatsApp, contact page and tech stack.

# 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": [
        "coffeecollective.dk",
        "https://www.monmouthcoffee.co.uk/",
        "fassbender-rausch.de"
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("locomint/company-enrichment").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": [
        "coffeecollective.dk",
        "https://www.monmouthcoffee.co.uk/",
        "fassbender-rausch.de",
    ] }

# Run the Actor and wait for it to finish
run = client.actor("locomint/company-enrichment").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": [
    "coffeecollective.dk",
    "https://www.monmouthcoffee.co.uk/",
    "fassbender-rausch.de"
  ]
}' |
apify call locomint/company-enrichment --silent --output-dataset

```

## MCP server setup

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

```

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/XAOQfQ7lGhQRFBTRK/builds/8i8yMA32vELZnRs9S/openapi.json
