# B2B Lead List Cleaner - Bulk Email Verification & Validator (`kamerozkan/b2b-lead-cleaner`) Actor

Bulk email verification and email list cleaning for B2B leads: an email validator and verifier that checks syntax and MX records, flags personal, disposable, and role inboxes, dedupes, and enforces ICP rules with an explained decision per row. Domain-level checks only; no SMTP mailbox pings.

- **URL**: https://apify.com/kamerozkan/b2b-lead-cleaner.md
- **Developed by:** [Kamer Ozkan](https://apify.com/kamerozkan) (community)
- **Categories:** Lead generation
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $1.70 / 1,000 audited leads

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/platform/actors/running/actors-in-store#pay-per-event

## What's an Apify Actor?

Actors are web data automations that power AI and operations. They run on the Apify platform to scrape websites, process data, connect APIs, and automate workflows.
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.
Actors are written with capital "A".

## 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.
The best way to integrate Actors is as follows.

- **AI agents and MCP clients** — the [Apify MCP server](https://docs.apify.com/integrations/mcp.md) at `https://mcp.apify.com` (remote, streamable HTTP, OAuth on first use).
- **Agentic workflows and local Actor development** — [Agent Skills](https://apify.com/.well-known/agent-skills/index.json) with the [Apify CLI](https://docs.apify.com/cli/docs.md): `npm install -g apify-cli`, then `apify login`.
- **JavaScript/TypeScript projects** — the official [JS/TS client](https://docs.apify.com/api/client/js/docs.md): `npm install apify-client`.
- **Python projects** — the official [Python client](https://docs.apify.com/api/client/python/docs.md): `pip install apify-client`.
- **Any other language** — the [REST API](https://docs.apify.com/api/v2.md).

For usage examples, see the [API](#api) section below.

For more details, see Apify documentation as [Markdown index](https://docs.apify.com/llms.txt) and [Markdown full-text](https://docs.apify.com/llms-full.txt).

# README

**Put an explained quality gate between a raw lead list and your CRM.**

This Actor audits B2B lead rows before you spend more enrichment credits, outreach credits, or SDR time. For every row it successfully delivers, you receive one deterministic decision:

- `ACCEPT`: the row passes the configured evidence and targeting rules
- `REVIEW`: evidence is incomplete, uncertain, or below the acceptance threshold
- `REJECT`: a hard quality, duplicate, or ICP rule failed

It checks email syntax, DNS mail routing, provider type, role inboxes, company-domain consistency, duplicates, freshness, and optional ICP rules. It does not connect to an SMTP server and does not claim that an individual mailbox exists.

**Free plan: $2 per 1,000 audited leads. Plan discounts reduce this to as low as $1.70. No start fee.**

### What you get

| Result | What it tells you |
| --- | --- |
| One explained decision per delivered row | Route good leads and isolate uncertainty without a black-box score |
| Batch and historical duplicate flags | Find repeated contacts before CRM import |
| Email syntax and DNS MX evidence | Confirm domain-level mail configuration without claiming mailbox verification |
| Personal, disposable, and role-inbox classification | Apply a consistent inbox policy |
| Country, city, title, industry, and account rules | Enforce the ICP attached to a list order |
| Raw-row preservation and normalized fields | Keep source-specific data while gaining stable automation fields |
| Run report and source economics | Measure delivered yield, reason mix, and audited-scope cost |

### Duplicate behavior

Duplicates are not silently removed.

The Actor keeps one output decision for every row it successfully delivers. A non-canonical duplicate remains in the output with:

- `decision: "REJECT"`
- `duplicate: true` for a duplicate in the current batch
- `historicalDuplicate: true` when the monitor has seen the lead before
- `DUPLICATE_LEAD` or `HISTORICAL_DUPLICATE` in `reasonCodes`

Each delivered decision, including a duplicate REJECT, is a billable `lead-audited` event. Route only `ACCEPT` rows downstream if you want a deduplicated usable list.

### Supported inputs and priority

Choose one source:

1. `datasetId`: an Apify dataset containing lead objects or email strings
2. `emails`: up to 5,000 email strings
3. `rows`: up to 5,000 JSON lead objects

If more than one source is supplied, the priority is `datasetId`, then `emails`, then `rows`.

Dataset runs can audit up to 25,000 rows per run. Use `offset` and `maxItems` to process larger sources in non-overlapping ranges. `offset` can be up to 10,000,000 and `maxItems` can be up to 25,000.

#### Dataset input

```json
{
  "datasetId": "YOUR_DATASET_ID",
  "qualityMode": "BALANCED",
  "offset": 0,
  "maxItems": 25000
}
```

In Apify Console, select the dataset with the resource picker. The Actor receives read-only access to that dataset. No API token is normally needed inside the Console input form.

#### Email-string input

```json
{
  "emails": [
    "ada@stripe.com",
    "info@example.com",
    "buyer@gmail.com"
  ],
  "qualityMode": "BALANCED"
}
```

Each email string becomes a source object such as `{ "email": "ada@stripe.com" }`. Email-only input can evaluate syntax, provider type, DNS mail routing, and duplicates. It cannot invent person, company, location, or employment evidence.

#### Pasted lead rows

```json
{
  "rows": [
    {
      "full_name": "Ada Rivera",
      "job_title": "VP Marketing",
      "email": "ada@stripe.com",
      "country": "United States",
      "company_name": "Stripe",
      "company_domain": "stripe.com",
      "industry": "Software"
    }
  ]
}
```

### Field detection and compatibility

The Actor detects common contact and company aliases, including nested paths such as `contact.email`, `person.jobTitle`, and `company.website`. The `OUTPUT` report shows:

- the selected path for every normalized field
- sampled coverage for that path
- fields explicitly overridden with `fieldMap`

Use manual mapping when the report selects the wrong path:

```json
{
  "datasetId": "YOUR_DATASET_ID",
  "fieldMap": {
    "fullName": "contact.name",
    "email": "contact.workEmail",
    "title": "employment.currentTitle",
    "companyDomain": "company.website"
  }
}
```

This Actor works best with person-level B2B lead datasets. Dataset items that are plain strings are treated as emails. A place or company dataset, including many Google Maps outputs, may not contain a contact email or job title. Do not assume generic compatibility: inspect field coverage, map the real paths, and test a small range first.

Phone numbers and LinkedIn values are normalized and preserved, not verified. The Actor also does not verify current employment.

### Quality modes

| Mode | Base behavior |
| --- | --- |
| `BALANCED` | Requires email and valid domain mail routing, rejects personal and disposable providers, rejects email-company domain mismatches, warns on role inboxes, and does not probe websites |
| `STRICT` | Adds role-inbox rejection, requires company-domain evidence, probes public websites, enables a 365-day freshness rule, and raises score thresholds |
| `CUSTOM` | Starts with the same preset as Balanced, then applies rule overrides |

Rule overrides apply after every preset, not only Custom. This makes it possible to make one deliberate change without recreating the entire preset:

```json
{
  "qualityMode": "BALANCED",
  "rules": {
    "rejectRoleBasedEmail": true,
    "acceptScore": 80
  }
}
```

`requireMx: true` automatically enables MX checks. `requireWebsiteReachable: true` automatically enables safe website checks. `reviewScore` must remain lower than `acceptScore`.

### Optional ICP contract

Use targeting rules when a source or vendor promised a specific audience:

```json
{
  "datasetId": "YOUR_DATASET_ID",
  "targeting": {
    "countries": ["United States"],
    "cities": ["New York", "Boston"],
    "titleKeywords": ["CMO", "VP Marketing", "Head of Growth"],
    "excludedTitleKeywords": ["Assistant", "Intern"],
    "industries": ["Software", "SaaS"],
    "companyDomains": ["example.com"]
  }
}
```

When a targeting list is configured, missing evidence is not treated as a match. It receives a specific missing-field or mismatch reason.

### Output contract

The default dataset contains one object per delivered decision. Important fields include:

- `sourceIndex`, `decision`, `qualityScore`, `primaryReason`, and `reasonCodes`
- normalized contact and company fields
- `emailStatus`, `mxStatus`, and `websiteStatus`
- `duplicate`, `historicalDuplicate`, and `dedupeKey`
- typed `evidence` and ordered `reasons`
- `normalizationTruncatedFields`
- `original`, `originalPreserved`, and `originalByteLength`
- `checkedAt` and `schemaVersion`

All records below are unedited engine output from a live run on 2026-07-28. Every
input row was a labeled sample (each `fullName` carries a "(sample)" suffix and each
address uses an `apify-task-demo` local part), so no real person appears here; the MX
evidence against the real domains is genuine.

A clean accepted lead, in full. Note `original`: the source row used `work_email` and
`job_title`, and field detection mapped them without configuration.

<details>
<summary>Show the full JSON example</summary>

```json
{
  "sourceIndex": 0,
  "decision": "ACCEPT",
  "qualityScore": 100,
  "primaryReason": null,
  "reasonCodes": [],
  "fullName": "Jonas Weber (sample)",
  "title": "VP Sales",
  "email": "apify-task-demo-501@asana.com",
  "phone": null,
  "linkedin": null,
  "country": "US",
  "city": null,
  "companyName": "Asana",
  "companyDomain": "asana.com",
  "industry": "SaaS Software",
  "emailDomain": "asana.com",
  "emailStatus": "BUSINESS",
  "mxStatus": "VALID",
  "websiteStatus": "NOT_CHECKED",
  "dedupeKey": "email:apify-task-demo-501@asana.com",
  "duplicate": false,
  "historicalDuplicate": false,
  "normalizationTruncatedFields": [],
  "evidence": {
    "email": {
      "syntaxValid": true,
      "domain": "asana.com",
      "status": "BUSINESS",
      "mx": {
        "status": "VALID",
        "records": [
          {
            "exchange": "aspmx.l.google.com",
            "priority": 10
          },
          {
            "exchange": "alt1.aspmx.l.google.com",
            "priority": 20
          },
          {
            "exchange": "alt2.aspmx.l.google.com",
            "priority": 30
          },
          {
            "exchange": "aspmx2.googlemail.com",
            "priority": 40
          },
          {
            "exchange": "aspmx3.googlemail.com",
            "priority": 50
          }
        ],
        "error": null
      },
      "mailboxExistence": "NOT_CLAIMED"
    },
    "company": {
      "domain": "asana.com",
      "domainInferredFromEmail": false,
      "website": {
        "status": "NOT_CHECKED",
        "httpStatus": null,
        "finalUrl": null,
        "error": null
      }
    }
  },
  "reasons": [],
  "original": {
    "full_name": "Jonas Weber (sample)",
    "job_title": "VP Sales",
    "work_email": "apify-task-demo-501@asana.com",
    "country_code": "US",
    "company_name": "Asana",
    "company_website": "https://asana.com",
    "company_industry": "SaaS Software",
    "verified_at": "2026-07-20T10:00:00Z",
    "sample_record": true
  },
  "originalPreserved": true,
  "originalByteLength": 278,
  "checkedAt": "2026-07-28T20:02:21.752Z",
  "schemaVersion": "1.0"
}
```

</details>

The same run exercised the failure catalog. Three guarantees, three records:

- **Nothing is dropped silently.** The duplicate row is delivered with
  `duplicate: true`, `DUPLICATE_LEAD`, and its `dedupeKey`, so your CRM sees why it was
  rejected instead of wondering where a row went.
- **A dead domain is proof, not a guess.** The `NO_MX` rejection carries the DNS lookup
  result inside `evidence.email.mx`, and `mailboxExistence` stays `NOT_CLAIMED` because
  no SMTP probing is performed.
- **Uncertainty is a verdict, not a coin flip.** A role-based team inbox gets `REVIEW`
  with named reasons, not a fake ACCEPT or a silent REJECT.

```json
{
  "sourceIndex": 1,
  "decision": "REJECT",
  "qualityScore": 30,
  "primaryReason": "The same lead already appeared in this batch.",
  "reasonCodes": [
    "DUPLICATE_LEAD"
  ],
  "fullName": "Jonas Weber (sample)",
  "email": "apify-task-demo-501@asana.com",
  "emailStatus": "BUSINESS",
  "mxStatus": "VALID",
  "duplicate": true,
  "dedupeKey": "email:apify-task-demo-501@asana.com",
  "originalPreserved": true
}
```

```json
{
  "sourceIndex": 3,
  "decision": "REJECT",
  "qualityScore": 45,
  "primaryReason": "Email domain has no observable mail server records.",
  "reasonCodes": [
    "EMAIL_DOMAIN_NO_MX"
  ],
  "fullName": "Tobias Lang (sample)",
  "email": "apify-task-demo-503@apify-demo-no-mx-20260728.com",
  "emailStatus": "BUSINESS",
  "mxStatus": "NO_MX",
  "duplicate": false,
  "dedupeKey": "email:apify-task-demo-503@apify-demo-no-mx-20260728.com",
  "originalPreserved": true,
  "evidence": {
    "email": {
      "domain": "apify-demo-no-mx-20260728.com",
      "mx": {
        "status": "NO_MX",
        "records": [],
        "error": "NO_DNS"
      },
      "mailboxExistence": "NOT_CLAIMED",
      "note": "remaining evidence fields unchanged, shortened here"
    }
  }
}
```

```json
{
  "sourceIndex": 6,
  "decision": "REVIEW",
  "qualityScore": 85,
  "primaryReason": "Quality score is below the ACCEPT threshold.",
  "reasonCodes": [
    "QUALITY_SCORE_NEEDS_REVIEW",
    "EMAIL_ROLE_BASED"
  ],
  "fullName": "Outreach Desk (sample)",
  "email": "hello@typeform.com",
  "emailStatus": "ROLE_BASED",
  "mxStatus": "VALID",
  "duplicate": false,
  "dedupeKey": "email:hello@typeform.com",
  "originalPreserved": true
}
```

The same run also produced named rejections for a personal inbox (`EMAIL_PERSONAL`), a
disposable domain (`EMAIL_DISPOSABLE`), and a missing address (`EMAIL_MISSING`); every
non-accepted row leaves with a machine-readable reason code.

`qualityScore` is a rule-based completeness score, not a deliverability percentage.

`mxStatus: "VALID"` means usable public MX routing was observed. `IMPLICIT_MX` means the domain uses the standards-based address-record fallback. Neither result proves that the specific mailbox exists.

The complete source item is copied to `original`. If its serialized size exceeds 7,500,000 bytes, the Actor emits an omission marker, sets `originalPreserved` to false, and adds `ORIGINAL_TOO_LARGE`. Oversized normalized strings are shortened to safe limits and listed in `normalizationTruncatedFields`.

The original source item and `dedupeKey` can contain personal data. Treat output storage, export, access, and deletion accordingly.

### Run-level report

The default key-value store record `OUTPUT` includes:

- delivered ACCEPT, REVIEW, and REJECT counts and rates
- duplicate counts and reason frequencies
- source type, offset, loaded rows, truncation, and budget effects
- resolved rules and targeting
- field mapping and sampled coverage
- history save status
- billing and delivery integrity counters
- explicit truth boundaries

Possible report statuses are:

- `SUCCEEDED`
- `PARTIAL_BUDGET_LIMIT`
- `PARTIAL_DELIVERY`
- `SUCCEEDED_WITH_WARNINGS`

Always check `status`, `source.deliveredItems`, and `billing.delivered` before treating a run as complete.

### Historical duplicate protection

Set `monitorName` for a recurring, non-overlapping workflow:

```json
{
  "datasetId": "YOUR_DATASET_ID",
  "monitorName": "us-saas-outbound"
}
```

The monitor stores SHA-256 hashes of normalized deduplication keys in a named Apify key-value store. Hashes are pseudonymized, not anonymous or encrypted. The decision dataset still contains the normalized `dedupeKey` and usually the original source row.

Named key-value stores are retained indefinitely until you delete them. History is capped at the newest 100,000 hashes. Use separate monitor names for separate business purposes and delete stores according to your retention policy.

Do not overlap scheduled runs that use the same `monitorName`. Each run evaluates historical duplicates from the snapshot available when it starts, so overlapping runs can both classify a newly seen lead as new.

The report distinguishes:

- `DISABLED`: no monitor requested
- `SAVED`: delivered dedupe keys were saved
- `SAVE_WARNING`: decisions were delivered, but history could not be updated

If history returns `SAVE_WARNING`, fix or retry the history step before the next scheduled run.

### Source economics

`rules.sourceCostUsd` must be the source cost attributable to the rows requested for this audit:

```json
{
  "datasetId": "YOUR_DATASET_ID",
  "offset": 10000,
  "maxItems": 5000,
  "rules": {
    "sourceCostUsd": 50
  }
}
```

Do not enter the cost of the entire source batch unless the run audits that entire batch. The report uses:

- `sourceCostProvidedUsd`: the value you entered
- `sourceCostScope: "AUDITED_ROWS"`: the declared scope was fully delivered
- `sourceCostScope: "INCOMPLETE_DELIVERY"`: safe allocation was not possible after partial delivery
- `sourceCostPerAcceptedLeadUsd`: calculated only for a complete audited scope with at least one ACCEPT

This is a supplied cost allocation, not a value independently verified by the Actor.

### API and automation

API calls require an Apify API token. Example:

```bash
curl -X POST \
  "https://api.apify.com/v2/acts/kamerozkan~b2b-lead-cleaner/runs" \
  -H "Authorization: Bearer YOUR_APIFY_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "datasetId": "YOUR_DATASET_ID",
    "qualityMode": "BALANCED",
    "offset": 0,
    "maxItems": 1000
  }'
```

After the run finishes, read decision rows from the default dataset and the report from key `OUTPUT` in the default key-value store.

For Make, Zapier, n8n, webhooks, CRMs, or AI agents:

- send `ACCEPT` to the permitted downstream workflow
- send `REVIEW` to a manual or enrichment queue
- archive or inspect `REJECT`
- check the run report before advancing a whole batch

This Actor is also exposed to AI agents through Apify's MCP server
(mcp.apify.com): an agent can discover it by search and run it with the same
pay-per-event billing, with no separate integration.

### Security and operational boundaries

- source dataset access is read-only
- private, local, and reserved network targets are never requested
- website checks resolve and pin public addresses before requests
- no SMTP mailbox probing is performed
- no mailbox ownership, current employment, or reply is guaranteed
- original source rows are copied to output unless the size guard omits them
- monitor history is pseudonymized and persists until deleted
- rows prevented by the run budget are not delivered or charged

### Pricing

One `lead-audited` event represents one delivered `ACCEPT`, `REVIEW`, or
`REJECT` quality decision. Current prices per 1,000 audited leads are:

| Apify plan | Price |
|---|---:|
| Free | $2.00 |
| Bronze | $1.90 |
| Silver | $1.80 |
| Gold, Platinum, or Diamond | $1.70 |

The active pricing model has no Actor start event. Apify shows the exact price
for the user's plan before every run.

There is no start fee. Rows blocked by the Apify run budget are not delivered or charged.

### Responsible use

Process only data you are authorized to use. Follow applicable privacy, marketing, and anti-spam laws, including GDPR, CAN-SPAM, and local equivalents. A technical quality decision does not create a lawful basis for outreach.

# Actor input Schema

## `datasetId` (type: `string`):

Select a B2B lead dataset containing lead objects or email strings. The Actor receives read-only access to this dataset. When supplied, datasetId takes priority over emails and rows.

## `emails` (type: `array`):

Paste up to 5,000 email strings for a domain-level QA run. This source takes priority over rows when datasetId is empty. It checks syntax and DNS mail configuration, not whether an individual mailbox exists.

## `rows` (type: `array`):

Paste up to 5,000 JSON lead objects. This source is used only when datasetId and emails are empty. Original source fields are copied to the output unless a row exceeds the safe dataset-item size.

## `qualityMode` (type: `string`):

Balanced is recommended. Strict requires more company evidence. Custom starts from the Balanced preset. The rule overrides below apply after any selected preset.

## `offset` (type: `integer`):

Skip this many source items before auditing. Use it with maxItems to process a large dataset in non-overlapping ranges.

## `maxItems` (type: `integer`):

Maximum rows to process after offset, and the cost cap: billing is $0.002 per audited lead, duplicates and failures included in the audit are still real decisions with reason codes. Dataset runs support up to 25,000 rows per run; pasted emails and rows are limited to 5,000.

## `targeting` (type: `object`):

When values are supplied, non-matching leads are rejected with an exact reason.

## `rules` (type: `object`):

Defaults come from the selected quality mode. Any value set here overrides that preset, including Balanced and Strict.

## `fieldMap` (type: `object`):

Usually unnecessary. Override auto-detection with dot paths such as contact.email or company.website.

## `monitorName` (type: `string`):

Reuse the same name on scheduled runs to reject leads already delivered in earlier runs (historical duplicate protection). Dedupe-key hashes are pseudonymized in a named store and persist until you delete them.

## `historyKey` (type: `string`):

Deprecated compatibility field for existing saved tasks. New integrations should use monitorName.

## `maxConcurrency` (type: `integer`):

Parallel MX and optional website checks. Higher is faster and does not change cost; lower it only if your network policy requires gentler DNS traffic.

## Actor input object example

```json
{
  "rows": [
    {
      "full_name": "Ada Rivera",
      "job_title": "VP Marketing",
      "email": "ada@stripe.com",
      "country": "United States",
      "company_name": "Stripe",
      "company_domain": "stripe.com",
      "industry": "Software"
    },
    {
      "full_name": "Duplicate Ada",
      "job_title": "VP Marketing",
      "email": "ada@stripe.com",
      "country": "United States",
      "company_name": "Stripe",
      "company_domain": "stripe.com",
      "industry": "Software"
    },
    {
      "full_name": "Personal Inbox",
      "job_title": "Marketing Manager",
      "email": "someone@gmail.com",
      "country": "United States",
      "company_name": "Example Co",
      "company_domain": "example.com",
      "industry": "Software"
    }
  ],
  "qualityMode": "BALANCED",
  "offset": 0,
  "maxItems": 1000,
  "maxConcurrency": 10
}
```

# Actor output Schema

## `decisions` (type: `string`):

Delivered source rows with ACCEPT, REVIEW, or REJECT, normalized fields, typed domain evidence, duplicate flags, raw-row preservation status, and machine-readable reason codes.

## `qualityReport` (type: `string`):

Typed run status, delivered-row summary, source range, field mapping, audited-scope economics, history-save status, truth boundaries, and billing integrity counters.

# 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 = {
    "rows": [
        {
            "full_name": "Ada Rivera",
            "job_title": "VP Marketing",
            "email": "ada@stripe.com",
            "country": "United States",
            "company_name": "Stripe",
            "company_domain": "stripe.com",
            "industry": "Software"
        },
        {
            "full_name": "Duplicate Ada",
            "job_title": "VP Marketing",
            "email": "ada@stripe.com",
            "country": "United States",
            "company_name": "Stripe",
            "company_domain": "stripe.com",
            "industry": "Software"
        },
        {
            "full_name": "Personal Inbox",
            "job_title": "Marketing Manager",
            "email": "someone@gmail.com",
            "country": "United States",
            "company_name": "Example Co",
            "company_domain": "example.com",
            "industry": "Software"
        }
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("kamerozkan/b2b-lead-cleaner").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 = { "rows": [
        {
            "full_name": "Ada Rivera",
            "job_title": "VP Marketing",
            "email": "ada@stripe.com",
            "country": "United States",
            "company_name": "Stripe",
            "company_domain": "stripe.com",
            "industry": "Software",
        },
        {
            "full_name": "Duplicate Ada",
            "job_title": "VP Marketing",
            "email": "ada@stripe.com",
            "country": "United States",
            "company_name": "Stripe",
            "company_domain": "stripe.com",
            "industry": "Software",
        },
        {
            "full_name": "Personal Inbox",
            "job_title": "Marketing Manager",
            "email": "someone@gmail.com",
            "country": "United States",
            "company_name": "Example Co",
            "company_domain": "example.com",
            "industry": "Software",
        },
    ] }

# Run the Actor and wait for it to finish
run = client.actor("kamerozkan/b2b-lead-cleaner").call(run_input=run_input)

# Fetch and print Actor results from the run's dataset (if there are any)
print("💾 Check your data here: https://console.apify.com/storage/datasets/" + run["defaultDatasetId"])
for item in client.dataset(run["defaultDatasetId"]).iterate_items():
    print(item)

# 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/python/docs/quick-start

```

## CLI example

```bash
echo '{
  "rows": [
    {
      "full_name": "Ada Rivera",
      "job_title": "VP Marketing",
      "email": "ada@stripe.com",
      "country": "United States",
      "company_name": "Stripe",
      "company_domain": "stripe.com",
      "industry": "Software"
    },
    {
      "full_name": "Duplicate Ada",
      "job_title": "VP Marketing",
      "email": "ada@stripe.com",
      "country": "United States",
      "company_name": "Stripe",
      "company_domain": "stripe.com",
      "industry": "Software"
    },
    {
      "full_name": "Personal Inbox",
      "job_title": "Marketing Manager",
      "email": "someone@gmail.com",
      "country": "United States",
      "company_name": "Example Co",
      "company_domain": "example.com",
      "industry": "Software"
    }
  ]
}' |
apify call kamerozkan/b2b-lead-cleaner --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "command": "npx",
            "args": [
                "mcp-remote",
                "https://mcp.apify.com/?tools=kamerozkan/b2b-lead-cleaner",
                "--header",
                "Authorization: Bearer <YOUR_API_TOKEN>"
            ]
        }
    }
}

```

## OpenAPI specification

Download the OpenAPI definition: https://api.apify.com/v2/actors/Fu5NBEXHS03Hwcaiz/builds/U4Jxs9wQ6DBOBHtDU/openapi.json
