# Reverse Email Lookup | Email to LinkedIn Profile (`b2bdata/person-by-email`) Actor

Reverse email lookup to LinkedIn: give email addresses, get one person row per address with name, title, company, location and profile URL. Free plan: 100 emails per run. Pay per person delivered, no match free. Does not verify or find emails. No login or cookies.

- **URL**: https://apify.com/b2bdata/person-by-email.md
- **Developed by:** [Open Enrich](https://apify.com/b2bdata) (community)
- **Categories:** Lead generation, Marketing
- **Features:** Limited permissions
- **Stats:** 3 total users, 2 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $7.70 / 1,000 person delivereds

This Actor is paid per event and usage. You are charged both the fixed price for specific events and for Apify platform usage.
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

## Reverse Email Lookup | Email to LinkedIn Profile

Reverse email lookup to LinkedIn: give a list of email addresses, get one row per address that matches a person, with name, title, current company, location and LinkedIn profile URL. No login, no cookies, no start fee. You pay only for a person row you receive: an address with nobody on file is free.

**No login | Bulk list in one run | One person per email | Misses and ambiguous matches are free | JSON/CSV export | Works via API, MCP and schedules**

> **Email in, person out.** Many reverse-lookup tools take one address per run and bill the empty answers too. This one takes a list, looks each address up once and charges only for a person it can stand behind. When several people share an address and the best guess does not fit the address, the row is withheld and costs nothing. It does not verify that an address is deliverable and it does not find emails: for that use [email-verifier](https://apify.com/b2bdata/email-verifier) and [email-finder](https://apify.com/b2bdata/email-finder).

[See all actors by b2bdata](https://apify.com/b2bdata)

***

### 💳 Pricing and plans

One event, `person`, charged once per person row delivered. Prices per 1,000 people (set on the Pricing tab):

| Event | When it is charged | Free plan | Gold plan |
|---|---|---|---|
| `person` | A person row is delivered to your dataset | $9.996 | $7.696 |

Bronze and Silver prices are on the Pricing tab. Higher Apify plans only lower the price. Empty runs, emails with nobody on file, ambiguous matches, entries that are not emails, failed lookups and notices are free, and there is no start fee.

**Plan-credit math.** The free plan's $5 monthly credit covers about 500 people at the Free price.

#### Success-only billing

You are charged only for a person row you receive. An address with nobody on file gets one free notice row (`reason: "not_found"`). An address that matched several people, where the best candidate does not fit the address, gets one free notice row (`reason: "ambiguous_match"`). A match without a profile URL gets a free `no_profile_url` notice. An entry that is not an email, a repeat, or a lookup that fails is listed in `MISSES` with its position and a reason and costs nothing.

#### The free plan

Free Apify accounts get up to 100 emails looked up per run, and as many runs as their Apify credit covers. Nothing is stored on our side. When a list is longer, the first 100 valid emails are looked up and one free notice row (`reason: "free_plan_limit"`) says how many were not looked up; they are not charged. Run again with the rest, or use a paid Apify plan for no per-run limit.

***

### ⚡ Why this reverse email lookup actor?

- **A list, not one address.** Up to 3,000 addresses per run, de-duplicated, each looked up once.
- **Paste addresses as you have them.** `jane@stripe.com`, `Jane Doe <jane@stripe.com>` and `mailto:jane@stripe.com` all become `jane@stripe.com`.
- **Withholds guesses it cannot back.** If an address belongs to several profiles, a row is delivered and charged only when the address domain names the person's current employer and the part before the @ fits the name. Everything else is a free notice.
- **Flat, readable rows.** Name, title, company, location, years in role, profile URL. No nested raw profile.
- **Success-only, no start fee.** No match, bad entries, repeats and notices are free.

***

### 🎯 Use cases: find the person behind an email

- **CRM clean-up.** Put a name, title and company on contacts you only hold an address for.
- **Inbound lead enrichment.** A form fill or a newsletter signup becomes a profile you can research.
- **Outreach research.** Check who an address belongs to before you write.
- **Agent workflows.** An AI agent holds an address and needs the person and the profile in a single call.

***

### 🚀 Quick start: reverse email lookup

#### 1. One address

```json
{ "items": ["jane@stripe.com"], "limit": 1 }
```

#### 2. A list

```json
{ "items": ["jane@stripe.com", "Bob Smith <bob@acme.io>", "mailto:zed@foo.org"], "limit": 100 }
```

Each entry becomes a bare lower-case address before the lookup.

***

### ⚙️ Inputs

| Key | Type | Default | What it does |
|---|---|---|---|
| `items` | array of strings | required | Email addresses. A name, a domain or a profile URL is not an email and is listed in `MISSES` as `invalid_email`. The same address written twice is looked up once. Maximum 3,000. Free plan: 100 emails per run. |
| `limit` | integer | 100 | Most people delivered in the whole run, which also caps what is charged. Maximum 3,000. Emails not reached are listed in `MISSES`. |
| `includeAudit` | boolean | false | Adds an `audit` object to each row. |

Any other field stops the run before a lookup with `remove unsupported fields: ...`. Nothing is charged.

***

### 📤 Output

One result row per matched email, in input order. Empty values are left out.

| Key | Type | Meaning, and when it is missing |
|---|---|---|
| `email` | string | The address you gave, cleaned |
| `fullName`, `firstName`, `lastName` | string | The person's name |
| `headline` | string | The profile headline. Missing when the person has none |
| `jobTitle` | string | Current job title. Missing when the profile lists none |
| `company` | string | Current employer, from the profile's open position. Missing when none is listed |
| `location` | string | Place on the profile |
| `country` | string | Two-letter country code |
| `startYear` | integer | Year the current position started. Missing when not on file |
| `tenureYears` | number | Years in the current position as the data holds it. Missing when not on file |
| `totalExperienceYears` | number | Years of work experience as the data holds it. Missing when not on file |
| `connections` | integer | Connection count on the profile |
| `profileUrl` | string | Public LinkedIn profile URL. A row without one is never delivered |
| `slug` | string | Profile slug |
| `updatedAt` | string | When the profile was last refreshed in the data |
| `audit` | object | Only with `includeAudit`: `requestId`, `latencyMs`, `candidates`, `inputRaw` |

Example output (the shape is real, the values are illustrative):

```json
{
  "email": "ann@example.com",
  "fullName": "Ann Example",
  "firstName": "Ann",
  "lastName": "Example",
  "headline": "Engineering leader at Example",
  "jobTitle": "VP Engineering",
  "company": "Example",
  "location": "Dublin, Ireland",
  "country": "IE",
  "startYear": 2022,
  "tenureYears": 4,
  "totalExperienceYears": 15,
  "connections": 812,
  "profileUrl": "https://www.linkedin.com/in/ann-example",
  "slug": "ann-example",
  "updatedAt": "2026-03-19 16:19:30.035Z"
}
```

#### How to read the rows

- **Work addresses match more often than personal ones.** An address on a company domain (`ann@example.com`) resolves more often than a personal mailbox (`gmail.com`, `outlook.com`). Coverage of personal addresses is lower, and a personal address says little about who the person is, so check the name before you act on it.
- **One address, one person.** When the address belongs to a single profile, that profile is the row. We cannot prove that the person still uses the address.
- **Shared addresses.** Some addresses match several profiles (a common first name at a large company, a shared mailbox). The data then picks one. We deliver that pick only when the address domain names the person's current employer and the part before the @ fits the name. Otherwise you get a free `ambiguous_match` notice. `includeAudit` shows `candidates`, the number of people that shared the address. A delivered row can still be a namesake: for a short address like `pat@bigcompany.com`, confirm with the headline and title.
- **Company is the current open position.** A person with several open positions gets the one that started last.
- **No seniority level and no company domain.** The data returns the employer's name, not its domain, and no seniority bucket, so neither is in the row.

#### Other records in the run

- **`MISSES`** (key-value store): `[{index, input, reason}]` for every entry without a result row. Reasons: `not_found`, `ambiguous_match`, `no_profile_url`, `invalid_email`, `duplicate_input` (the same address as an earlier entry; `duplicateOf` names it), `not_looked_up_limit` (the run `limit` was reached first), `not_looked_up_error` (the service failed three times in a row) or `Upstream HTTP <code>`.
- **`errors-person-by-email`** (key-value store): the lookups that failed for a technical reason, so you can re-run only those emails.
- **`RUN_SUMMARY`**: counts (requested, valid, looked up, found, not found, ambiguous, delivered).
- **Notice rows** (`type: "notice"`) appear for an address with no person, an ambiguous match, a free-plan list longer than 100 (`free_plan_limit`), a reached `limit` (`limit_reached`), a reached spend limit (`charge_limit_reached`), repeated service failures (`upstream_errors`) or invalid input (`invalid-input`). Notices are never charged.

#### What is NOT returned

- Email addresses, phone numbers or deliverability checks (use [email-finder](https://apify.com/b2bdata/email-finder) and [email-verifier](https://apify.com/b2bdata/email-verifier))
- Work history, skills or education (use [linkedin-profile-by-url](https://apify.com/b2bdata/linkedin-profile-by-url))
- Gender, birth date or other private details

***

### 🛡️ Reliable results

**Our data promise.** We work to keep the data current, and we stand behind it. If a person has moved on, an address points at the wrong person or a row looks wrong, open the Issues tab with your run link. We review every report and sort it out with you, case by case.

- **No match is free and visible.** Every address without a row is in `MISSES` with its position and a reason and is never charged.
- **Repeats are looked up and charged once.** The same address written twice is one row.
- **Each row is saved as it is delivered.** A run that stops keeps what it already produced.
- **Bounded pace.** Lookups run at about 60 a minute, so a long list takes time. The run timeout is one hour.
- **Typos fail early.** An unsupported input field, a limit above its maximum or a list above 3,000 stops the run before any charge.

***

### 🤖 For AI agents (MCP)

This actor runs through the Apify MCP server. Prompt to copy:

> "Use b2bdata/person-by-email with items=\["jane@stripe.com"] and limit=1. Return fullName, jobTitle, company and profileUrl."

**When to use it.** You hold an email address and need the person behind it.
**When not to.** You need to know whether an address is deliverable (use b2bdata/email-verifier), want to find an address from a name (use b2bdata/email-finder) or need a full profile (use b2bdata/linkedin-profile-by-url).
**Input contract.** `items` array of email addresses (maximum 3,000); optional `limit` 1-3,000 (default 100) and `includeAudit`.
**Output contract.** One row per matched email with `email`, `fullName`, `jobTitle`, `company`, `profileUrl`. Output is deterministic and schema-stable. Addresses without a match are a free notice row and listed in `MISSES`.
**Smoke test.** `limit: 1` on a work address of a well-known company returns one row.
**Error cases.** `No valid email addresses` means no entry was an email. `remove unsupported fields`, a bad number or a list above 3,000 ends the run SUCCEEDED with one free `invalid-input` notice row and nothing charged.

***

### 🔌 Integrations: send results anywhere

1. **API.** Fetch rows from the run's default dataset: `GET /v2/datasets/{datasetId}/items?format=csv`.
2. **Webhook** on run success to push rows to your own endpoint.
3. **n8n, Make, Zapier.** Use the native Apify nodes: run the actor, then read the dataset.
4. **Google Sheets, Airtable, HubSpot, Salesforce, Clay.** Join on `email` and map `fullName`, `jobTitle`, `company` and `profileUrl` to contact fields.

***

### 🕐 Schedule examples

- **Nightly enrichment.** A schedule with a saved task that points at the day's new contacts.
- **Signup enrichment.** A webhook from your form tool starts a run with the new addresses.

***

### 🔗 Related actors

- `b2bdata/email-finder`: a work email for a person at a domain. [Open](https://apify.com/b2bdata/email-finder)
- `b2bdata/email-verifier`: whether an address is valid, catch-all or invalid. [Open](https://apify.com/b2bdata/email-verifier)
- `b2bdata/linkedin-profile-by-url`: the full profile for a LinkedIn URL. [Open](https://apify.com/b2bdata/linkedin-profile-by-url)
- `b2bdata/social-handles-finder`: social profiles from an email or LinkedIn URL. [Open](https://apify.com/b2bdata/social-handles-finder)

[See all actors by b2bdata](https://apify.com/b2bdata)

***

### ❓ FAQ

#### Data and coverage

**How do I find a LinkedIn profile from an email address?**
Put the address in `items` and run. You get one row with name, title, company, location and `profileUrl`.

**Does it work with personal email addresses?**
Sometimes. Personal mailboxes match less often than work addresses, and the person behind a personal address is harder to confirm. Work addresses on a company domain are the strong case.

**Why did I get no row for an address?**
It is in `MISSES` with a reason: nobody on file, an ambiguous match we did not stand behind, a match without a profile URL, or an entry that was not an email.

**Is the person always the right one?**
When an address resolves to a single profile, yes as far as the data goes. When several people share it, we deliver only a pick that fits the employer and the name, and `includeAudit` shows how many candidates there were. Confirm short or generic addresses with the title and headline.

**Do I get email addresses or phone numbers back?**
No. You get the person and the profile. Use `b2bdata/email-finder` to find an address.

**Do I need a LinkedIn account or cookies?**
No. It is a lookup without login, cookies or an account to ban.

#### Pricing

**What do I pay when an address has no match?**
Nothing. There is no start fee, and no match, bad entries, repeats and notices are free.

***

### 🛟 Support

Use the **Issues** tab on this actor's Apify page for bugs, questions and feature requests. We answer within 48 hours on working days. Please include the run ID and the input you used. Published on Apify by b2bdata. Not affiliated with, endorsed by, or sponsored by any company or person named in the examples above.

# Changelog

This Actor's version history is a separate document: https://apify.com/b2bdata/person-by-email/changelog.md

# Actor input Schema

## `items` (type: `array`):

Email addresses to look up. Example: \["jane@stripe.com", "Jane Doe <jane@stripe.com>", "mailto:jane@stripe.com"]. Addresses are lower-cased and de-duplicated; an entry that is not an email address is listed in the MISSES record as invalid_email. An address with nobody on file returns one free notice row. Maximum 3,000 per run. Free plan: 100 emails per run.

## `limit` (type: `integer`):

Most people delivered in the whole run, which also caps what is charged. Emails not reached are listed in MISSES and are not charged. Default 100, maximum 3000.

## `includeAudit` (type: `boolean`):

Adds requestId, latencyMs, candidates and inputRaw to each row under audit. candidates is how many people shared the address (0 = one match). Omit for clean rows.

## Actor input object example

```json
{
  "items": [
    "jane@stripe.com"
  ],
  "limit": 100,
  "includeAudit": false
}
```

# Actor output Schema

## `people` (type: `string`):

One row per matched email: name, title, company, location and profile URL

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

Counts of emails looked up, matched, not found and not delivered for this run

# 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 = {
    "items": [
        "jane@stripe.com"
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("b2bdata/person-by-email").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 = { "items": ["jane@stripe.com"] }

# Run the Actor and wait for it to finish
run = client.actor("b2bdata/person-by-email").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 '{
  "items": [
    "jane@stripe.com"
  ]
}' |
apify call b2bdata/person-by-email --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,b2bdata/person-by-email"
        }
    }
}
```

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/hddodTUuHbOa5L0nb/builds/5QrT6yrpJR4337iok/openapi.json
