# B2B Leads Finder: Verified Emails, No Login (`agnes.developer.queen/b2b-leads-finder`) Actor

B2B leads finder for public LinkedIn profiles by title, company and location, with email evidence when an email can be derived. No login. $0.004 verified, $0.002 probable, $0.0005 without email.

- **URL**: https://apify.com/agnes.developer.queen/b2b-leads-finder.md
- **Developed by:** [Agnes Maina](https://apify.com/agnes.developer.queen) (community)
- **Categories:** Lead generation
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $4.00 / 1,000 lead with verified emails

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

## B2B Leads Finder: verified emails, no login

This B2B leads finder turns job titles, seniority, companies and locations into public LinkedIn lead rows with a work email when one can be derived, the evidence behind that email, and the company domain and logo. You pay per lead: $0.004 per lead with a verified email, $0.002 per lead with a probable email and $0.0005 per lead with no email. No login, no cookies, no LinkedIn API call and no paid enrichment API.

Limits before you run it: up to 5,000 delivered leads per run, 30 job titles, 200 company names, 20 locations and 20 company keywords, and at most 2,000 search jobs per run. It returns no phone numbers, and company size, revenue and industry are always null. Emails are pattern-derived unless `email_status` is `verified`: only that tier passed an SMTP check and a negative catch-all check, and if the mailbox check server is unreachable, every row that has mail servers drops to probable.

### What you get

One flat row per person, with snake_case keys:

| Group | Fields |
| --- | --- |
| Person | `first_name`, `last_name`, `full_name`, `job_title`, `headline` |
| Role | `seniority_level`, `functional_level`, `decision_maker` |
| Place | `linkedin`, `city`, `state`, `country`, `location_raw` |
| Email | `email`, `email_status`, `email_pattern`, `email_evidence` |
| Company | `company_name`, `company_domain`, `company_website`, `company_linkedin`, `company_logo` |
| Always null | `company_size`, `company_revenue`, `company_industry` |
| Provenance | `source` (`google-serp`), `scraped_at` |

Company LinkedIn URLs appear only when a company link occurred inside that person's Google result. The company name comes from the headline when it says "at Company", otherwise from the current employer in the profile's structured data when exactly one shows, then from the segment after the matched title, such as "CFO | Acme", and last from the Google byline. Strict mode reads the profile anyway, while with strict match off it reads only for billable rows whose headline and byline name no employer.

A free company suggestion endpoint supplies names, domains and logos when an exact normalized company name matches, including a company's own social or directory domain, so Google staff get google.com. Otherwise a Google official site query uses the same Apify GOOGLE_SERP proxy and rejects social and directory hosts. Placeholder employers such as Freelance or Stealth get no domain.

The actor also writes `RUN_SUMMARY` with delivered and filtered counts, charge-bar reasons, email status counts, SMTP availability and `chargedEventCount`.

#### Email evidence

| Status | Meaning |
| --- | --- |
| `verified` | An MX server accepted `RCPT TO` for the candidate and explicitly rejected a random recipient. Evidence `smtp-accepted`. This is an SMTP check, not proof of identity or delivery |
| `probable` | MX exists and a learned or common pattern is used. Catch-all servers and inconclusive checks stay probable. Evidence `mx+pattern`, or `smtp-blocked` when the server refused the checking IP |
| `unknown` | No domain, no usable MX, no usable name, or all candidates were rejected. Email is null. Evidence `no-domain`, `no-mx`, `no-name`, `smtp-rejected` |

Candidates are tried in order: `first.last`, `first`, `flast`, `f.last`, `firstlast`, `last`. A pattern confirmed earlier in the run for the same domain is tried first, and catch-all checks never teach one. Each SMTP connection gets five seconds, or eight on the mailbox check server. If connections fail across three consecutive distinct domains, SMTP stops for the run and one warning is logged.

Apify blocks outbound TCP port 25, so the checks run on the developer's own mailbox check server. That server receives each lead's first name, last name and company domain, and logs only the domain and the outcome. If it cannot be reached, the actor checks from the platform instead, where the three domain breaker disables SMTP and every row with MX becomes `probable`. `RUN_SUMMARY.emailRelay` reports which path ran.

### Try it in 30 seconds

1. Open the [Actor page in the Apify Store](https://apify.com/agnes.developer.queen/b2b-leads-finder) and click **Try for free**.
2. Put your titles in `jobTitles`, one per line, for example `Head of Marketing`.
3. Add companies in `companyNames` as names (`Stripe`) or URLs, set `maxLeads` to 20 and click **Start**.

### Example output

Illustrative row with a probable email, not a claim about a real address:

```json
{
  "first_name": "Jane",
  "last_name": "Doe",
  "full_name": "Jane Doe",
  "job_title": "Head of Marketing",
  "headline": "Head of Marketing at Example Company",
  "seniority_level": "director",
  "functional_level": "marketing",
  "decision_maker": true,
  "linkedin": "https://www.linkedin.com/in/jane-doe/",
  "city": "Austin",
  "state": "Texas",
  "country": "United States",
  "location_raw": "Austin, Texas, United States",
  "email": "jane.doe@example.com",
  "email_status": "probable",
  "email_pattern": "first.last",
  "email_evidence": "mx+pattern",
  "company_name": "Example Company",
  "company_domain": "example.com",
  "company_website": "https://example.com",
  "company_linkedin": null,
  "company_logo": null,
  "company_size": null,
  "company_revenue": null,
  "company_industry": null,
  "source": "google-serp",
  "scraped_at": "2026-10-06T00:00:00.000Z"
}
```

### Pricing

Pay per event, no subscription:

| Event | Price | You pay when |
| --- | --- | --- |
| Actor start | $0.00005 | once per run |
| `lead-with-verified-email` | $0.004 | the email passed the SMTP and catch-all checks |
| `lead-with-probable-email` | $0.002 | a pattern guess on a domain with mail servers |
| `lead-without-email` | $0.0005 | a LinkedIn profile with no derivable email |

That is $4 per 1,000 verified and $2 per 1,000 probable leads.

Only matches that clear the headline charge bar are billed. With `strictMatch`, the profile must be readable and list a searched company as a current employer. Other rows that pass your filters come back free, and a row with neither a LinkedIn URL nor an email is free. The `emailStatus`, seniority and department filters run before charging, and the actor checks the budget before each charge.

When the next charge does not fit your spending limit the run stops, even if a cheaper tier could still fit.

### Input

```json
{
  "jobTitles": ["Head of Marketing", "VP Marketing"],
  "seniority": ["vp", "director"],
  "department": ["marketing"],
  "locations": ["United States"],
  "companyNames": ["Stripe", "Asana"],
  "companyKeywords": [],
  "maxLeads": 20,
  "emailStatus": ["verified", "probable", "unknown"],
  "strictMatch": true
}
```

| Input | Behavior |
| --- | --- |
| `jobTitles` | Required, 1 to 30 titles. C level acronyms and title variants match |
| `seniority` | Optional list: owner, partner, cxo, vp, director, manager, senior, entry, training, unpaid, unknown. Empty means all |
| `department` | Optional list: executive, data, engineering, product, design, sales, marketing, finance, hr, legal, support, operations, other. Empty means all |
| `locations` | Up to 20 query locations. Narrows the search without verifying residence |
| `companyNames` | Up to 200 names or company URLs, which must hold the current-employer position to clear the charge bar |
| `companyKeywords` | Up to 20 quoted terms, searched separately. They do not prove an industry |
| `maxLeads` | Default 50, range 1 to 5,000. Counts leads after filters, free rows included |
| `emailStatus` | Defaults to all three tiers. An empty list also means all |
| `strictMatch` | Default true. False allows charging from headline evidence alone |

Inputs are limited to 2,000 search jobs: companies times title groups (`ceil(jobTitles.length / 3)`) times locations times company keywords, and larger combinations are rejected.

### Use cases

- Outbound lists: find decision makers at each named account and send only charged rows to your sequencer.
- Cold email: take the verified tier only and skip the guesses.
- Recruiting: find CTOs and heads of engineering in one city.
- CRM enrichment: write `company_domain` and `company_website` back to each account.

### Integrations

Run it from the Apify API and read the dataset back in the same call:

```bash
curl -X POST \
  "https://api.apify.com/v2/acts/agnes.developer.queen~b2b-leads-finder/run-sync-get-dataset-items" \
  -H "Authorization: Bearer $APIFY_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"jobTitles":["Head of Marketing"],"companyNames":["Stripe"],"maxLeads":20}'
```

The same call works from Python, Node.js, Make, n8n or Zapier, and AI assistants and MCP clients can call the Actor through the Apify MCP server:

```
https://mcp.apify.com/?tools=agnes.developer.queen/b2b-leads-finder
```

### FAQ

#### Are all emails verified?

No. Only the verified tier passed the SMTP and catch-all checks. Pattern-derived emails can be wrong, and acceptance can still precede a bounce.

#### What happens if the mailbox check server is down?

The actor stops SMTP after three blocked domains and keeps MX based probable emails, so verified only returns few leads.

#### Do you have phone numbers, company industry, size or revenue?

No phone numbers. Industry, size and revenue are null, because these sources do not establish them.

#### Can it find everyone at a company?

No. Google indexing, public headlines and profile readability limit coverage. Each search reads at most five pages and stops when a page yields nothing.

#### How accurate are company domains?

A suggestion lookup needs an exact match after normalization. The Google fallback takes the first eligible organic host, so similarly named companies can still give a wrong domain. A domain is not identity verification.

#### How are locations split?

Three comma-separated parts become city, state and country. Two become city and country. One is kept as country, with `location_raw` preserved.

#### What does strict match verify?

That the public LinkedIn profile is readable and names a requested current employer, after its headline clears the title and company bar. It does not verify with the employer or establish email ownership.

#### Why are some leads free?

Their headline or profile did not prove the requested match, or they have neither a LinkedIn URL nor an email. Reasons appear in `RUN_SUMMARY`.

### Related Actors

- [LinkedIn People Search Scraper](https://apify.com/agnes.developer.queen/linkedin-people-search-scraper) returns the same public profiles without emails, and charges only confirmed matches.
- [LinkedIn New Hires Finder](https://apify.com/agnes.developer.queen/linkedin-new-hires-finder) finds decision makers who joined a company recently, with the month they started.
- [LinkedIn Company Scraper](https://apify.com/agnes.developer.queen/linkedin-company-scraper) turns company names and URLs into company facts.

### Support

Open an issue on the [Issues tab of the Store page](https://apify.com/agnes.developer.queen/b2b-leads-finder/issues) with the run ID and `RUN_SUMMARY`.

# Actor input Schema

## `jobTitles` (type: `array`):

At least one target title. Acronyms and common title variants are supported. Companies × title groups (ceil(titles / 3)) × locations × company keywords must not exceed 2,000 jobs, with each empty optional list counting as one.

## `seniority` (type: `array`):

Optional seniority filters. Empty means all bands.

## `department` (type: `array`):

Optional function filters, derived from each headline. Empty means all functions.

## `locations` (type: `array`):

Cities, regions or countries used in the Google query.

## `companyNames` (type: `array`):

Optional company names or LinkedIn company URLs. Current-employer matching applies when supplied.

## `companyKeywords` (type: `array`):

Optional quoted search terms. Each term is searched separately alongside titles and company names. Google keyword relevance is not company industry verification.

## `maxLeads` (type: `integer`):

Maximum delivered leads after filters, including free unmatched rows.

## `emailStatus` (type: `array`):

Select the email evidence tiers to deliver. Applied before charging. Empty means all statuses.

## `strictMatch` (type: `boolean`):

Before billing a headline match, confirm the public LinkedIn profile and its current employer. Unreadable or unmatched profiles are free.

## Actor input object example

```json
{
  "jobTitles": [
    "Head of Marketing",
    "VP Marketing"
  ],
  "locations": [
    "United States"
  ],
  "maxLeads": 20,
  "emailStatus": [
    "verified",
    "probable",
    "unknown"
  ],
  "strictMatch": true
}
```

# Actor output Schema

## `leads` (type: `string`):

No description

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

No description

# 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 = {
    "jobTitles": [
        "Head of Marketing",
        "VP Marketing"
    ],
    "locations": [
        "United States"
    ],
    "maxLeads": 20
};

// Run the Actor and wait for it to finish
const run = await client.actor("agnes.developer.queen/b2b-leads-finder").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 = {
    "jobTitles": [
        "Head of Marketing",
        "VP Marketing",
    ],
    "locations": ["United States"],
    "maxLeads": 20,
}

# Run the Actor and wait for it to finish
run = client.actor("agnes.developer.queen/b2b-leads-finder").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 '{
  "jobTitles": [
    "Head of Marketing",
    "VP Marketing"
  ],
  "locations": [
    "United States"
  ],
  "maxLeads": 20
}' |
apify call agnes.developer.queen/b2b-leads-finder --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,agnes.developer.queen/b2b-leads-finder"
        }
    }
}
```

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/fwOvpIqODm3TgPvJN/builds/cl5ianbRDwbvcVoaL/openapi.json
