# Recruiter Finder - Talent Acquisition Contacts by Company (`b2bdata/recruiter-finder`) Actor

Find recruiters: give company domains (staffing agencies too) or countries, get one row per recruiter or talent acquisition person: name, title, company, location, profile URL, email flags. Free plan: 100 domains per run. Pay per recruiter delivered, empty lookups free. No login or cookies.

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

## Pricing

from $1.40 / 1,000 recruiter 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

## Recruiter Finder - Talent Acquisition Contacts by Company

Recruiter finder: give a list of company domains, staffing and recruiting agencies included, and get one row per recruiter or talent acquisition person found at each company, with title, company, location, profile URL and flags for whether an email is on file. Or give countries and search by job title across companies. A person with several profiles or positions is one row. No login, no cookies, no start fee. You pay only for a recruiter row you receive.

**No login | Domains, URLs or emails in | One recruiter per row | Your own title words | Filter by country | Per-domain cap so one big company cannot run away | Unknown domains = free | JSON/CSV export**

> **Domains in, recruiters out.** It starts from the domain you already hold in a CRM or a target list, not from a company name, and keeps only people whose current job title contains the words you choose (default: recruiter, talent acquisition, sourcer). It does not return email addresses, only whether one is on file. For emails chain it with [email-finder](https://apify.com/b2bdata/email-finder).

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

***

### 💳 Pricing and plans

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

| Event | When it is charged | Free plan | Gold plan |
|---|---|---|---|
| `recruiter` | A recruiter row is delivered to your dataset | $1.896 | $1.396 |

Bronze and Silver prices are on the Pricing tab. Higher Apify plans only lower the price. Empty runs, unknown domains, domains with no recruiters on file, failed lookups, rows skipped by the checks below, unreadable entries and notices are free, and there is no start fee.

**Plan-credit math.** The free plan's $5 monthly credit covers about 2,630 recruiters at the Free price.

#### Success-only billing

You are charged only for a recruiter row you receive. A domain with no company on file, or with no recruiter among its people, gets one free notice row (`reason: "no_recruiters"`). A hosting or platform domain shared by too many companies gets a free notice (`domain_too_broad`). An entry that is not a domain, a repeat, or a lookup that fails is listed in `MISSES` with its position and a reason and costs nothing. A second profile or position of a recruiter you already received is skipped and never charged.

#### The free plan

Free Apify accounts get up to 100 domains 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 domains 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.

#### Keep a run small while you test

Two caps protect you. `maxPeoplePerDomain` (default 100, maximum 1,000) stops any one company from using up the run, and `limit` (default 100, maximum 10,000) caps the whole run. Try one domain with `limit: 5`, read the rows, then scale up.

***

### ⚡ Why this recruiter finder?

- **Start from the domain.** `shopify.com`, `https://www.shopify.com/careers` and `jane@shopify.com` all become `shopify.com`. No company-name matching and no LinkedIn URL needed.
- **Your own title words.** `titles` takes up to 10 words, for example `["technical recruiter", "head of talent"]`. The default set is recruiter, talent acquisition and sourcer.
- **Real employers only.** A row is delivered only when the matching position sits at a company that owns the domain, so a profile that lists a free-mail address or a personal mailbox as its employer is skipped, not charged.
- **One row per person.** The data holds one record per profile and position. The same name at the same company, or the same profile twice, is delivered once.
- **Success-only, no start fee.** Unknown domains, bad entries, repeats, skipped rows and notices are free.

***

### 🎯 Use cases: reach the people who hire

- **Sell to recruiters.** Run a list of staffing agencies or in-house talent teams to build a prospect list for HR tech, sourcing tools or job boards.
- **Recruiter outreach for candidates.** List the talent acquisition people at the companies you want to work for.
- **Account mapping.** For each target account, find who runs hiring.
- **Agent workflows.** An AI agent holds company domains and needs the recruiters at each one in a single call.

***

### 🚀 Quick start: recruiters for a list of domains

#### 1. One domain

```json
{ "items": ["shopify.com"], "limit": 5 }
```

#### 2. Technical recruiters at several companies, in two countries

```json
{ "items": ["shopify.com", "https://www.stripe.com/jobs", "airbnb.com"], "titles": ["technical recruiter", "sourcer"], "countries": ["us", "ca"], "maxPeoplePerDomain": 25, "limit": 75 }
```

Each entry becomes its bare domain before the lookup.

#### 3. No domains: recruiters in a country

```json
{ "countries": ["de"], "limit": 50 }
```

One search by title words and country across all companies, capped by `limit`. Each row carries the company domain of the position it matched.

***

### ⚙️ Inputs

| Key | Type | Default | What it does |
|---|---|---|---|
| `items` | array of strings | none | Company domains, website URLs or email addresses. A company name is not a domain and is listed in `MISSES` as `invalid_domain`. The same domain written twice is looked up once. Free plan: 100 domains per run. Give `items`, `countries`, or both. |
| `titles` | array of strings | recruiter, talent acquisition, sourcer | Title words, 3 to 64 characters, up to 10. A person is kept when a current job title contains one of them, in any letter case. |
| `countries` | array of strings | anywhere | Two-letter country codes, up to 5. People with no country on file never match. |
| `maxPeoplePerDomain` | integer | 100 | Most recruiters delivered for one domain. Maximum 1,000. |
| `limit` | integer | 100 | Most recruiters delivered in the whole run, which also caps what is charged. Maximum 10,000. Domains 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 recruiter. Rows for one domain stay together, domains keep input order. Empty values are left out.

| Key | Type | Meaning, and when it is missing |
|---|---|---|
| `fullName`, `firstName`, `lastName` | string | The person's name. A profile with no name at all is skipped |
| `jobTitle` | string | Title of the position this row comes from |
| `seniority` | string | `cxo`, `vp`, `director`, `manager`, `founder` or `other`, grouped from the title. Most recruiters are `other` |
| `company` | string | Company name of that position |
| `companyDomain` | string | Domain of that company |
| `headline` | string | The profile headline. Missing when the person has none |
| `location` | string | Place on the profile |
| `country` | string | Two-letter country code. Missing when not on file |
| `startYear` | integer | Year the person started the position. Missing when it is not on file |
| `connections` | integer | Connection count on the profile |
| `hasWorkEmail` | boolean | An address on the employer's domain is on record. Missing when that could not be checked |
| `hasPersonalEmail` | boolean | A personal mailbox is on record. Missing when that could not be checked |
| `profileUrl` | string | Public profile URL |
| `slug` | string | Profile slug |
| `updatedAt` | string | When the profile was last refreshed in the data |
| `audit` | object | Only with `includeAudit`: `requestId`, `latencyMs`, `inputRaw` |

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

```json
{
  "fullName": "Ann Example",
  "firstName": "Ann",
  "lastName": "Example",
  "jobTitle": "Senior Technical Recruiter",
  "seniority": "other",
  "company": "Example Inc",
  "companyDomain": "example.com",
  "headline": "Hiring engineers",
  "location": "Toronto, Canada",
  "country": "CA",
  "startYear": 2021,
  "connections": 500,
  "hasWorkEmail": true,
  "hasPersonalEmail": false,
  "profileUrl": "https://linkedin.com/in/ann-example",
  "slug": "ann-example",
  "updatedAt": "2025-01-02T03:04:05Z"
}
```

#### How to read the rows

- **Who is on the list.** People the data holds as currently working at the company that owns the domain, with a current job title that contains one of your title words. It is not the company's own staff list, and it is not a count of everyone who recruits there.
- **Title words match inside titles.** `recruiter` also matches "Recruiter Coordinator" and "Senior Recruiter, Sales" because the word sits inside the title. Check `jobTitle` before you act on a row, and use more specific `titles` for a tighter list.
- **Repeat and look-alike profiles.** The data can hold the same person more than once. Rows with the same name at the same company, or the same profile, are delivered once. Distinct names with near-identical profiles can still appear; open the profile before you rely on it. On a large employer this is visible: one search for a well-known payments company returned a group of "Senior Lead Recruiter" profiles that look copied from each other.
- **Plain data, no ranking.** Rows come in the order the data returns them, not by seniority or relevance.
- **Email flags are not emails.** `hasWorkEmail: true` means an address is on record, not that we return it.
- **Several domains.** A person found at two domains in your list stays with the first domain.

#### Other records in the run

- **`MISSES`** (key-value store): `[{index, input, reason}]` for every entry without a result row. Reasons: `no_recruiters`, `domain_too_broad`, `invalid_domain`, `duplicate_input` (the same domain as an earlier entry; `duplicateOf` names it), `not_looked_up_limit` (the run `limit` was reached first), or `Upstream HTTP <code>`.
- **`errors-recruiter-finder`** (key-value store): the lookups that failed for a technical reason, so you can re-run only those domains. A note `(partial: ...)` means some recruiters were already delivered.
- **`RUN_SUMMARY`**: counts (requested, valid, looked up, recruiters delivered, repeats skipped, rows skipped by the checks) and, per domain, how many were delivered.
- **Notice rows** (`type: "notice"`) appear for a domain with no recruiters, a platform domain, a free-plan list longer than 100 (`free_plan_limit`), a reached `limit` (`limit_reached`), a reached spend limit (`charge_limit_reached`) or invalid input (`invalid-input`). Notices are never charged.

#### What is NOT returned

- Email addresses or phone numbers (use [email-finder](https://apify.com/b2bdata/email-finder))
- Work history, skills or education (use [linkedin-profile-by-url](https://apify.com/b2bdata/linkedin-profile-by-url))
- Open jobs, hiring volume or which roles a recruiter is hiring for
- A company's full employee list (use [people-at-company](https://apify.com/b2bdata/people-at-company))

***

### 🛡️ Reliable results

**Our data promise.** We work to keep the data current, and we stand behind it. If a recruiter has moved on, a company is missing 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 data is free and visible.** Every domain without a row is in `MISSES` with its position and a reason and is never charged.
- **Repeats are charged once.** The same domain written twice, or the same recruiter found twice, is one row.
- **Each row is saved as it is delivered.** A run that stops, or a lookup that fails halfway through a company, 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 title word that is too short, a bad country code or a limit above its maximum stops the run before any charge.

***

### 🤖 For AI agents (MCP)

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

> "Use b2bdata/recruiter-finder with items=\["shopify.com"], titles=\["technical recruiter"] and limit=20. Return fullName, jobTitle, company and profileUrl."

**When to use it.** You hold company domains and need the recruiters or talent acquisition people at each, or you need recruiters in a country.
**When not to.** You need email addresses (use b2bdata/email-finder), every employee of a company (use b2bdata/people-at-company) or a full profile (use b2bdata/linkedin-profile-by-url).
**Input contract.** `items` array of domains, URLs or emails, and/or `countries` (at least one of the two); optional `titles` (3-64 characters each, up to 10), `maxPeoplePerDomain` 1-1,000 (default 100), `limit` 1-10,000 (default 100).
**Output contract.** One row per recruiter with `fullName`, `jobTitle`, `company`, `companyDomain`, `profileUrl`. Output is schema-stable. Domains without data are a free notice row and listed in `MISSES`.
**Smoke test.** `limit: 1` on a well-known domain returns one row.
**Error cases.** `No valid domains` means no entry was a domain. `remove unsupported fields`, a bad `titles` or `countries` value or a bad number 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 `companyDomain` and map `fullName`, `jobTitle` and `profileUrl` to contact fields.

***

### 🕐 Schedule examples

- **Weekly agency refresh.** A schedule with a saved task that points at your list of staffing-agency domains.
- **New-account mapping.** A webhook from your CRM starts a run with the new domains.

***

### 🔗 Related actors

- `b2bdata/people-at-company`: every person found at a company domain, filtered by seniority. [Open](https://apify.com/b2bdata/people-at-company)
- `b2bdata/email-finder`: a work email for a person at a domain. [Open](https://apify.com/b2bdata/email-finder)
- `b2bdata/linkedin-people-search`: search people across companies by title and location. [Open](https://apify.com/b2bdata/linkedin-people-search)
- `b2bdata/headcount-by-department`: how many people a company has per team. [Open](https://apify.com/b2bdata/headcount-by-department)

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

***

### ❓ FAQ

#### Data and coverage

**How do I find recruiters at a company by domain?**
Put the domain in `items` and run. You get one row per recruiter with name, title, company, location and profile URL.

**Can I find recruiters at staffing agencies?**
Yes. An agency is a company like any other: put its domain in `items`. Add `countries` to keep recruiters based in the places you target.

**Can I use my own job titles?**
Yes. Set `titles`, for example `["technical recruiter", "head of talent", "recruiting manager"]`. Words match inside the title, in any letter case, and must be 3 to 64 characters.

**Why did I get fewer recruiters than the company has?**
Rows stop at `maxPeoplePerDomain` (default 100) and at `limit` (default 100 for the whole run). Raise them. People with no matching title word, no name, or a position at a company that does not own the domain are skipped.

**Why is a domain missing from the results?**
It is in `MISSES` with a reason: nobody with your title words on file, a platform domain shared by many companies, or an entry that was not a domain.

**Do I get email addresses?**
No. `hasWorkEmail` and `hasPersonalEmail` say whether an address is on record. Use `b2bdata/email-finder` to find one.

#### Pricing

**What do I pay when a domain has no recruiters?**
Nothing. There is no start fee, and unknown domains, bad entries, repeats, skipped rows and notices are free.

***

### ⚖️ Legal

Use for B2B prospecting only. Comply with all applicable laws including GDPR, CCPA, and CAN-SPAM.

The rows contain public professional information. You are responsible for how you contact the people in them.

***

### 🛟 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/recruiter-finder/changelog.md

# Actor input Schema

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

Company domains to find recruiters at; staffing and recruiting agencies work the same way. Example: \["shopify.com", "https://www.stripe.com/jobs", "jane@airbnb.com"]. A website URL or an email address is reduced to its domain; a company name is not a domain and is listed in the MISSES record as invalid_domain. Each domain is looked up once. A domain with no recruiters on file returns one free notice row. Leave empty only when you give countries. Free plan: 100 domains per run.

## `titles` (type: `array`):

Title words, 3 to 64 characters, up to 10; a person is kept when a current job title contains one of them, in any letter case. Example: \["recruiter", "talent acquisition", "sourcer", "head of talent"]. Leave empty for the default set: recruiter, talent acquisition, sourcer. A short word such as "hr" is refused. Broad words also match unrelated titles, for example "talent" matches "talent show host".

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

Two-letter country codes of where the person is based, up to 5. Example: \["us", "gb"]. With domains, only recruiters based there are kept. With no domains, this runs one search by title words and country, capped by limit. People with no country on file never match a country filter.

## `maxPeoplePerDomain` (type: `integer`):

Most recruiters delivered for any one domain, so a large employer cannot use up your run. Default 100, maximum 1000. A person with several profiles or positions counts once.

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

Most recruiters delivered in the whole run, which also caps what is charged. Domains not reached are listed in MISSES and are not charged. Default 100, maximum 10000.

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

Adds requestId, latencyMs and inputRaw to each row under audit. Omit for clean rows.

## Actor input object example

```json
{
  "items": [
    "shopify.com"
  ],
  "maxPeoplePerDomain": 100,
  "limit": 100,
  "includeAudit": false
}
```

# Actor output Schema

## `recruiters` (type: `string`):

One deduplicated row per recruiter: name, title, company, domain, location, profile URL and email flags

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

Counts, recruiters per domain and stop reason 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": [
        "shopify.com"
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("b2bdata/recruiter-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 = { "items": ["shopify.com"] }

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

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,b2bdata/recruiter-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/IJhP3d8GPlesLLytw/builds/X4Nkvwy3DYvm144P1/openapi.json
