# Company Enrichment from Domain: contacts, socials, tech, DNS (`weio/company-profile-from-domain`) Actor

Company enrichment from a domain: name, description, role emails, phones, socials, address, CMS, ecommerce, analytics, email provider, SPF, DMARC, SSL expiry, registration date. Only the company's own website and public DNS/RDAP. $0.006 per enriched domain; empty and blocked rows are free.

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

## Pricing

$6.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?

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

## Company Enrichment from Domain

**Company enrichment and domain enrichment from the company's own website.** Give it a list of company domains
(website to company data). For each one you get a single row: the company name, a short description, role email
addresses, phone numbers, social links, the schema.org postal address, the website's CMS, ecommerce platform and
analytics tags, the email provider, SPF and DMARC records, the SSL certificate expiry date and the domain
registration date.

- **Where the data comes from:** only the company's own website and public DNS and RDAP (the registries'
  successor to WHOIS). Nothing is bought, guessed or taken from a third-party database.
- **What it does not have:** no employee counts, no revenue figures and no other estimates. It is not a people
  database: emails are role addresses (info@, sales@...) on the company's own domain or a free-mail inbox it publishes.
- **Bot-walled sites return status only:** a site that blocks automated readers (HTTP 403/503, or a bot-check or
  queue page) gets its HTTP status and the error `bot wall` or `http 403`, plus DNS facts when `includeDns` is on.
  Such rows are free.

### Pricing

**$0.006 per enriched domain** (event `domain-enriched`). A row is charged only when the company's own website was
read: it answered with a status below 400, was not a bot wall and gave at least one fact. DNS and RDAP facts (email
provider, SPF, DMARC, registration date) are included at no extra charge. Free rows: invalid domains, and sites that
are unreachable, robots-blocked, blocked (403/503) or bot-walled, even when their DNS facts are returned. Every row
has a `charged` column that says whether it was billed. A domain listed twice is profiled and charged once.

### Input

| Input | Default | What it does |
|---|---:|---|
| `domains` | 5 sample domains (prefill) | Required. Domains or website URLs, up to 500 per run. `example.com`, `www.example.com` and `https://example.com/contact` all mean `example.com`. A domain listed twice is profiled once. An entry that is not a domain gets a row with error `invalid domain`. |
| `maxPagesPerDomain` | 3 | Pages read on each site, homepage included (1 to 5). 1 reads the homepage only. 2 adds the contact page. 3 adds the about page. 4 and 5 add linked team, location, company or imprint pages. |
| `includeDns` | `true` | Look up the email provider, SPF, DMARC and the registration date. Turn it off for a faster, website-only profile. |

```json
{ "domains": ["mailchimp.com", "zapier.com", "buffer.com", "squarespace.com"], "maxPagesPerDomain": 3, "includeDns": true }
```

### Output

You get exactly one dataset row per unique input domain, in input order, with these keys in this order:

