# Person to Business Enrichment - Find Someone's Company (`leadproof/person-business-enrichment`) Actor

Private preview of PR #63, built from branch claude/person-business-enrichment-uedcyd. Not listed, not priced, not for customers.

- **URL**: https://apify.com/leadproof/person-business-enrichment.md
- **Developed by:** [Lead Proof](https://apify.com/leadproof) (community)
- **Categories:** Lead generation, Automation
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

$30.00 / 1,000 person matcheds

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

## Person to Business Enrichment

Start from a person's name and whatever context you have. Get back the business
or businesses they are professionally associated with, with the evidence that
links them: the page that states it, the quote, and when it was read.

### Why it exists

You have a list of people and no companies: a conference attendee list, a
registration export, a membership roll. Finding each person's practice or
employer by hand means searching, opening pages and deciding whether the
"Jane Smith" on a clinic's team page is your Jane Smith. This Actor does the
searching and reading, and it is strict about the deciding: it reports a
business only when a source states the link and the person is identified on
that source. Everything else comes back as a candidate, with the reason it was
not promoted.

It is not limited to one profession. For US healthcare providers it also
consults the free national provider registry, and skips it for everybody else.

### How to use it

1. Add people: a full name each, plus whatever you know - city, region,
   country, profession, credentials, or the business website if you have it.
2. Leave the defaults on for a first run: web search plus reading the pages
   it finds.
3. Set the run's maximum charge in the run options. The Actor stops starting
   people once another match could not be paid for.
4. Run, then read `outcome` first: `matched` rows carry businesses, `candidate`
   rows carry what was found and why it was not enough.

### Input

Only `fullName` is required.

```json
{
  "people": [
    {
      "inputId": "row-001",
      "fullName": "Tim Cook",
      "profession": "Executive Chair",
      "city": "Cupertino",
      "region": "CA",
      "country": "US",
      "website": "https://www.apple.com/leadership/tim-cook/"
    }
  ]
}
```

- `inputId` is returned unchanged and keeps two people with the same name
  apart. Rows are numbered by position if you leave it out.
- An address you supply is kept as given and never treated as a workplace: a
  badge address is often a home, an old office or shared.
- Any field the schema does not know is preserved and returned with the row.
- Optional sources, both off by default: `useGoogleMaps` (Maps listings as
  candidates) and `verifyEmails` (deliverability of the emails found).

### Output

One row per input person, in input order.

| Field | What it holds |
|---|---|
| `inputId`, `person` | Your input as sent, and how the name was parsed |
| `outcome` | `matched`, `candidate`, `not_found` or `error` (see below) |
| `identity` | Whether the person was identified, and on what: a profession or credential stated on the page, a registry number, a website you supplied |
| `businesses[].business` | Name, website, phones, emails, addresses, category, and what the business does in its own published words |
| `businesses[].relationship` | `rawTitle`: the role exactly as the source wrote it, or empty when the source states the relationship without one. `type` (employment, ownership, partnership, other) and `normalizedTitle` are our classification, kept apart from it. Current, former or unknown, with the reason; dates only when a source printed them |
| `businesses[].linkEvidence` | The URL, the supporting quote or structured-data path, when it was read, and the source's own date if it has one |
| `candidates[]` | Businesses that did not meet the bar, each with `notPromotedBecause` |
| `sourceAttempts[]` | Every source tried: `ok`, `empty`, `blocked`, `timeout`, `parse_failed`, `undetermined`, `error` or `skipped` |
| `warnings`, `completeness` | What could not be established, and which fields are missing |
| `costs` | What this person reserved for other Actors, what those Actors are expected to charge, and whether the figures were complete when read |

Every contact carries a scope: `business_general`, `branch`,
`person_professional` or `unknown`. A practice's main line and a named
practitioner's direct line are different things and are returned as such.

A run summary is written to the key-value store as `SUMMARY`, and a flat CSV
view is available from the dataset.

#### Outcomes

| Outcome | Meaning | What it is not |
|---|---|---|
| `matched` | A source states the link and the person was identified on that source. | Not a guarantee: read `identity` and the evidence. |
| `candidate` | Plausible businesses were found and none met the bar. | Not a weak match. A candidate's contacts belong to the candidate business. |
| `not_found` | The sources answered and had nothing. | |
| `error` | The Actor could not look properly: sources failed, or the run stopped first. | Never a statement that the person has no business. |

#### How a business gets promoted

1. **The person is identified on that source.** An exact name plus a city is
   not enough. Something independent has to agree, and a value that came from
   the search query itself corroborates nothing.
2. **A source states the relationship with that business.** Structured
   data linking the two; a sentence connecting them ("Jane Smith, DDS is an
   associate dentist at Riverside Dental Group", "Riverside Dental Group
   welcomes Dr. Jane Smith", "Dr. Smith and our team at Riverside Dental
   Group"); or the business's own roster listing the person under a team
   heading. A page that only mentions the person does not count, nor does
   being on the business's domain, a search snippet, a Maps listing, a shared
   address or a business named after the person's surname.
3. **No rival.** If several distinct people fit the same evidence, nothing is
   promoted. Two people can share a name exactly; the row says so instead of
   choosing one.

### Pricing

Pay per event:

| Event | Price | When |
|---|---|---|
| `person-matched` | $0.03 | Once for each input person with at least one matched business. Several businesses on one person's row are one event. |

Candidate, not-found and error rows are free.

This Actor also runs other LeadProof Actors on your account, and they bill you
separately at their own published prices:

| Actor | When | Its price |
|---|---|---|
| [Web Search](https://apify.com/leadproof/web-search) | Always, unless you turn web search off | $0.005 per results page, usually one per person |
| [Google Maps Scraper](https://apify.com/leadproof/google-maps-scraper) | Only with `useGoogleMaps` | Per place delivered, up to three per person |
| [Bulk Email Verifier](https://apify.com/leadproof/bulk-email-verifier) | Only with `verifyEmails` | Per address checked |

Two limits keep a run inside what you intended:

- **The run's maximum charge** (a run option) bounds this Actor's own events.
  A person is started only while their match could still be charged, so the
  limit stops work before it is done rather than after.
- **`maxTotalCostUsd`** bounds what the run may commit the other Actors to.
  Each one's allowance is reserved before it starts and passed to the platform
  as that Actor's own spending limit, and it is not freed during the run.

### Reliability

- **Resume is safe.** Rows already delivered are not recomputed or charged
  again, and another Actor started for a person is re-attached rather than
  started twice. If a start was interrupted before its run ID was recorded, it
  is not started again: the row says so.
- **Shared businesses are read once.** Several people at one practice share
  its page reads; each keeps their own relationship to it.
- **One bad row does not end the batch.** It becomes an `error` row and the
  rest continue.
- **Every URL is checked** before it is fetched, and again at every redirect.

### Limits

- People with common names and no profession or credential usually come back
  as candidates. That is the honest answer, not a failure.
- Well-known people are written about everywhere. A page that writes about
  someone without stating their relationship with its own publisher - an
  encyclopedia entry, a news article, a social post, an honours page - is a
  candidate, never a match, so a famous name with no company page usually
  comes back as a candidate. Supply the company page to confirm it.
- A company page on a separate newsroom site names itself after the
  newsroom, and says nothing about the person's relationship with the
  newsroom, so it stays a candidate.
- A blocked or unreadable page is recorded as such, never as "nothing there".
- A public source can be stale. Retrieval time is always recorded; a source's
  own date only when it publishes one, and its absence means unknown recency.
- Emails are the ones a business published. None are guessed, and a general
  mailbox is never presented as a person's own.
- LinkedIn profiles are not read. When web search surfaces a profile address,
  it is listed as an address and never used as evidence of a job.

### Fair use

This Actor reads public, professional information: business websites, public
search results and, for US healthcare providers, the public provider registry.
It logs into nothing, solves no challenges and bypasses no access controls. You
are responsible for having a lawful basis to process the people you enter and
for how you contact them.

### Related

- [Business Enrichment](https://apify.com/leadproof/business-enrichment) -
  start from a business instead of a person.
- [Website Email Finder](https://apify.com/leadproof/website-email-finder) -
  published emails from a list of websites.
- Built by [LeadProof](https://leadproof.co).

# Actor input Schema

## `people` (type: `array`):

One object per person. Only fullName is required; a plain name string works too. Optional fields: inputId (kept as given, so two people with the same name never merge), address, city, region, country, profession, credentials, website, organizationHint. A supplied website is read as a strong lead. An address is treated as the person's own address, never as their workplace. Any other field you send, a LinkedIn URL included, is preserved and returned unchanged, not read.

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

Used for any person whose record does not carry a country. Affects phone normalization and which registries apply.

## `useWebSearch` (type: `boolean`):

Search each person's name with their profession and location, then read the pages that come back. This is the main discovery path for people outside a registry.

## `useGoogleMaps` (type: `boolean`):

Off by default. Also looks the person up on Google Maps. Maps never states who works at a business, so its listings are returned only as candidates, and only when a listing carries the person's family name or sits at their registered practice. Runs LeadProof Google Maps Scraper, billed separately at its own price.

## `useNpiRegistry` (type: `boolean`):

Free official register. Applies only to enrolled US healthcare providers, and is skipped silently for everybody else. It identifies a person by number, which is the strongest identity signal available; it does not name an employer.

## `readBusinessWebsites` (type: `boolean`):

Open the business's own site for contact details, locations and a description of what it does. Turning this off leaves most rows as candidates.

## `verifyEmails` (type: `boolean`):

Off by default. Checks whether the email addresses found for a matched business would accept mail. A deliverable mailbox is not evidence that it belongs to the person or that they work there, and a verdict never changes an email's scope. Runs LeadProof Bulk Email Verifier, billed separately at its own price per address.

## `maxBusinessesPerPerson` (type: `integer`):

A person may own one business, work at another and have a former role at a third. Associations beyond this cap move to the candidates array with a reason; they are not dropped. Hard limit applied to API and Console input.

## `maxCandidatesPerPerson` (type: `integer`):

Cap on the uncertain businesses kept for review. Hard limit applied to API and Console input.

## `maxPeople` (type: `integer`):

People beyond this limit are not processed and the count is reported. Hard limit applied to API and Console input.

## `maxSearchesPerPerson` (type: `integer`):

Search queries per person before discovery stops. Hard limit applied to API and Console input.

## `maxPagesPerPerson` (type: `integer`):

Web pages opened per person. Pages shared by several people are fetched once and counted once. Hard limit applied to API and Console input.

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

Hard limit applied to API and Console input.

## `maxRunSeconds` (type: `integer`):

The run stops starting new work at this point, pushes what it has and says it stopped early. Hard limit applied to API and Console input.

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

Hard limit applied to API and Console input.

## `maxTotalCostUsd` (type: `string`):

Ceiling on what this run may commit its child Actors to. Each child's allowance is reserved before it starts and passed to the platform as that child's own limit; reserved money is not freed during the run, so the ceiling holds even though a child's final bill is known only later. This Actor's own charges are capped separately by the run's maximum charge. 0 means no ceiling.

## `maxCostPerPersonUsd` (type: `string`):

Stops one difficult record reserving too much of the batch. Counts reservations, like the run ceiling. 0 means no per-person ceiling.

## Actor input object example

```json
{
  "people": [
    {
      "inputId": "row-001",
      "fullName": "Tim Cook",
      "profession": "Executive Chair",
      "city": "Cupertino",
      "region": "CA",
      "country": "US",
      "website": "https://www.apple.com/leadership/tim-cook/"
    }
  ],
  "country": "",
  "useWebSearch": true,
  "useGoogleMaps": false,
  "useNpiRegistry": true,
  "readBusinessWebsites": true,
  "verifyEmails": false,
  "maxBusinessesPerPerson": 3,
  "maxCandidatesPerPerson": 5,
  "maxPeople": 20,
  "maxSearchesPerPerson": 3,
  "maxPagesPerPerson": 6,
  "concurrency": 2,
  "maxRunSeconds": 900,
  "requestTimeoutSeconds": 20,
  "maxTotalCostUsd": "1.0",
  "maxCostPerPersonUsd": "0.08"
}
```

# Actor output Schema

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

Every person's row as JSON: outcome, businesses with evidence, candidates and source attempts.

## `csv` (type: `string`):

One flat line per person, for spreadsheets.

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

Outcome counts, stop reason, the charged events and the cost picture: reservations, expected charges of other Actors, platform usage.

# 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 = {
    "people": [
        {
            "inputId": "row-001",
            "fullName": "Tim Cook",
            "profession": "Executive Chair",
            "city": "Cupertino",
            "region": "CA",
            "country": "US",
            "website": "https://www.apple.com/leadership/tim-cook/"
        }
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("leadproof/person-business-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 = { "people": [{
            "inputId": "row-001",
            "fullName": "Tim Cook",
            "profession": "Executive Chair",
            "city": "Cupertino",
            "region": "CA",
            "country": "US",
            "website": "https://www.apple.com/leadership/tim-cook/",
        }] }

# Run the Actor and wait for it to finish
run = client.actor("leadproof/person-business-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 '{
  "people": [
    {
      "inputId": "row-001",
      "fullName": "Tim Cook",
      "profession": "Executive Chair",
      "city": "Cupertino",
      "region": "CA",
      "country": "US",
      "website": "https://www.apple.com/leadership/tim-cook/"
    }
  ]
}' |
apify call leadproof/person-business-enrichment --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,leadproof/person-business-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/XDNhX0euZM9brOpSV/builds/cSWYnffpkV32mmmVS/openapi.json
