# Company Website & Profile Resolver (with evidence) (`plusonets/company-web-presence-resolver`) Actor

Resolve a company name or domain to its official website, social profiles, careers and ATS slug, and key pages. Every claim includes a source URL, snippet, and timestamp. Charged only when calibrated confidence meets your threshold.

- **URL**: https://apify.com/plusonets/company-web-presence-resolver.md
- **Developed by:** [Olalekan Johnson](https://apify.com/plusonets) (community)
- **Categories:** Lead generation, AI
- **Security:** Limited permissions
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $20.00 / 1,000 company resolveds

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 Website & Profile Resolver (with evidence)

Give an agent or a CRM a company name or a domain. Get back the official website and the public pages that site actually links to, each claim tied to a URL, a short snippet, and the time it was observed. A row is billed only when the evidence score meets your threshold.

Unofficial. Not affiliated with LinkedIn, Brave Search, Serper, Greenhouse, Lever, Ashby, Apple, Google, or any company you resolve.

### Who it is for

AI agents and enrichment pipelines that must turn "Acme" into the right domain without inventing one. RevOps and CRM cleanup, when a spreadsheet has legal names and you need the real site, careers page, and docs. Scheduled watchers that should alert only when the resolved identity changes.

It does not guess facts with a language model. If a page does not say it, the field stays empty.

### What you get

For each company:

- Canonical official website, after redirects
- Social profile URLs that the site itself publishes (LinkedIn company page included only in that case)
- Careers page and, when the HTML exposes one, the ATS vendor and board slug
- Pricing, docs, contact, and status-page links
- App-store or marketplace listings linked from the site
- Sitemap size from the first sitemap document
- Confidence from 0 to 1, floored to two decimals
- `evidence[]` with `claim`, `url`, `snippet`, `snippetSha256`, `observedAt`
- `status`: `resolved`, `low_confidence`, `not_found`, `blocked`, `no_results`, or `unsupported`

A run summary is stored in the default key-value store under `OUTPUT` (`rowsCharged`, `rowsDropped`, `fillRate`, coverage, and error counts).

#### Example

Trimmed from a local run of the default input on 23 October 2026. `charged` is true here because the pay-per-event test path accepted the row. On Apify it is true only after `company-resolved` is billed.

```json
{
  "input": "Stripe",
  "status": "resolved",
  "officialWebsite": "https://stripe.com/",
  "companyNameObserved": "Stripe",
  "confidence": 1.0,
  "confidenceThreshold": 0.8,
  "charged": true,
  "chargeEvent": "company-resolved",
  "coverage": "partial",
  "coverageNote": "No search API key was used. Candidates came from the supplied domain or URL and conservative domain guesses.",
  "linkedinCompanyUrl": "https://www.linkedin.com/company/stripe",
  "githubUrl": "https://github.com/stripe",
  "careersUrl": "https://stripe.com/careers",
  "pricingUrl": "https://stripe.com/pricing",
  "docsUrl": "https://docs.stripe.com/",
  "contactUrl": "https://stripe.com/contact/sales",
  "statusPageUrl": "https://status.stripe.com",
  "sitemapUrl": "https://stripe.com/sitemap/sitemap.xml",
  "sitemapSize": 9,
  "sitemapKind": "index",
  "fillRate": 1.0,
  "evidence": [
    {
      "claim": "registrable domain matches the company name",
      "url": "https://stripe.com/",
      "snippet": "stripe.com",
      "snippetSha256": "1ffda32ad70e3cac6d6a04e2fbb00c062cc12d3a02796149e639c6d9a3e2774b",
      "observedAt": "2026-10-23T14:11:55Z"
    },
    {
      "claim": "homepage title matches the company name",
      "url": "https://stripe.com/",
      "snippet": "Stripe | Financial Infrastructure to Grow Your Revenue",
      "snippetSha256": "9b75a03d7f02b799048b25ef8699bfdce88e231ea5a699b189a8a01dda2604a8",
      "observedAt": "2026-10-23T14:11:55Z"
    },
    {
      "claim": "schema.org Organization name matches the company",
      "url": "https://stripe.com/",
      "snippet": "Stripe",
      "snippetSha256": "961a6062ba1ba2d8608e5eea8be67483d936f2f9f41e312217407198e14f087c",
      "observedAt": "2026-10-23T14:11:55Z"
    },
    {
      "claim": "footer or schema.org legalName names the company",
      "url": "https://stripe.com/",
      "snippet": "Stripe, LLC",
      "snippetSha256": "26a7c57c0c19bec1a40b7b16570c2b8b488006e10e4c6b6173905af27c4d5c83",
      "observedAt": "2026-10-23T14:11:55Z"
    }
  ]
}
```

`sitemapKind: index` means 9 child sitemaps were listed. Those children are not downloaded, so this is not a count of every URL on the site.

### How candidates are chosen

With a search API key, the Actor queries Brave Search or Serper, drops directories and social hosts (Wikipedia, LinkedIn, GitHub, news sites, and similar), and fetches the remaining candidates plus a conservative guess.

Without a key, coverage is `partial` and the row says so. Candidates are only:

- the domain or URL you supplied, and
- one or two guesses: the company slug on `.com`, and, when you set a country hint, that country's TLD.

No other Apify Actor is called. LinkedIn is never fetched. A LinkedIn company URL is copied only when the company site publishes it. Personal `/in/` profiles are discarded. Email addresses are redacted and not returned.

### Confidence

The score is a weighted sum of what the fetched HTML contained, then floored to two decimals. It is not a model probability.

| Signal | Weight |
| --- | --- |
| Domain matches the company slug, or the page is the domain you supplied | 0.32 |
| Title, site name, or schema.org name matches | 0.26 |
| schema.org Organization or legal name matches | 0.16 |
| Footer copyright or schema.org `legalName` matches | 0.10 |
| Contact link on the same site | 0.06 |
| Social profile linked from the site | 0.06 |
| Careers, pricing, docs, or status link | 0.04 |

Hard caps keep weak pages below the default 0.8 charge line:

- Parked domain or a directory host scores 0 and is not the official site.
- schema.org names a different organization: at most 0.30.
- No on-page name match: at most 0.35.
- Name matches but the domain was not supplied and does not match: at most 0.40.
- Article or news page without an Organization match: at most 0.55.
- Country or city hint not visible in the title, footer, or schema.org address, and the TLD does not match: at most 0.74. A US hint does not apply this cap.
- Fewer than three independent signals: at most 0.79.
- Two different domains both score near the threshold: the row stays `low_confidence` instead of picking one.

A GitHub profile linked from the site can add 0.04 when that profile links back and the score is just under the threshold. That is the only extra fetch of a social site, and it is skipped when the homepage already clears the threshold.

### Pricing

You're only charged for rows that pass validation. `company-resolved` fires only when status is `resolved`, confidence is at least your threshold, the website is a public https URL, there are at least two complete evidence items, and the fill rate is at least 0.5. `low_confidence`, `not_found`, `blocked`, `no_results`, and `unsupported` are free. The same input is not charged twice in one run.

There is no per-dataset-item charge. Prices:

| Event | Price | When it fires |
| --- | --- | --- |
| `company-resolved` | $0.02 | A validated row at or above the threshold |
| unresolved rows | $0 | Anything that does not pass the rule above |
| `apify-actor-start` | $0.005 per GB (platform start event) | Once per run |

At the default 512 MB, the start event is about $0.0025 per run.

| Run | Billable rows | Event total |
| --- | --- | --- |
| Default demo, 8 companies, all resolved | 8 | 8 × $0.02 = **$0.16** |
| 100 companies, 70 resolved, 30 not found or low confidence | 70 | 70 × $0.02 = **$1.40** |
| 1,000 resolved companies | 1,000 | **$20.00** |

`maxCompanies` defaults to 25 (about $0.50 if every row resolves). `maxChargeUsd` defaults to $5 (about 250 resolved rows). The run also stops when Apify reports the platform max total charge. Rows already delivered stay. A resolved company that would exceed the cap is not returned as a free success.

### Input

Every load-bearing field has a default. An empty `{}` run uses the demo list: Stripe, Notion, Figma, Vercel, Shopify, Atlassian, HubSpot, Cloudflare.

| Field | Default | What it does |
| --- | --- | --- |
| `companies` | the 8 demo names | Names, domains, or URLs |
| `companiesText` | empty | Extra companies, one per line. `Name, domain.com` is accepted |
| `countryHint` | empty | Disambiguation only |
| `cityHint` | empty | Disambiguation only |
| `confidenceThreshold` | 0.8 | Charge line |
| `searchProvider` | `none` | `none`, `brave`, or `serper` |
| `searchApiKey` | empty, secret | Your Brave or Serper key. Never required |
| `maxCompanies` | 25 | Cap, 1 to 500 |
| `outputMode` | `all` | `changes_only` emits new or changed identities |
| `maxChargeUsd` | 5 | Actor-side dollar cap. 0 disables it |

Private IPs, localhost, `file://`, credentialed URLs, and ports other than 80 and 443 are rejected and returned as `unsupported`. They are not fetched.

### Repeat runs

Resolved identities are stored in the named key-value store `company-web-presence-state`. A later `blocked` or `not_found` result does not overwrite a good baseline. `changes_only` pushes a dataset row only when the website, LinkedIn URL, careers URL, or ATS slug differs, or when the company has never been resolved. If nothing changed, the dataset gets one free `no_results` row so the run is not silently empty.

`OUTPUT` on every run includes `counts`, `rowsCharged`, `rowsDropped`, `fillRate`, `chargeLimitReached`, `coverage`, and `errors` (`proxy`, `rateLimit`, `blocked`, `network`, `other`). An HTTP 200 challenge page counts as `blocked`, not as a match.

### Integrations

The dataset is flat enough for CSV (`appStoreUrlsFlat` joins store links with pipes). `evidence` stays an array.

- **Webhook.** Point an Apify webhook at the dataset. Filter on `status == resolved` or, for a watcher, run with `outputMode: changes_only`.
- **n8n / Make.** Use the Apify node, pass `companies` from a sheet or CRM, and map `officialWebsite`, `linkedinCompanyUrl`, `careersUrl`, and `confidence`.
- **Google Sheets.** Use the Apify integration to import the default dataset. Empty profile fields are null, not invented strings.
- **MCP.** The input and dataset schemas name every field. Call the Actor with `{}` and the demo set runs. Read `OUTPUT` for `rowsCharged` before you trust a batch.

### Scope and limits

- Public HTML only. No login, no CAPTCHA solving, no browser. A site that is an empty JavaScript shell can come back `low_confidence` or `not_found`.
- Without a search key, a generic name can miss or, more rarely, land on a different company that owns the `.com` and uses the same name. Close scores are returned as `low_confidence` instead of a forced pick.
- Sitemap size is the first sitemap or sitemap index only.
- ATS slug is set only when a known board URL (Greenhouse, Lever, Ashby, Workable, and similar) appears in the homepage or careers HTML.
- This is not a legal registry check, a sanctions screen, or a guarantee of corporate identity.
- Polite fetching: an identified User-Agent, per-host delay, short timeouts, and one retry on 429 and 5xx. The contact address in the User-Agent is plusonecares@gmail.com.
- Page text is treated as untrusted. Snippets are short plain text. Emails are stripped. Nothing on the page is executed or followed as an instruction.
- Do not use the output to email people. This Actor does not collect personal contact data. Outreach still has to follow the rules that apply to you.

Source pages remain the property of their publishers. The Actor records short factual excerpts so you can audit a claim. It does not grant a licence to republish those sites.

### Run locally

Python 3.12.

```bash
pip install -r requirements-dev.txt
pytest
RUN_LIVE=1 pytest tests/test_live_smoke.py
python -m src
```

`python -m src` reads Apify local storage input, or `INPUT.json` in the working directory. With neither, the demo list is used. Local runs set `ACTOR_TEST_PAY_PER_EVENT` so charge calls are logged and not billed.

On 23 October 2026 the default eight companies resolved 8/8 in about 7 seconds, with no search key: precision 1.00 and coverage 1.00 against those known domains. The fixture eval in `tests/` (11 companies that should resolve, plus parked, article, directory, wrong-brand, ambiguous, and blocked cases) also scored precision 1.00 and coverage 1.00.

### FAQ

**Why was a row free?** Confidence was under the threshold, the page was blocked or ambiguous, or the row failed the fill-rate check. `reason` says which. `rowsDropped` in `OUTPUT` counts rows that looked resolved but failed validation.

**Do I need a search key?** No. You will get `coverage: partial` and domain guesses only. Brave and Serper are optional bring-your-own-key providers.

**Do you scrape LinkedIn?** No. The company URL is copied from the site you resolved. The LinkedIn page is not requested.

**Can I raise the batch size?** Yes. Set `maxCompanies` and `maxChargeUsd`. The run stops cleanly at the cap.

**What if two companies share a name?** You get `low_confidence` and both domains in `evidence`, not a silent pick.

Questions: plusonecares@gmail.com.

# Actor input Schema

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

Company names, domains, or URLs, one per item. Names like Stripe and domains like stripe.com are both accepted. Leave the default to run the public demo set.

## `companiesText` (type: `string`):

Optional extra companies, one per line. A line may be a name, a domain, a URL, or name plus domain separated by a comma. Merged with Companies and de-duplicated.

## `countryHint` (type: `string`):

Optional country name or ISO code. Used only to disambiguate. When it is set (and is not the United States), a page that does not mention the country and is not on that country's TLD is kept below the default charge threshold.

## `cityHint` (type: `string`):

Optional city. Used only to disambiguate. If the city is missing from the title, footer, and schema.org address, confidence is capped below the default threshold.

## `confidenceThreshold` (type: `number`):

Charge company-resolved only when calibrated confidence is at least this value (0 to 1). Default 0.8.

## `searchProvider` (type: `string`):

Optional web search API used to discover candidate sites. Without a key, candidates come only from the domain or URL you supplied and from conservative domain guesses. This Actor does not call other Apify Actors.

## `searchApiKey` (type: `string`):

Bring-your-own key for the selected search provider. Brave uses the X-Subscription-Token header. Serper uses the X-API-KEY header. Leave empty to skip search. Stored encrypted.

## `maxCompanies` (type: `integer`):

Stop after this many unique companies. The default keeps a demo run small.

## `outputMode` (type: `string`):

All companies returns every input. Alert only when changed returns a row only when the resolved identity differs from the previous successful run, or when the company is seen for the first time.

## `maxChargeUsd` (type: `number`):

Actor-side cap using the suggested 0.02 USD unit price. The run also stops when the platform reports the max total charge. 0 disables this extra cap. Unbilled rows do not count.

## Actor input object example

```json
{
  "companies": [
    "Stripe",
    "https://www.shopify.com"
  ],
  "companiesText": "Notion\nHubSpot, hubspot.com",
  "countryHint": "Germany",
  "cityHint": "Berlin",
  "confidenceThreshold": 0.8,
  "searchProvider": "none",
  "maxCompanies": 25,
  "outputMode": "all",
  "maxChargeUsd": 5
}
```

# Actor output Schema

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

Dataset items with the official website, linked profiles, confidence, and evidence.

## `summary` (type: `string`):

Counts, rows charged, rows dropped, fill rate, coverage, and error categories.

# 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": [
        "Stripe",
        "Notion",
        "Figma",
        "Vercel",
        "Shopify",
        "Atlassian",
        "HubSpot",
        "Cloudflare"
    ],
    "companiesText": "",
    "confidenceThreshold": 0.8,
    "searchProvider": "none",
    "maxCompanies": 25,
    "outputMode": "all",
    "maxChargeUsd": 5
};

// Run the Actor and wait for it to finish
const run = await client.actor("plusonets/company-web-presence-resolver").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": [
        "Stripe",
        "Notion",
        "Figma",
        "Vercel",
        "Shopify",
        "Atlassian",
        "HubSpot",
        "Cloudflare",
    ],
    "companiesText": "",
    "confidenceThreshold": 0.8,
    "searchProvider": "none",
    "maxCompanies": 25,
    "outputMode": "all",
    "maxChargeUsd": 5,
}

# Run the Actor and wait for it to finish
run = client.actor("plusonets/company-web-presence-resolver").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": [
    "Stripe",
    "Notion",
    "Figma",
    "Vercel",
    "Shopify",
    "Atlassian",
    "HubSpot",
    "Cloudflare"
  ],
  "companiesText": "",
  "confidenceThreshold": 0.8,
  "searchProvider": "none",
  "maxCompanies": 25,
  "outputMode": "all",
  "maxChargeUsd": 5
}' |
apify call plusonets/company-web-presence-resolver --silent --output-dataset

```

## MCP server setup

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

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/eTZWGcAgl8jbeuAdN/builds/eOecUPRtgxBx4XbGa/openapi.json
