# Leads Scraper $1.5/1000 Leads up to 50K Run (`onion_labs/contacts-search`) Actor

- **URL**: https://apify.com/onion\_labs/contacts-search.md
- **Developed by:** [Onion Labs](https://apify.com/onion_labs) (community)
- **Categories:**
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $1.50 / 1,000 results

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/actors/running/actors-in-store.md#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

## B2B Contacts Search

Search a curated database of **2.9 million B2B contacts** by company, job title,
seniority, industry, location and company size. Every result carries the
person's professional profile and the firmographics of the company they work at.

Built for prospecting, list building, market research and CRM enrichment.

### What you get

One dataset item per contact, with up to 43 fields. Fields are omitted when no
value is known, so treat everything except `id` as optional.

```json
{
  "id": "linkedin.com/in/alex-saurbier",
  "fullName": "Alex Saurbier",
  "linkedinUrl": "https://www.linkedin.com/in/alex-saurbier",
  "email": "alexsaurbier@commonroom.io",
  "emailStatus": "VERIFIED",
  "jobTitle": "Enterprise Account Executive",
  "seniority": "Senior",
  "department": "Sales",
  "companyName": "Common Room",
  "companyDomain": "commonroom.io",
  "companyIndustry": "Software Development",
  "companyEmployeeCount": 183,
  "country": "United States",
  "sourceCount": 2,
  "lastSeenAt": "2026-07-22T10:04:11"
}
```

`sourceCount` is how many independent sources agreed on the contact. Higher
means better corroborated.

### Coverage

| Field | Share of contacts |
|---|---|
| Job title | 100% |
| LinkedIn profile | 99.9% |
| Company domain | 94.9% |
| Email | 83.6% |
| Company industry | 72.3% |
| Country | 29.9% |
| Seniority | 28.4% |
| State or region | 26.6% |
| Verified email | 6.9% |
| Phone | 6.0% |

Filtering on a field excludes every contact that has no value for it. Seniority
and department are the sparsest, so pair them with a job title search rather
than relying on them alone.

### How to search

Every filter is optional. Filters combine with AND; multiple values inside one
filter are alternatives.

**Target companies**

- `companyDomains` and `companyNames` to focus on named accounts
- `excludeCompanyDomains` to suppress existing customers

**People**

- `jobTitles` and `excludeJobTitles`, matched against title and LinkedIn headline
- `seniorities` and `departments`, picked from the values used in the data
- `keywords`, matched across name, title, headline, bio, skills and company text

**Company filters**

- `industries`, `companyTechnologies`
- `minCompanyEmployees` / `maxCompanyEmployees`, which understand both exact
  counts and ranges such as `50-199`
- `minCompanyFoundedYear` / `maxCompanyFoundedYear`

**Location**

- `countries` from a list, `states` and `locations` as free text

**Data quality**

- `requireEmail`, `requireVerifiedEmail`, `requirePhone`, `requireLinkedinUrl`

**Output**

- `maxResults`, from 1 to 50,000

#### Example: heads of growth at mid sized software companies

```json
{
  "jobTitles": ["head of growth", "vp growth"],
  "industries": ["Software"],
  "minCompanyEmployees": 50,
  "maxCompanyEmployees": 500,
  "countries": ["United States"],
  "requireEmail": true,
  "maxResults": 500
}
```

#### Example: everyone at a named account

```json
{
  "companyDomains": ["stripe.com", "figma.com"],
  "maxResults": 200
}
```

Naming company domains is the fastest and cheapest query: the actor reads only
the slice of the database holding those companies instead of scanning it.

### Speed and cost

The run stops the moment `maxResults` is reached, so asking for 100 contacts
costs a fraction of asking for 50,000. A typical filtered query for a few
hundred results touches a small percentage of the database and finishes in
seconds.

Pricing is pay per event: a small charge per run, plus a per contact charge that
is higher for contacts with an email than for profile only contacts. Current
prices are shown on the Actor page.

### Data quality notes

- **Placeholders are treated as missing.** Upstream sources sometimes write
  `Not revealed`, `Unclassified` or `[""]` where a value is absent. Those are
  stripped, so `requirePhone` never returns a row whose phone is a placeholder.
- **Taxonomy fields are normalized.** Seniority, country and industry arrive
  under many spellings and casings. They are canonicalized so one filter value
  reaches every spelling: `US` and `United States` are one country, `director`
  and `Director` are one rank.
- **Free text is verbatim.** Company descriptions, bios and headlines are passed
  through exactly as recorded.
- **The dataset is a point in time snapshot.** Contact data decays a few percent
  a month. `lastSeenAt` on each record and the build date in the run log tell
  you how fresh a given record is.

### Output schema

The full field list, with types and descriptions, is published as the Actor's
output schema and visible on the Actor page. Three preview views are provided:
Overview, Contact details and Company.

### Support

Open an issue on the Actor page with the run id and the input you used.

# Actor input Schema

## `maxResults` (type: `integer`):

The run stops as soon as this many contacts are found, so a smaller number is faster and costs less. Every available field is returned on each contact.

## `companyDomains` (type: `array`):

Return contacts working at these companies. Use bare domains such as acme.com. This is the fastest query type: it reads only the shards holding those companies instead of scanning the database.

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

Match on company name text. Case insensitive, matches anywhere in the name.

## `excludeCompanyDomains` (type: `array`):

Never return contacts at these domains. Useful for suppressing existing customers.

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

Match job title or LinkedIn headline. Any one match is enough, for example: head of growth, vp marketing, cto. Present on 100% of contacts.

## `excludeJobTitles` (type: `array`):

Drop contacts whose title contains any of these, for example: intern, assistant, retired.

## `seniorities` (type: `array`):

Seniority rank as recorded in the database. Present on 28% of contacts, so selecting a value here excludes the 72% with no seniority recorded.

## `departments` (type: `array`):

Department or job function. Present on only 3% of contacts, by far the sparsest filter here, so use it alongside a job title search rather than on its own.

## `keywords` (type: `array`):

Match anywhere across name, job title, headline, bio, skills, company name, company description and company keywords.

## `industries` (type: `array`):

Industry recorded against the company. Present on 72% of contacts. Values match as text, so Software also matches Software/SaaS and Software Development.

## `companyTechnologies` (type: `array`):

Match technologies recorded against the company, for example: hubspot, salesforce, shopify.

## `minCompanyEmployees` (type: `integer`):

Smallest company size to include. Understands both exact counts and ranges such as 50-199.

## `maxCompanyEmployees` (type: `integer`):

Largest company size to include.

## `minCompanyFoundedYear` (type: `integer`):

Only companies founded in this year or later.

## `maxCompanyFoundedYear` (type: `integer`):

Only companies founded in this year or earlier.

## `countries` (type: `array`):

Country of the contact or their company. Recorded for 30% of contacts and heavily United States weighted.

## `states` (type: `array`):

Free text, matched against the state and the wider location fields, for example: New York, California, Texas. Enter one or more.

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

Broadest location match: city, state, country or the free text location on the person or the company. Use this for cities, for example: london, austin.

## `requireEmail` (type: `boolean`):

Skip contacts with no email address. 2,460,041 of 2,942,246 contacts qualify.

## `requireVerifiedEmail` (type: `boolean`):

Skip contacts whose email is not marked verified by the upstream source. 202,462 contacts qualify.

## `requirePhone` (type: `boolean`):

Skip contacts with no phone number. 176,317 contacts qualify. Placeholder values such as "Not revealed" count as missing.

## `requireLinkedinUrl` (type: `boolean`):

Skip contacts with no LinkedIn profile URL. 2,939,042 of 2,942,246 contacts qualify, so this rarely changes the result.

## Actor input object example

```json
{
  "maxResults": 1000,
  "requireEmail": false,
  "requireVerifiedEmail": false,
  "requirePhone": false,
  "requireLinkedinUrl": false
}
```

# Actor output Schema

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

Every contact that matched the filters, one dataset item each, with the person's profile and their company firmographics.

## `contactsCsv` (type: `string`):

The same contacts as a CSV download, ready for import into a CRM or outreach tool.

## `runSummary` (type: `string`):

Counts for the run: contacts returned, records matched and scanned, shards read, why the run stopped, and the dataset build date.

# 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 = {};

// Run the Actor and wait for it to finish
const run = await client.actor("onion_labs/contacts-search").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 = {}

# Run the Actor and wait for it to finish
run = client.actor("onion_labs/contacts-search").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 '{}' |
apify call onion_labs/contacts-search --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,onion_labs/contacts-search"
        }
    }
}

```

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/bpqLaw9n7rwrYhKuy/builds/rXatdu1DvKnQkOyAU/openapi.json