| Field | Meaning |
|---|---|
| `domain` | The normalized domain (`example.com`). For an invalid entry, the text you entered. |
| `finalUrl` | The homepage URL after redirects. |
| `httpStatus` | The HTTP status of that homepage. |
| `companyName` | The company's name. Taken from schema.org first, then `og:site_name`, then the page `<title>` (see below). |
| `companyNameSource` | Where the name came from: `schema.org`, `og:site_name`, `title`, or `null`. |
| `description` | The homepage meta description. If there is none, `og:description`. If there is none, the schema.org description. At most 300 characters. |
| `emails` | **Role addresses only**, such as info@, sales@, support@ and hello@, from every page read and from schema.org `email`, kept only when they are on the company's own domain (or its redirect target) or a common free-mail inbox. Template placeholders (email@address.com, you@domain.com...) and addresses on other companies' domains (a web agency, a sister business) are dropped. Addresses that look like a person's name (jane.smith@...) are dropped on purpose. Sorted, at most 10. |
| `phones` | Phone numbers from `tel:` links, North American numbers in the visible text, and schema.org `telephone`. Template placeholder numbers are dropped. Sorted, at most 6. |
| `socialLinks` | `{network: url}` for Facebook, Instagram, LinkedIn, X, YouTube, TikTok, Yelp, Pinterest and Nextdoor. Links on the pages come first, then schema.org `sameAs`. The first link found for a network is kept. Share buttons are ignored. |
| `postalAddress` | `{streetAddress, addressLocality, addressRegion, postalCode, addressCountry}` from a schema.org PostalAddress, written inline or referenced by `@id` on the same page. `null` when the site publishes no structured address. The actor never guesses an address from page text. |
| `cms` | The site builder or CMS, such as WordPress, Wix, Squarespace or Webflow. Detected from the homepage markup, response headers and the generator tag. |
| `ecommercePlatform` | Shopify, WooCommerce, BigCommerce or Magento when detected. |
| `analytics` | Analytics and advertising tags found on the homepage, such as Google Analytics, Google Tag Manager, Meta Pixel and Microsoft Clarity. Sorted. |
| `emailProvider` | Who handles the domain's email, based on its lowest-preference MX host. Values include Google Workspace, Microsoft 365, Zoho, GoDaddy, Proofpoint, Mimecast, Barracuda, Rackspace, Yahoo, iCloud, Fastmail, Proton, Namecheap, IONOS, Hostinger and Cloudflare Email Routing. Any other host gives its registrable domain. A null MX record gives `none (null MX)`. No MX record gives `null`. |
| `spf` | The domain's SPF record (`v=spf1 ...`), or `null`. |
| `dmarc` | The domain's DMARC record (`v=DMARC1; ...`), or `null`. |
| `sslExpiry` | When the website's TLS certificate expires (its `notAfter`), in ISO-8601 UTC. `null` when the site is not served over https or the certificate does not verify. |
| `domainCreated` | The domain's registration date from RDAP. For a subdomain, the registered parent's date. |
| `pagesChecked` | The pages that were read, homepage first. |
| `error` | `null` on success. Otherwise a short reason: `invalid domain`, `bot wall` (a bot-check or queue page answered instead of the site; status only), `robots-disallowed` (also an unreadable robots.txt or a crawl delay that is too long), `blocked: non-public address`, `host asked us to back off`, `too many redirects`, `bad redirect`, `unreachable: <reason>` (for example `unreachable: gaierror` when the domain does not resolve), `http 404` (or another status), `not html`, `certificate error`, `dns lookups failed`, `dns failed: <reason>` or `unexpected failure: <reason>`. |
| `charged` | `true` when this row was billed one `domain-enriched` event (see Pricing). |

A row with an error still carries every fact that could be read. For example, a site with a broken certificate is
still read, its facts are filled in, and the row says `certificate error`. DNS facts are filled in even when the
website is down.

#### How the company name is chosen

1. schema.org JSON-LD: an Organization, LocalBusiness or one of their common subtypes, such as Store, Restaurant,
   Dentist, Physician, Attorney, Plumber, HomeAndConstructionBusiness, AutoDealer or RealEstateAgent. `@graph`,
   arrays and lists of types are all read. The names of WebSite, WebPage, Product and Person entries are never used.
   schema.org microdata (an organization `itemscope` with `itemprop="name"`) counts as schema.org too, unless the
   organization is a property of another item, such as a Product's manufacturer or an Offer's seller.
2. The `og:site_name` meta tag.
3. The homepage `<title>`. If the title starts with a generic segment such as "Home" or "Welcome" followed by a
   separator (`|`, `–`, `—`, `-`, `:`), the next segment is used. Otherwise the whole title is used, so this
   fallback can include a tagline.

#### Which pages are read

The actor reads the homepage first. It tries `https://domain/`, then `http://domain/`, then `https://www.domain/`.
Then, within `maxPagesPerDomain`, it reads more pages on the same site:

1. The linked contact page and the linked about page.
2. If either is not linked, `/contact` or `/about` instead.
3. Only when `maxPagesPerDomain` is 4 or 5: linked team, location, company, imprint or impressum pages.

No URL is requested twice. Redirects are followed one hop at a time, so a redirect to a page that was already read
is not requested again. A redirect that only adds or removes the trailing slash (`/contact` to `/contact/`) is
followed. Pages that are not HTML, or that return an error status, are skipped. Page text is never
included in the output.

### How it behaves on other people's sites

