# Company Domain Finder (`wildorigins/company-domain-finder`) Actor

🏷️ From $0.60 / 1K | Turn a list of company names into official websites and social profiles, with a confidence score, the signals that fired, and the rejected candidates and why. Directory and profile hosts are never returned as a company website.

- **URL**: https://apify.com/wildorigins/company-domain-finder.md
- **Developed by:** [Wild Origins](https://apify.com/wildorigins) (community)
- **Categories:** Lead generation, Automation
- **Stats:** 42 total users, 27 monthly users, 99.9% runs succeeded, 1 bookmarks
- **User rating**: No ratings yet

## Pricing

from $0.60 / 1,000 company records

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?

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 Domain Finder

Give it a list of company names and it returns the official website for each one, the social profiles that website publishes, a confidence score, and the candidate domains it rejected along with the reason.

### 🔍 What does Company Domain Finder do?

Company name in, **official domain out**. For every name you supply, Company Domain Finder builds candidate domains from the name itself, reads each candidate homepage, and accepts one only when the page proves it belongs to the company you asked for. Names that route cannot settle go to Wikidata, and then to a search engine. The winner comes back with a `confidence` between 0 and 1, a `matchedOn` list saying exactly which signals fired, and `resolutionMethod` and `resolutionEvidence` saying which route found it and what the page proved.

The easiest way to try it is to click **Try for free** and run it on the two names already in the input box. Paste your own list in and it will work the same way.

Matching a company name to a domain is a **judgement, not a lookup**, so this Actor never returns a guess dressed up as a certainty. A weak match comes back marked `ambiguous` with the runners up attached. A name with no findable website comes back `not_found` rather than as a plausible looking wrong answer.

Four statuses, and the difference between the last two is the one that saves you money:

| Status | What it means |
|---|---|
| `found` | One domain won clearly, and its own homepage was fetched and read |
| `ambiguous` | A domain is proposed but not confirmed: a close runner up, a score under your bar, or a homepage that did not respond |
| `not_found` | The lookup ran in full and no candidate survived. Either this company has no findable website, or every candidate found refused to answer, which `sourcesUnavailable` and `error` tell apart |
| `unavailable` | The lookup could not be completed: Wikidata could not be reached after the name candidates found nothing, the run deadline cut the lookup short, or the domain already belongs to another company in your list. `error` says which. Worth running again, and **not charged** |

### 📊 What data can I extract?

One row per company, whatever the outcome:

| Field | Type | What it is |
|---|---|---|
| `query` | string | The entry exactly as you supplied it, so rows line back up with your list |
| `companyName` | string | The name that was looked up |
| `domain` | string | The registrable domain judged to be the company's own website |
| `websiteUrl` | string | The homepage that was fetched and scored, after redirects |
| `confidence` | number | 0 to 1, how strongly the evidence supports the match |
| `matchedOn` | array | The signals that fired, such as `registry-match` or `name-is-domain` |
| `homepageVerified` | boolean | Whether the returned domain's own homepage was fetched and read, rather than the domain resting on a registry claim alone |
| `resolutionMethod` | string | Which route found the domain: `slug-exact`, `slug-corroborated`, `wikidata`, `wikidata-unverified`, `search`, `website-verified` or `website-unverified`. Null when there is no domain |
| `resolutionEvidence` | string | What the page proved, as plus separated tokens such as `name-legal+town`. Null when there is no domain |
| `resolvedTitle` | string | The title tag of the page that was matched |
| `description` | string | The meta or Open Graph description the homepage publishes |
| `logoUrl` | string | The Open Graph image or touch icon, as an absolute URL |
| `country` | string | ISO 3166 alpha-2, from your request or inferred from the domain ending |
| `registeredAt` | string | When the domain was first registered, from RDAP |
| `domainAgeDays` | number | Whole days since registration. A three week old domain is not the company you asked for |
| `nameServers` | array | Nameservers the domain resolves through, lowercased and sorted |
| `registrar` | string | The registrar holding the domain. Not the owner, which every registry redacts |
| `socials` | object | `linkedin`, `x`, `facebook`, `instagram`, `youtube`, `tiktok`, `github` |
| `alternates` | array | Rejected candidates, each with its score and the reason it lost |
| `status` | string | `found`, `ambiguous`, `not_found` or `unavailable` |
| `sourcesUnavailable` | array | Which of `registry`, `search` and `homepages` failed to answer for this company, plus `deadline` when the run timeout cut the lookup short. Empty when everything answered, and also empty when a route was never needed |
| `checkedAt` | string | When the lookup ran, ISO 8601 |
| `error` | string | Why a row without a confirmed answer is worth running again. Null on a confirmed row |

### 💡 Why find company domains?

**Name only lists.** A column of company names from a form, a badge scan or an old spreadsheet becomes a column of domains, with the ones that could not be resolved marked rather than dropped.

**The first field everything else asks for.** Contact address tools, technology lookups, headcount data and news feeds all take a domain as their input. This produces that field from a name.

**Deduplication.** "Apex Engineering Ltd" and "Apex Engineering" are two rows until both resolve to `apexengineering.co.uk`, at which point they are one account.

**Filings, directories and shortlists.** Paste the company names out of a document and get the website and social profiles for the whole set in one run.

**Agent tooling.** The output schema, the confidence score and `homepageVerified` are there so an AI agent can decide whether to trust a row or ask a human, rather than acting on a guess.

### 🚀 How do I use Company Domain Finder?

1. Click **Try for free**.
2. Put your company names into `companies`. Plain strings are fine, and objects with `name`, `country`, `city`, `postcode` and `hint` disambiguate the common names. Add `website` to an entry when you already have a domain and want it checked rather than guessed.
3. Set `country` to an ISO 3166 alpha-2 code such as `GB` if your whole list is from one market.
4. Leave `minConfidence` at `0.4` unless you want a stricter bar for a row to count as confirmed.
5. Click **Start**, then download the results as JSON, CSV or Excel, or read them from the API.

### ⬇️ Input

```json
{
  "companies": [
    "Monzo Bank",
    { "name": "Apex Engineering", "country": "GB", "city": "Manchester" },
    { "name": "Alfie Marketing Ltd", "postcode": "HP14 3GA", "website": "alfiemarketing.com" }
  ],
  "country": "GB",
  "minConfidence": 0.4,
  "includeSocials": true,
  "maxCandidates": 8,
  "useSearchFallback": false
}
```

| Field | Type | Default | What it does |
|---|---|---|---|
| `companies` | array | required, prefilled with `["Monzo Bank", "Patagonia"]` | Names as strings, or objects with `name` plus optional `country`, `city`, `postcode`, `hint` and `website`. A run with no names fails rather than finishing empty, so a broken integration is visible instead of silent |
| `country` | string | none | ISO 3166 alpha-2 code applied to every entry without its own country |
| `minConfidence` | number | `0.4` | Below this a row is flagged `ambiguous` rather than `found`. Nothing is ever hidden |
| `includeSocials` | boolean | `true` | Read social profile links from the matched homepage markup |
| `maxCandidates` | integer | `8` | How many slug domains, the ones built from the name itself, are fetched and checked per company before Wikidata is asked. Counts only those, not the Wikidata claims checked afterwards. 1 to 8, and 8 is the maximum |
| `useSearchFallback` | boolean | `false` | Send one paced DuckDuckGo query for a company neither its name nor Wikidata resolved. Off by default because from the Apify platform that endpoint challenges the request within seconds, so it resolves nothing there and only costs time |

### ⬆️ Output

#### Table view

Results arrive as a Companies table you can sort and filter in the Console, with the company, the domain, the confidence, the status, whether the homepage was read, any source that did not answer, the signals that matched, the page title, the country, the socials and the rejected candidates lined up per row.

#### JSON

Two real rows, copied out of a run of the code that is in this Actor today, on 25 August 2026. Nothing here is tidied up: `country` is null because the request did not state one and `.com` implies nothing, two of the seven social slots are empty because the Monzo homepage does not link them, and the rejected candidates are the four slug domains that were tried before Wikidata was asked, each with the reason it lost. Monzo's domain is not its name, so the name route found nothing and the registry claim, confirmed by the name on the homepage, is what resolved it.

```json
{
  "query": "Monzo Bank",
  "companyName": "Monzo Bank",
  "domain": "monzo.com",
  "websiteUrl": "https://monzo.com/",
  "confidence": 1,
  "matchedOn": ["evidence-gate","registry-match","name-in-domain","name-on-homepage","social-handle-match"],
  "homepageVerified": true,
  "resolvedTitle": "Monzo | Open a Free Account",
  "description": "Your salary sorted into Pots. Your spare change turned into savings. Your money, doing what it should with Monzo.",
  "logoUrl": "https://images.ctfassets.net/ro61k101ee59/2TTYm6n3SCsWCaZnIT3Vo1/dd3379fae4c09ed2c86a4e7effafaace/Open_Graph_monzo.com__50_.jpg",
  "country": null,
  "registeredAt": "1998-01-19T05:00:00Z",
  "domainAgeDays": 10445,
  "nameServers": ["curt.ns.cloudflare.com","kara.ns.cloudflare.com"],
  "registrar": "Amazon Registrar, Inc.",
  "socials": {
    "linkedin": "https://www.linkedin.com/company/monzo-bank",
    "x": "https://twitter.com/monzo",
    "facebook": "https://www.facebook.com/monzobank",
    "instagram": "https://www.instagram.com/monzo",
    "youtube": "https://www.youtube.com/monzobank",
    "tiktok": null,
    "github": null
  },
  "alternates": [
    { "domain": "monzobank.com", "confidence": 0, "reason": "homepage could not be fetched: fetch failed" },
    { "domain": "monzobank.co.uk", "confidence": 0, "reason": "homepage could not be fetched: fetch failed" },
    { "domain": "monzo-bank.com", "confidence": 0, "reason": "homepage could not be fetched: fetch failed" },
    { "domain": "monzo-bank.co.uk", "confidence": 0, "reason": "homepage could not be fetched: fetch failed" }
  ],
  "status": "found",
  "sourcesUnavailable": [],
  "checkedAt": "2026-08-25T09:30:10.281Z",
  "error": null,
  "resolutionMethod": "wikidata",
  "resolutionEvidence": "name-legal+registry"
}
```

And the second row from the same run, for a company name that was invented so there would be nothing to find. Every promised field is present, the ones with no answer are null, and the candidates that were tried are listed with the reason each one lost. Every slug domain failed to resolve at all, so the row carries `homepages` in `sourcesUnavailable` and is charged, because the lookup ran in full.

```json
{
  "query": "Zzqxwv Fabrication Holdings",
  "companyName": "Zzqxwv Fabrication Holdings",
  "domain": null,
  "websiteUrl": null,
  "confidence": 0,
  "matchedOn": [],
  "homepageVerified": false,
  "resolvedTitle": null,
  "description": null,
  "logoUrl": null,
  "country": null,
  "registeredAt": null,
  "domainAgeDays": null,
  "nameServers": null,
  "registrar": null,
  "socials": {
    "linkedin": null,
    "x": null,
    "facebook": null,
    "instagram": null,
    "youtube": null,
    "tiktok": null,
    "github": null
  },
  "alternates": [
    { "domain": "zzqxwvfabrication.com", "confidence": 0, "reason": "homepage could not be fetched: fetch failed" },
    { "domain": "zzqxwvfabrication.co.uk", "confidence": 0, "reason": "homepage could not be fetched: fetch failed" },
    { "domain": "zzqxwv-fabrication.com", "confidence": 0, "reason": "homepage could not be fetched: fetch failed" },
    { "domain": "zzqxwv-fabrication.co.uk", "confidence": 0, "reason": "homepage could not be fetched: fetch failed" }
  ],
  "status": "not_found",
  "sourcesUnavailable": ["homepages"],
  "checkedAt": "2026-08-25T09:30:10.282Z",
  "error": "every candidate homepage failed to respond, so this row may improve on a retry",
  "resolutionMethod": null,
  "resolutionEvidence": null
}
```

### Where the candidates come from

Three routes, tried in order, and each one only for the companies the route before did not settle.

1. **The name itself, through the evidence gate.** `apexengineering.co.uk`, `apexengineering.com`, `apex-engineering.co.uk` and so on, up to `maxCandidates` of them, the requested market's endings first. Each homepage is read and accepted only when it proves the company: the name **exactly as you gave it**, including any Ltd or Limited, on the page, or the name without its legal form plus something you passed that the page also carries, a postcode, a town, a country or a hint. A page that is parked, for sale, or a business directory listing is refused before the name is even read. This is how most small businesses are resolved, in one or two fetches, with nothing else contacted.
2. **[Wikidata](https://www.wikidata.org).** For the well known brands whose domain is not their name. It states official websites as a claim on the company itself. Two calls to `wikidata.org/w/api.php`, a name search and then the claims. Only entities whose label agrees with your name and that are an organisation rather than a place or a person are used, so the town of Dawlish does not answer for Dawlish Limited and the Arizona town does not answer for Patagonia. The claimed homepage is then read through the same gate, with the registry claim standing in as corroboration.
3. **[DuckDuckGo](https://html.duckduckgo.com/html/), only when you turn it on.** One paced query per company that is still unresolved, sent to `html.duckduckgo.com` unauthenticated. Aggregators are stripped out, and a hit only counts when the site's own homepage passes the gate. It is off by default: from the Apify platform the endpoint challenges the request within seconds, so on there it resolves nothing and costs every unresolved name a wait. Set `useSearchFallback` to true if you run somewhere it answers. It is switched off for the rest of the run at the first sign of a block either way.

Then [RDAP](https://rdap.org) for the winning domain's registration date and nameservers, which is free, keyless and standardised.

Everything is plain HTTP. No browser, no paid API, no key of yours or ours. Every fetch is capped at 2 MB, with six seconds for a site to start answering and ten more to send its page, and at most eight pages are streaming at once so a large brand homepage is read to the end rather than cut off. A candidate whose page is cut off is reported as timed out in `alternates`, never as a page with nothing on it. A run keeps a 45 second margin before its own timeout, so a long list finishes successfully with everything resolved so far and the status message says how many names were reached.

**A website you supply is verified, never guessed past.** Give an entry `website` and the Actor reads that homepage and reports whether the page proves the company, as `website-verified` when it does and `website-unverified`, marked `ambiguous`, when it does not. It never goes looking for a different domain when you have already named one.

**One domain, one company.** A domain that comes up for a second company in the same run is either a site that publishes company data or a group trading from one website, and in neither case can the second row be answered on its own. The first company's row stands, the domain is blocked for the rest of the run, and the second company comes back `unavailable` with the reason in `error`, unbilled, never `not_found`. If the two really are one group, pass `website` on the second entry and it is verified directly.

**Two names for one site are one answer.** A brand whose registry entry states more than one domain, `greggs.co.uk` and `greggs.com` say, that both answer from the same site is judged once. The duplicate goes into `alternates` as the same site reached through a redirect, rather than tying with itself and demoting a confirmed match to `ambiguous`.

**Worth knowing before you buy.** The DuckDuckGo endpoint is the least stable part of this Actor. It is a public HTML page rather than an API, nobody promises it to us, and from the Apify platform it refuses within seconds, which is why it is off by default and last in line. The name itself and Wikidata resolve most companies without it, a lookup that loses a source says so in `sourcesUnavailable` rather than pretending, and a lookup that never got its candidates gathered is not charged for.

### Resolution methods and evidence

Every row with a domain carries `resolutionMethod` and `resolutionEvidence`, so you can filter on how the answer was reached and how strong the proof is.

| `resolutionMethod` | What it means |
|---|---|
| `slug-exact` | A domain built from the name, whose homepage prints the name exactly as you gave it |
| `slug-corroborated` | A domain built from the name, whose homepage prints the name without its legal form plus something you passed |
| `wikidata` | An official website claim on Wikidata, confirmed by the name on that homepage |
| `wikidata-unverified` | An official website claim on Wikidata whose homepage did not respond. Always `ambiguous` |
| `search` | A search hit whose own homepage passed the gate |
| `website-verified` | The website you supplied, and its homepage proves the company |
| `website-unverified` | The website you supplied, read, but the page does not prove the company. Always `ambiguous` |

`resolutionEvidence` lists the tokens that fired, joined with `+`. The first is the name match: `name-legal` for the strict form, `name-core` for the name without its legal suffix, `name-trimmed` for the name with trailing words such as Solutions or Services dropped. The rest are the corroboration found: `registry`, `postcode`, `town`, `country` when the page names your country, `country-domain` when only the domain ending matches it, and `hint`.

A core name backed by nothing but the country is delivered as `ambiguous`, not `found`, with the reason in `error`. Hand checked on the day this shipped: a Leeds company asked for by name resolved to a same name company in London on exactly that evidence. Pass `city` or `postcode` and the same lookup either confirms or refuses.

### How the confidence score is worked out

Each candidate homepage is fetched and checked for the signals below. They add up, and the total is capped at 1.

| Signal | What it means |
|---|---|
| `evidence-gate` | The homepage itself proved the company, see `resolutionEvidence` |
| `registry-match` | An open registry states this domain is the company's official website |
| `established-domain` | Registered more than two years ago, so it is not a fresh lookalike |
| `name-is-domain` | The domain label is exactly the company name with the spaces closed up |
| `name-in-domain` | The company name is contained in the domain label |
| `exact-name-in-title` | The full company name appears in the page title |
| `name-in-title` | Every word of the name appears in the page title |
| `name-on-homepage` | The full company name appears in the homepage text |
| `social-handle-match` | A social profile the homepage links to carries the company's handle |
| `top-search-result` | The domain was the first non directory result |
| `in-search-results` | The domain appeared further down the results |
| `country-match` | The domain ending matches the country you asked for |

Two rules stop a plausible wrong answer getting through. **A candidate with no name signal is never returned**, however well it ranked in a search, so a nonsense name comes back `not_found` instead of resolving to whatever happened to be first. And **a domain ending that contradicts your requested country is penalised**, so asking for a UK company and being handed the Italian arm of a group at full confidence cannot happen.

Where the best and second candidate finish within 0.1 of each other, the row is `ambiguous` even if the score is high. Two candidates that close means the Actor cannot tell them apart, and saying so is worth more than a coin toss.

**A score is not a confirmation.** A registry claim scores well on its own, and some sites answer a plain homepage request with a 403 or a 404 while being perfectly real. When the winning domain's own homepage never responded, the row carries `homepageVerified: false` and comes back `ambiguous` rather than `found`, however high the score, with the reason in `error`. The domain is still there and still probably right. It just has nothing behind it that this Actor saw with its own eyes, and a row that cannot tell you that is asking you to trust it blindly.

### The aggregator trap

Search results for any company name fill up with pages **about** the company rather than pages **by** it: LinkedIn, Crunchbase, Bloomberg, Companies House mirrors, Yell, Glassdoor, Trustpilot. None of those is the company's own website, and returning one is the most damaging mistake this Actor could make, because it is only caught after somebody has acted on it.

Company Domain Finder keeps a denylist of those hosts and never returns one as `domain`. They appear in `alternates` with the reason, so you can see they were considered and thrown out rather than missed.

The denylist has one deliberate exception. Someone looking up "LinkedIn" wants `linkedin.com`, so a host stops counting as an aggregator when the name being searched is that aggregator's own brand. Country versions are covered too, which matters more than it sounds: `google.com` being on a list that `google.co.uk` was missing from is exactly how a made up company name once resolved to a search engine.

### Limits

- Social profiles are read from the matched homepage's own markup. That means a company whose footer does not link its accounts returns nulls, and it also means this Actor never contacts a social platform, so nothing here is affected by their blocking.
- A homepage that requires JavaScript to render its text may score lower on the page signals, because no browser is used. The domain and registry signals are unaffected.
- Only the first candidate homepage per domain is read. This finds the website, it does not crawl it.
- `maxCandidates` caps how many slug domains built from the name are checked, 1 to 8. Every domain tried and refused is listed in `alternates` with the reason.
- A run stops early if five companies in a row come back `unavailable` because Wikidata refused them, since that means a source is down rather than the list being unusual. The rows already resolved are kept and the status message says how many names were reached.
- A run that reaches its own timeout finishes successfully with every company resolved so far, and the status message says how many names were not reached. Nothing that was charged is lost. A company the deadline caught mid lookup, before its candidates were gathered, is delivered as `unavailable` with `deadline` in `sourcesUnavailable`, is not charged, and is counted among the names not reached.
- A run with no company names in it fails rather than finishing green with an empty dataset, so a broken integration is visible on the run list instead of looking like a list nobody could resolve.

### ⏱️ How long does a run take?

Measured on real runs, so you know what normal looks like and can tell it apart from a run that has stalled.

| Companies in the list | Typical run time |
|---|---|
| 2 | 7 to 30 seconds |
| 12 | about 10 seconds |
| 50 | 70 to 80 seconds |

The spread on that first row is the honest shape of this Actor rather than noise. A well known name is settled by the first candidate domain the resolver tries, and that is one page load. A name that no candidate proves has to fall through to Wikidata and then to a search engine, and each of those is another round trip before the row can be written. A list of fifty obscure limited companies has therefore been measured at both 76 seconds and, on a batch where almost every name needed the fallback route, over four minutes. The first few seconds of any run are the container starting rather than the work.

Two settings move the figure directly. `maxCandidates` sets how many domains are built and read per name, and `useSearchFallback` adds the last and slowest route for names nothing else settles. Turning the fallback off makes a run faster and returns fewer resolved domains, which is the trade rather than a bug.

A run is never silently stuck. Every company is logged as it is resolved, with the route that settled it, and if a run reaches its time limit it stops early, keeps every domain already found and says how many names were left in the status message.

### 💰 How much does it cost?

Charged **once per company name**. A name that resolves cleanly, one that comes back ambiguous and one with no findable website all cost the same, because proving a company has no website costs the same fetches as finding one.

Company resolved, per lookup:

| Free | Bronze | Silver | Gold | Platinum | Diamond |
|---|---|---|---|---|---|
| $0.001 | $0.00085 | $0.0007 | $0.0006 | $0.0005 | $0.0004 |

Free is the list price and each paid plan steps down from it, to 40 percent on Diamond. A thousand company names is **$1.00** on the Free plan, **$0.85** on Bronze, and **$0.40** on Diamond. Apify's own Actor start event is charged on top at $0.00005 a run, once, because this Actor caps its memory at 256 MB, so that thousand name run is $1.00005 on Free. The Apify listing always shows the current rates.

**A company that was never looked up is not charged.** If the name candidates find nothing and Wikidata refuses the request, if the run deadline arrives before the candidates were gathered, or if the domain already belongs to another company in your list, the row comes back `unavailable`, `error` says why, and no `company-resolved` event fires for it. You pay for answers, including the answer "this company has no website", and not for a lookup that never happened.

That is about the source, not about the company. A name whose candidate homepages were all found and all refused to respond **is** charged, because the lookup ran in full: every candidate was fetched and "no reachable website" is the answer. Those rows come back `not_found` carrying `homepages` in `sourcesUnavailable` and an `error` saying they may improve on a retry, so you can tell them apart from a company that genuinely publishes nothing.

If five companies in a row come back `unavailable`, a source is down rather than the list being unusual, and the run stops there and says so in the log. You keep the rows already resolved, and the rest of the list is left for a later run rather than burned against a source that is not answering.

The price does not move with `maxCandidates`, how many sources answered, or how many social profiles were found.

You can cap what a run is allowed to spend before you start it. Keep the cap above **$0.00105**, the start event plus one lookup, because a cap below that could not pay for a single result and would stop the run without returning anything.

### 🔌 Integrations

Send results straight to Google Sheets, Slack, Airtable, Zapier, Make or your own webhook using [Apify integrations](https://docs.apify.com/platform/integrations). Runs can be [scheduled](https://docs.apify.com/platform/schedules), monitored, and triggered by an event in another tool, so a weekly list of new accounts can be resolved without anyone opening the Console.

### 🔗 Using Company Domain Finder with the Apify API

```bash
curl -X POST "https://api.apify.com/v2/acts/spookyweb~company-domain-finder/run-sync-get-dataset-items?token=YOUR_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"companies": ["Monzo Bank", "Patagonia"], "country": "GB"}'
```

Or with the Apify client:

```js
import { ApifyClient } from 'apify-client';

const client = new ApifyClient({ token: 'YOUR_TOKEN' });
const run = await client.actor('spookyweb/company-domain-finder').call({
  companies: ['Monzo Bank', { name: 'Apex Engineering', country: 'GB', city: 'Manchester' }],
  minConfidence: 0.4,
});
const { items } = await client.dataset(run.defaultDatasetId).listItems();
```

Full detail is in the [Apify API reference](https://docs.apify.com/api/v2), and every run is also callable from the [Python](https://docs.apify.com/api/client/python) and [JavaScript](https://docs.apify.com/api/client/js) clients.

### ❓ FAQ

#### What does the confidence score actually mean?

It is the weighted sum of the signals listed above, capped at 1. A domain stated by an open registry, matching the name exactly, and carrying the company's name in its page title will sit near 1. A domain that only looks right will sit near 0.3. Read `matchedOn` alongside it, because the same score can be reached different ways and the list tells you which.

#### Why did I get a row marked ambiguous with a high confidence?

Because a second candidate scored within 0.1 of it. That usually means a group runs more than one legitimate domain, or two real businesses share a name. Both are in `alternates`, so you can pick.

#### Will it ever return a LinkedIn or Crunchbase page as the website?

No. Directory, registry and social profile hosts are on a denylist and are never returned as `domain`. They show up in `alternates` with the reason. The one exception is when you are actually searching for that company, so looking up "LinkedIn" does return `linkedin.com`.

#### What happens to a company with no website at all?

You get a row with `status` set to `not_found` and `domain` set to null. That is a real finding rather than a failure, and the run finishes successfully. The candidates that were tried are in `alternates` with the reason each one lost.

#### How do I tell "no website" apart from "the site was down"?

Read `status` alongside `sourcesUnavailable` and `error`. `unavailable` means nothing was really checked: the name candidates found nothing and Wikidata refused the request, or the run deadline cut the lookup short, or the domain already belongs to another company in your list. `not_found` with an empty `sourcesUnavailable` means the lookup ran in full and this company has no findable website, which is a real result you can act on. `not_found` carrying `homepages` is the middle case: candidates were found, every one of them was fetched, and none of them answered. `error` says which of the three happened in a sentence, and all three are worth a second run except the middle one, which usually means the sites are genuinely down or refusing automated requests.

#### Why is a row ambiguous when the confidence is high?

Either a second candidate finished within 0.1 of the winner, or `homepageVerified` is false, or the only corroboration was the country and `error` says so. The homepage case is the interesting one: the domain was claimed by the registry and matches the name, but its homepage answered with an error or a timeout when the Actor tried to read it, so there is nothing confirming the claim. Some perfectly real sites do this to a plain HTTP request. Treat the domain as a strong lead and check it yourself, rather than as a verified answer.

#### Can I look up companies outside the UK and the US?

Yes. The country hint is optional and the Actor works on any market. Setting `country` adds that market's domain endings to the candidates tried and puts the country into the search, which improves the answer for common names.

#### How do I disambiguate two companies with the same name?

Pass an object instead of a string: `{ "name": "Apex", "country": "GB", "city": "Manchester", "postcode": "M1 1AE", "hint": "structural steel" }`. The city, postcode and hint are what the evidence gate looks for on the page alongside the name, and they go into the search query too, which is usually the difference between the right company and a coin toss. If you already know the domain, add `website` and it is verified instead.

#### Does it use my API keys, or anyone else's?

No. Every source is public and keyless, which is why the price is what it is.

### ⚖️ Is it legal to find company domains?

Yes. This reads publicly available information: an open knowledge base, a public search index and the homepages companies publish themselves. It handles business information rather than personal data, it does not log in anywhere, and it does not use breach data or scraped private databases.

Social profile URLs are read from a company's own website markup, so no social platform is ever contacted and their terms of service do not come into it. What you do with the results afterwards is yours to get right: if you go on to contact named people, UK GDPR and its equivalents make you the data controller for that. Apify's [ethical scraping guide](https://blog.apify.com/is-web-scraping-legal/) covers the wider picture.

### 👍 Your feedback

Found a bug, or want a field that is not here yet? Open an issue on the Actor's Issues tab. Requests that make the data more useful get built, and problems get fixed quickly.

### 🔎 You might also like

| Actor | What it does |
|---|---|
| [Company Email Finder](https://apify.com/spookyweb/company-email-finder) | Contact addresses, the email naming convention behind them, and MX validation |
| [Website Contact Scraper](https://apify.com/spookyweb/website-contact-scraper) | Emails, phones, socials and addresses from company websites, one record per domain |

# Actor input Schema

## `companies` (type: `array`):

The companies to look up. Give plain names as strings, or objects with a name plus any of country, city, postcode and hint to tell two businesses of the same name apart, and website when you already have a domain and want it verified rather than guessed. Every entry produces one row whatever the outcome.

## `country` (type: `string`):

An ISO 3166 alpha-2 code such as GB or US, applied to every entry that does not carry its own country. It steers the search and adds that market's domain endings to the candidates tried.

## `minConfidence` (type: `number`):

Between 0 and 1. A winning domain scoring below this is still returned, flagged as ambiguous rather than found, so nothing is ever hidden from you. Default 0.4.

## `includeSocials` (type: `boolean`):

Read LinkedIn, X, Facebook, Instagram, YouTube, TikTok and GitHub links from the company's own homepage markup. No social platform is ever contacted.

## `maxCandidates` (type: `integer`):

How many slug domains, the ones built from the company name itself such as apexengineering.co.uk and apex-engineering.com, are fetched and checked against the evidence gate before Wikidata is asked. It counts only those slug domains, not the Wikidata claims checked afterwards. More candidates find awkward names more often and take longer. Default 8, which is also the maximum.

## `useSearchFallback` (type: `boolean`):

For a company neither its own name nor Wikidata resolved, send one paced query to the DuckDuckGo HTML endpoint and check the top hits through the evidence gate. Off by default: from the Apify platform that endpoint challenges the request within seconds, so it resolves nothing there and only costs time. Turn it on for runs from your own infrastructure where it answers. It is switched off for the run at the first sign of a block either way.

## Actor input object example

```json
{
  "companies": [
    "Monzo Bank",
    "Patagonia"
  ],
  "country": "",
  "minConfidence": 0.4,
  "includeSocials": true,
  "maxCandidates": 8,
  "useSearchFallback": false
}
```

# Actor output Schema

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

One row per company looked up, carrying the domain, the confidence, the signals that matched, the social profiles and the rejected candidates.

# 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 = {
    "companies": [
        "Monzo Bank",
        "Patagonia"
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("wildorigins/company-domain-finder").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 = { "companies": [
        "Monzo Bank",
        "Patagonia",
    ] }

# Run the Actor and wait for it to finish
run = client.actor("wildorigins/company-domain-finder").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 '{
  "companies": [
    "Monzo Bank",
    "Patagonia"
  ]
}' |
apify call wildorigins/company-domain-finder --silent --output-dataset

```

## MCP server setup

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

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/7jgMA1I35T4EqNhWW/builds/C5MbCMoG8jD0rlfFx/openapi.json
