# CSV Contact Enrichment - Fill Contact Data in Any Columns (`b2bdata/contact-enrich-csv`) Actor

Enrich a CSV or list of rows with work emails, phones, job title and company. Any column names: map them once, every original column comes back unchanged with new columns added. Pay per enriched contact, misses free. Free plan: 100 rows per run. Does not verify mailboxes. No login or cookies.

- **URL**: https://apify.com/b2bdata/contact-enrich-csv.md
- **Developed by:** [Open Enrich](https://apify.com/b2bdata) (community)
- **Categories:** Lead generation, Automation, Integrations
- **Features:** Limited permissions
- **Stats:** 1 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $3.00 / 1,000 contact enricheds

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

## CSV Contact Enrichment - Fill Contact Data in Any Columns

CSV contact enrichment for any spreadsheet: give rows with any columns, name the ones that hold a LinkedIn URL, email, handle or name plus company domain, and get every row back unchanged with work email, phone, job title and company added. No login, no cookies, no start fee. You pay only for the contacts you receive.

**Any column names | Your columns kept as they are | Email, phone, title, company per row | Up to 1,000 rows per run | Misses are free | JSON/CSV export**

> **Not an email verifier.** Each email comes with a status (`valid`, `catch_all`, `unknown`), and `unknown` is common because no mailbox check is run. Check the list yourself before a campaign.

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

***

### Pricing and plans

Three events, each charged only for a value you receive. Prices per 1,000 (set on the Pricing tab):

| Event | When it is charged | Free plan | Gold plan |
|---|---|---|---|
| `contact` | A row resolves to a person and is delivered with at least one field you asked for | $3.963 | $2.996 |
| `email-found` | A work email on your company domain is found and added | $0.963 | $0.696 |
| `phone-found` | A phone number is delivered in the row | $39.963 | $29.963 |

Bronze and Silver prices are on the Pricing tab. Higher Apify plans only lower the price. Empty runs, rows that are not found, failed lookups, unreadable rows and notices are free, and there is no start fee. You choose with `fields` what is looked up, so you are never charged for a phone you did not ask for.

**Plan-credit math.** The free plan's $5 monthly credit covers about 1,260 `contact` events, or about 5,190 `email-found` events, or about 125 `phone-found` events, all at the Free price.

#### The free plan

Free Apify accounts get up to 100 rows 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 are looked up and one free notice row (`reason: "free_plan_limit"`) says how many rows were not looked up; they are not charged. Run again with the rest, or use a paid Apify plan for no per-run limit.

#### Keep a run small while you test

`limit` caps the rows looked up. The default is 25, the maximum is 1,000, and `0` means all rows up to 1,000. Try 2 to 3 rows first, read the output, then scale up.

***

### Quick start: enrich a CSV with email and phone

Turn your CSV into a list of row objects (any CSV-to-JSON tool does it; the header row becomes the keys) and pass it as `items`.

#### 1. Two rows, auto-detected columns

```json
{
  "items": [
    { "ID": 1, "Name": "Patrick Collison", "Company Website": "stripe.com", "Notes": "met at a conference" },
    { "ID": 2, "LinkedIn URL": "https://www.linkedin.com/in/satyanadella", "Company Website": "microsoft.com" }
  ]
}
```

#### 2. Odd column names, with a column map

```json
{
  "items": [{ "Contact": "Patrick Collison", "Corporate Site": "stripe.com", "Owner": "Sam" }],
  "columnMap": { "Contact": "fullName", "Corporate Site": "domain" },
  "fields": ["email", "jobTitle", "company"]
}
```

***

### Bulk contact enrichment: inputs

| Key | Type | Default | What it does |
|---|---|---|---|
| `items` | array of objects | required | One object per CSV row, any column names. Each row needs one strong key: a LinkedIn profile URL, an email, a GitHub, Twitter/X or Facebook handle, or a full name plus a company domain. Rows without one are listed in `MISSES`. |
| `columnMap` | object | auto-detect only | `{ "your column": "field" }`. Fields: `linkedin`, `email`, `github`, `twitter`, `facebook`, `fullName`, `firstName`, `lastName`, `domain`. Obvious headers (LinkedIn, Email, Name, First Name, Last Name, Domain, Website) are found without it. |
| `fields` | array | `["email", "phone"]` | What to add: `email`, `phone`, `jobTitle`, `company`. Only the picked fields are looked up and charged. |
| `limit` | integer | 25 | Rows looked up. `0` = all rows, up to 1,000. A higher number stops the run with a message before any charge. |
| `includeAudit` | boolean | false | Adds an `audit` object to each row. |

Any other field stops the run before a lookup with `remove unsupported fields: ...`. An unknown `columnMap` field or `fields` value does the same. Nothing is charged.

**Which key wins.** When a row has several, the order is LinkedIn URL, email, GitHub, Twitter/X, Facebook, then name plus domain. **Emails** are looked up from the person's name and the company domain in the row, so add a domain column (`Website`, `Domain`, `Company Domain`) when you want `email`. A row that already has an email keeps it and is not charged for one.

***

### Enrich CSV with emails and phones: output

One row per resolved contact, in input order. Your original columns come first, unchanged. Values we add:

| Key | Type | Meaning, and when it is missing |
|---|---|---|
| (your columns) | any | Every column you sent, same names, same order, same values |
| `profileUrl` | string | Professional profile link of the matched person |
| `fullName` | string | Matched name. Skipped when your row already holds the same name |
| `email` | string | Work email on your domain, lower case. Missing when none was found or no domain was given |
| `emailStatus` | string | `valid`, `catch_all` or `unknown`. Rows with a known-invalid address get no email |
| `confidence` | number | 0-1 from the status: `valid` 0.95, `catch_all` 0.6, `unknown` 0.5 |
| `phone` | string | First phone on file, international format when the source gives one. Missing when none is on file |
| `jobTitle`, `company` | string | Current role and employer on the profile. Missing when not on file |
| `audit` | object | Only with `includeAudit` |

**If a column already exists.** An empty cell with the same header (case and spaces ignored) is filled, so an empty `Email` column gets the email. A non-empty cell is never overwritten: our value goes to a new `<name>Enriched` column (for example `jobTitleEnriched`), and an identical value is not repeated.

```json
{
  "ID": 2,
  "Full Name": "Patrick Collison",
  "Company Website": "stripe.com",
  "Notes": "met at a conference",
  "profileUrl": "https://www.linkedin.com/in/patrickcollison",
  "jobTitle": "Cofounder",
  "company": "Arc Institute",
  "email": "patrick.collison@stripe.com",
  "emailStatus": "unknown",
  "confidence": 0.5
}
```

*A real row from a test run with `fields` set to email, phone, jobTitle and company. No phone was on file for this person, so there is no `phone` and no `phone-found` charge. The job title and employer come from the profile and can differ from the domain you gave.*

#### Other records in the run

- **`MISSES`** (key-value store): `[{row, input, reason}]` for every row that produced no result. `row` is the 1-based position in your list. Reasons: `no_strong_key`, `not_found`, `invalid`, `nothing_found` (resolved, but none of the fields you asked for was found), `Upstream HTTP <code>`.
- **`errors-contact-enrich-csv`**: lookups that failed for a technical reason, so you can run only those rows again.
- **`RUN_SUMMARY`**: counts, requested fields, stop reason, free-plan status.
- **Notice rows** (`type: "notice"`) are free: `free_plan_limit` (list longer than 100 on a free plan), `charge_limit_reached` (the run's charge limit was hit), `invalid-input`.

#### What is NOT returned

- Personal email addresses
- Several alternative addresses per person
- A live mailbox check of the address
- Matches by phone number, street address or company name alone

***

### Reliable results

**Our data promise.** We work to keep every contact current, and we stand behind it. If a record is outdated, an email bounces or a person has moved on, open the Issues tab with your run link. We review every report and sort it out with you, case by case.

- **Misses are free and visible.** Every row without a result is in `MISSES` with its row number and a reason.
- **Your data stays yours.** Original columns are returned as they came in; nothing is dropped, renamed or reordered.
- **Only addresses on your domain.** An email on another domain is never returned as the result.
- **Each row is saved as it is delivered.** A run that stops keeps what it already produced.
- **Typos fail early.** An unsupported input field or a limit above 1,000 stops the run before any charge, with one free notice row.
- **Check before you send.** `emailStatus` and `confidence` are signals, not a guarantee.

***

### Use with AI agents (MCP)

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

> "Use b2bdata/contact-enrich-csv with items=\[{"Name":"Patrick Collison","Company Website":"stripe.com"}], fields=\["email","jobTitle"] and limit=1. Return email, emailStatus and jobTitle."

**When to use it.** You hold rows with a name, a LinkedIn URL, an email or a handle, and want email, phone, title or company added without losing your columns.
**When not to.** You hold only company domains (use a domain actor), or you only need to verify emails you already have.
**Input contract.** `items` array of row objects; optional `columnMap`, `fields`, `limit` 0-1,000, `includeAudit`.
**Output contract.** One row per resolved contact: your columns, then `profileUrl`, `fullName`, and the fields you asked for. Deterministic and schema-stable, rows keep input order. Unresolved rows are absent from the dataset and listed in `MISSES`.
**Smoke test.** `limit: 1` on a well-known person returns one row.

***

### Integrations

1. **API.** `GET /v2/datasets/{datasetId}/items?format=csv` returns the enriched rows as a CSV, your columns first.
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.** Map `email`, `phone` and `emailStatus` back to your fields.

***

### Related actors

- `b2bdata/email-finder`: you only need a work email from a name and a company domain. [Open](https://apify.com/b2bdata/email-finder)
- `b2bdata/bulk-linkedin-profile-enrichment`: you hold LinkedIn URLs and want the full profile row. [Open](https://apify.com/b2bdata/bulk-linkedin-profile-enrichment)
- `b2bdata/phone-number-finder`: you hold LinkedIn URLs and want only people who have a phone. [Open](https://apify.com/b2bdata/phone-number-finder)
- `b2bdata/email-phone-finder`: you have an email and need a phone. [Open](https://apify.com/b2bdata/email-phone-finder)

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

***

### FAQ

**How do I enrich my CSV with email and phone?**
Convert the CSV to a list of row objects, pass it as `items`, and leave `fields` at its default (email and phone). Each resolved row comes back with your columns plus the new ones.

**Can I enrich a CRM export with work emails if my column names are unusual?**
Yes. Add a `columnMap` such as `{"Contact": "fullName", "Corporate Site": "domain"}`. Obvious headers are detected without it.

**Is this a spreadsheet email finder for any columns?**
Yes. Any number of extra columns is allowed and all of them come back unchanged.

**Can I enrich a lead list with names and company?**
Yes, with a full name and a company domain per row. A single name with no domain cannot be matched and is listed in `MISSES` for free.

**Can I use it as a CSV enrichment API for AI agents?**
Yes. Call it through the Apify API or MCP with `limit: 1`. The output is stable and one row per resolved contact.

**What do I pay when nobody is found?**
Nothing. There is no start fee, and misses, failed lookups and notices are free. A row that resolves but has none of the fields you asked for is also 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 a row that shows the problem. Published on Apify by b2bdata as proprietary software. 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/contact-enrich-csv/changelog.md

# Actor input Schema

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

Step 1. A list of row objects, one per CSV row, with any columns you like. Each row needs at least one strong key: a LinkedIn profile URL, an email address, a GitHub, Twitter/X or Facebook handle, or a full name plus a company domain. Rows without one are listed in MISSES and are free. Convert a CSV to this list with any CSV-to-JSON tool or your own script. Example: \[{"Name": "Patrick Collison", "Company Website": "stripe.com"}].

## `columnMap` (type: `object`):

Step 2, optional. Tells the actor what each of your columns holds: {"your column name": "field"}. Fields: linkedin, email, github, twitter, facebook, fullName, firstName, lastName, domain. Obvious names (LinkedIn, Email, Name, First Name, Last Name, Domain, Website) are detected without this. Omit to auto-detect only. An unknown field name stops the run before any charge.

## `fields` (type: `array`):

Step 3. Which values to add: email (needs a name and a company domain), phone, jobTitle, company. Only the fields you pick are looked up and charged. Omit for email and phone.

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

Step 4. How many rows from the list are looked up. 0 means all of them, up to 1,000 per run. A value above 1,000 stops the run with a message; split the list. Omit for 25. Free Apify plans look up up to 100 rows per run.

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

Adds requestId, batchUnitsCharged, latencyMs, inputCanonical and emailUnits to each row under audit. Omit for clean rows.

## Actor input object example

```json
{
  "items": [
    {
      "Name": "Patrick Collison",
      "Company Website": "stripe.com",
      "Notes": "CEO"
    },
    {
      "LinkedIn URL": "https://www.linkedin.com/in/satyanadella",
      "Company Website": "microsoft.com",
      "Notes": "CEO"
    }
  ],
  "fields": [
    "email",
    "phone"
  ],
  "limit": 25,
  "includeAudit": false
}
```

# Actor output Schema

## `contacts` (type: `string`):

Every original column unchanged, plus profileUrl, fullName and the fields you asked for

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

Counts, requested fields, stop reason and free-plan status for this run

## `misses` (type: `string`):

Rows that did not become a result, each with its row number and a reason (never charged)

# 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": [
        {
            "Name": "Patrick Collison",
            "Company Website": "stripe.com",
            "Notes": "CEO"
        },
        {
            "LinkedIn URL": "https://www.linkedin.com/in/satyanadella",
            "Company Website": "microsoft.com",
            "Notes": "CEO"
        }
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("b2bdata/contact-enrich-csv").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": [
        {
            "Name": "Patrick Collison",
            "Company Website": "stripe.com",
            "Notes": "CEO",
        },
        {
            "LinkedIn URL": "https://www.linkedin.com/in/satyanadella",
            "Company Website": "microsoft.com",
            "Notes": "CEO",
        },
    ] }

# Run the Actor and wait for it to finish
run = client.actor("b2bdata/contact-enrich-csv").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": [
    {
      "Name": "Patrick Collison",
      "Company Website": "stripe.com",
      "Notes": "CEO"
    },
    {
      "LinkedIn URL": "https://www.linkedin.com/in/satyanadella",
      "Company Website": "microsoft.com",
      "Notes": "CEO"
    }
  ]
}' |
apify call b2bdata/contact-enrich-csv --silent --output-dataset

```

## MCP server setup

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

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/06KSuiAVypNdvg0kF/builds/1t12hOQEZbaXbivti/openapi.json