- Every request identifies itself as `WeioBot`.
- It obeys robots.txt for every host and every redirect hop. If robots.txt refuses the homepage, the row says
  `robots-disallowed` and nothing else on that site is fetched. The actor never tries another route around it.
- Requests are paced to about one per second per host (slower if robots.txt asks for a crawl delay). The pacing is
  shared by every domain in the run.
- When a host answers 429 or 503, the actor waits as long as the host asks (Retry-After) and then retries. If
  the host asks for a pause longer than 30 seconds, the actor does not wait. The page is skipped instead.
- Only public internet addresses are contacted.
- For `sslExpiry`, the actor opens one verified TLS connection to the site and reads the certificate. It sends no
  HTTP request on that connection.
- DNS facts come from public DNS-over-HTTPS (Cloudflare, with Google as the fallback). Registration dates come from
  RDAP, the registries' official successor to WHOIS, found through the IANA bootstrap file. Those services are
  reached the same way: WeioBot identity, their robots.txt obeyed, paced, with Retry-After backoff on rate limits.

### Limits

- This actor does not run JavaScript. A site that renders all of its content with JavaScript may yield only its
  title.

- Only role email addresses are returned. If a site lists only personal addresses, `emails` is empty.

- A row is charged when the company's own website gave at least one fact (see Pricing), whether or not the site publishes a role email, phone number or social link.

- Phone numbers in the visible text are recognized only in North American format. `tel:` links and schema.org
  numbers in any format are kept.

- Technology detection looks at the homepage only, using a compact rule set, not a full signature database.

- With DNS on, each domain needs about 6 to 7 DNS-over-HTTPS lookups. Those lookups share one DNS host that is paced
  to one request per second, so a large list takes about 6 to 7 seconds per domain. The website part runs up to
  5 domains at once.

- `domainCreated` is `null` when the registry has no RDAP service or does not publish a registration event.

- The default run timeout is 1 hour, which fits about 500 domains with DNS on (about 7 seconds each). For longer
  lists, split them or raise the timeout in the run options. If a run stops early you are charged only for the rows
  it delivered.

- A bot-check page is recognised by its title or challenge markers (Cloudflare, PerimeterX, DataDome, Incapsula and
  similar). A new kind of wall may not be recognised; its row then has the page's own title as `companyName`.

Built and maintained by Weio, Inc. The actor was written and is operated by AI agents (Anthropic Claude and OpenAI
Codex), with a person accountable at Weio.

# Actor input Schema

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

Company domains or website URLs, one per entry (example.com, www.example.com or https://example.com/contact all mean example.com). Duplicates are profiled once; an entry that is not a domain gets a row with error "invalid domain". Up to 500 per run (about 7 seconds per domain with DNS on; the default run timeout is 1 hour).

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

Pages read on each site, homepage included. 1 = homepage only; 2 adds the contact page; 3 adds the about page; 4 and 5 add linked team, location, company or imprint pages.

## `includeDns` (type: `boolean`):

Look up the email provider (MX), SPF, DMARC and the domain registration date through public DNS-over-HTTPS and RDAP. Turn off for a faster, website-only profile.

## Actor input object example

```json
{
  "domains": [
    "mailchimp.com",
    "zapier.com",
    "buffer.com",
    "squarespace.com",
    "weio.ai"
  ],
  "maxPagesPerDomain": 3,
  "includeDns": true
}
```

# Actor output Schema

## `profiles` (type: `string`):

The default dataset, profiles view.

# 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": [
        "mailchimp.com",
        "zapier.com",
        "buffer.com",
        "squarespace.com",
        "weio.ai"
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("weio/company-profile-from-domain").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": [
        "mailchimp.com",
        "zapier.com",
        "buffer.com",
        "squarespace.com",
        "weio.ai",
    ] }

# Run the Actor and wait for it to finish
run = client.actor("weio/company-profile-from-domain").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": [
    "mailchimp.com",
    "zapier.com",
    "buffer.com",
    "squarespace.com",
    "weio.ai"
  ]
}' |
apify call weio/company-profile-from-domain --silent --output-dataset

```

## MCP server setup

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

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/incdhWZ38fxVnaGZS/builds/ugogrRUhwmvZTa8AV/openapi.json
