Every pitch deck has the sentence. "We sell to heads of marketing at US software companies with 50 to 200 people." The deck has it, the website has it, and the list of people to write to still gets bought from someone, because nobody on the team has turned the sentence into a query.
This post turns it into one. Not a tool tour: one sentence, the filter it becomes, the free count that says how big the market is, the breakdown that shows what the filter really caught, the first twenty rows, seven checks before anyone trusts them, and the weekly run that brings only new people. Every number below is from runs on 4 November 2026 on a free Apify account.
In one paragraph
Two Actors on Apify share one database of people and companies. People Database Search takes the filters and answers mode: "count" and mode: "market" with no per-row charge: how many people match, which titles they really hold, where they work, how many have an address on record. B2B Leads Finder takes the same filters and delivers the rows, each with a work or personal email, paid per lead found; people without an address are skipped free and counted in a note. Both run from the form on the Actor page, from one curl call, or as MCP tools that Claude Code fills in from the sentence.
The sentence, one clause at a time
"Heads of marketing at US software companies with 50 to 200 people" is four filters. Written as the input of the count, with what each field does:
{
"mode": "count", // count = the number, nothing per row
// market = the segment's shape, nothing per row
// people = the rows, paid per person
"countries": ["us"], // where the person lives, two-letter codes, up to 60
"employerIndustries": ["Software Development"], // the current employer's industry, either wording works
"employeeCountMin": 51, // the employer's headcount, both ends inclusive
"employeeCountMax": 200,
"titleKeywords": ["head of marketing", "vp marketing", "vp of marketing",
"chief marketing officer", "cmo"]
// any of these in the current job title; a short one such as
// cmo switches the whole list to whole-word matching
}
Two things a first-timer gets wrong here, and both are visible in the breakdown later. "Head of marketing" as a substring also catches "head of marketing operations". And a title list is a list of spellings: a company that calls the role "VP, Marketing" is in the market and not in your filter until you add the spelling.
The key you hold decides the door
The sentence is one kind of input. If you hold something else, a different Actor on the same database takes it:
| What you hold | Actor | What comes back |
|---|---|---|
| a sentence: titles, size, country, industry | People Database Search, then B2B Leads Finder | counts and the market shape, then leads with an email |
| a list of company websites | Company Employees | the people at each company by role, with contacts on request |
| a list of profile URLs | Email Finder | the addresses on record for each profile |
| a list of email addresses | Email to Profile Finder | the person and employer behind each address |
| one website | Company Enrichment by Domain | the company card: size, industry, HQ, founding year, funding |
| company filters only | Company Database Search | the companies, to feed the domain list above |
The rest of this post stays on the first row.
Count first. It costs nothing
Paste the block into the Actor's form, or send it to the REST endpoint:
curl -X POST "https://api.apify.com/v2/acts/b2bsearch~people-database-search/run-sync-get-dataset-items?token=<APIFY_TOKEN>" \
-H "Content-Type: application/json" \
-d @input.json
Eight seconds, one row:
| Field | Value |
|---|---|
_total |
1,683 |
_totalCapped |
false |
_note |
1,683 people match these filters. A count charges nothing per row |
So the sentence is a market of about 1,700 people today. That number decides the rest: a sequence of 200 a week lasts two months before the segment is exhausted, and the "50 to 200" clause is the one to loosen when it runs out.
One more count worth running the first time. About half of the companies on file carry no headcount at all, and a size filter drops every one of them. With "includeUnknownEmployerSize": true the same box counts 1,747. Only 64 more here, because a company with an industry on record usually has a headcount too, but for small or local segments the gap can be a third of the market.
Read the market before you pull a row
Change mode to "market" and keep everything else. Thirteen seconds, 186 rows, each one a dimension, a value, a count and a share. The rows that change what you do next:
| Dimension | What it showed |
|---|---|
| job title, top five | chief marketing officer 31 %, vp of marketing 24 %, head of marketing 20 %, cmo 4 %, member 4 % |
| job title, further down | advisor 3 %, fractional cmo 2 %, founder 2 %, board member 2 %, marketing advisor 1 %, svp of marketing 1 % |
| seniority | cxo 47 %, vp 35 %, founder 9 %, director 4 %, manager 2 %, other 67 % |
| employer size | 51 to 200: 111 %; 1 to 10: 14 %; 11 to 50: 10 %; 1,001 to 10,000: 9 % |
| contact | personal email on record, at most: 1,240 people, 72 % |
| employer, top entry | a peer community for revenue leaders, 39 positions |
The shares add up to more than 100 % and the note row says why: the breakdown counts current positions, so a person with two jobs is in it twice. That is the first lesson of the market read. These marketing leaders hold about 1.6 current positions each, and the second one is the noise: "member", "executive member", "advisor", "board member" at a community, a council or a startup they advise. The top employer in the whole breakdown is a peer community, followed by a women's executive network and a publisher's council. None of them is a company you sell to, and none of them will be the row's employer in the pull, because the filter matched the marketing title, not the membership. They are there so you know what the second position of your buyer looks like.
The second lesson is in the title list. "Fractional cmo" and "chief marketing officer (fractional)" are forty people who run marketing for several companies at once; "marketing advisor" and "advisor" are seventy-six more. If you sell to in-house leaders, they are not your buyer, and a word in the title separates them.
The third is the email share. At most 72 % of the market has a personal address on record, and the work-address share is lower and only known at pull time. So a list of 100 will be read from around 200 people. Keep that in mind when the count looks small.
Fix the box, count again
Two fields from the market read, and one for freshness:
{
"mode": "count",
"countries": ["us"],
"employerIndustries": ["Software Development"],
"employeeCountMin": 51,
"employeeCountMax": 200,
"titleKeywords": ["head of marketing", "vp marketing", "vp of marketing", "chief marketing officer", "cmo"],
"excludeTitleKeywords": ["fractional", "advisor", "consultant", "interim"],
"updatedAfter": "2026-01-01"
}
| Box | People |
|---|---|
| the sentence as written | 1,683 |
| minus fractional, advisor, consultant, interim in the title | 1,462 |
| and only records refreshed since 1 January 2026 | 785 |
The last line is the one that hurts and the one that saves the campaign. updatedAfter keeps the people whose record was touched this year, which is the cheapest proxy for "still in the job" a database can give you. It halves the market. It also halves the share of rows that bounce or reach someone who left in 2024, which an earlier post measured on an ordinary sales box: 243 of 417 rows were dead before the first email, and the date filter took the "moved" skips to zero.
785 is the number to pull from.
Pull twenty
B2B Leads Finder takes the same box with three field names that read more like a marketer wrote them, plus the email rules:
{
"jobTitles": ["head of marketing", "vp marketing", "vp of marketing",
"chief marketing officer", "cmo"], // same list as titleKeywords above
"countries": ["us"],
"industries": ["Software Development"], // same as employerIndustries
"companySizeMin": 51, // same as employeeCountMin / Max
"companySizeMax": 200,
"excludeJobTitles": ["fractional", "advisor", "consultant", "interim"],
"updatedAfter": "2026-01-01",
"emailType": "work", // work = an address at the current employer only; any = work, else personal
"maxPerCompany": 1, // the rest at the same company are skipped free and counted
"maxResults": 20 // stop after this many leads found; misses do not count toward it
}
Eight seconds. Twenty rows with a work address and one note row:
| Row | Count | Charged |
|---|---|---|
| leads with a work address at the current employer | 20 | yes, per lead |
skipped_no_email: matched, no work address on record |
17 | no |
So the Actor read 37 people to deliver 20, and said so. The twenty, without names:
| Title | Company size | Industry | Location | Record updated | |
|---|---|---|---|---|---|
| Chief Marketing Officer | 117 | Software Development | Massachusetts | work | Apr 2026 |
| Chief Marketing Officer | 89 | Software Development | New York | work | Mar 2026 |
| VP of Marketing | 165 | Software Development | Chicago area | work | Mar 2026 |
| Chief Marketing Officer | 151 | Data Security Software Products | Boston area | work | Apr 2026 |
| Chief Marketing Officer | 123 | Software Development | San Diego area | work | Mar 2026 |
| Head of Marketing | 156 | Software Development | Washington | work | Apr 2026 |
| VP of Marketing | 198 | Software Development | California | work | Apr 2026 |
| Chief Marketing Officer | 138 | Software Development | Maryland | work | Mar 2026 |
| Head of Marketing | 76 | Software Development | Florida | work | Apr 2026 |
| Chief Marketing Officer | 51 | Software Development | Los Angeles area | work | Mar 2026 |
| Global Chief Marketing Officer (CMO) | 197 | Software Development | United States | work | Mar 2026 |
| Chief Marketing Officer | 97 | Software Development | San Francisco | work | Mar 2026 |
| Head of Marketing and Communications | 175 | Software Development | New York area | work | Apr 2026 |
| Head of Marketing | 85 | Software Development | California | work | Mar 2026 |
| VP of Marketing | 62 | Software Development | Massachusetts | work | Apr 2026 |
| Head of Marketing | 51 | Software Development | California | work | Apr 2026 |
| Head of Marketing | 159 | Software Development | Texas | work | Mar 2026 |
| Chief Customer and Marketing Officer | 75 | Software Development | Boston area | work | Apr 2026 |
| VP of Marketing | 95 | Software Development | Seattle area | work | Mar 2026 |
| Head of Marketing | 95 | Software Development | Boston area | work | Mar 2026 |
Sixteen of the twenty also carry a personal address in personalEmail, and six have a verified mobile on record, flagged in phoneOnRecord and delivered only when you ask for phones. Eleven of the twenty live in California or Massachusetts, which is the software industry, not the filter.
What a row holds: fullName, jobTitle, headline, email, emailType, workEmail, personalEmail, companyName, companyDomain, companySize, companyIndustry, location, countryCode, the public profile link, phoneOnRecord, updatedAt, a stable personId, and two columns that only appear when they matter: primaryTitle and primaryCompany when the profile lists another current job first, and _employerUnconfirmed when the profile no longer shows an open position at the matched company.
Seven checks before you trust a list
A list is not good because it came back. It is good because it passes these, and each one is a field or a column in the rows above:
| Check | Where it lives | On the twenty |
|---|---|---|
| 1. The title is the one in the sentence |
jobTitle; wrong spellings go to excludeJobTitles
|
all 20 carry one of the five titles; three are longer variants such as "Chief Customer and Marketing Officer" |
| 2. The person is still at that company |
_employerUnconfirmed absent; primaryTitle empty |
no row unconfirmed; 3 rows list another current job first (a seat on a peer-community board, a senior role at a larger software company, a founder's side business) |
| 3. The address is at that company |
workEmail domain equals companyDomain
|
18 of 20; one is the brand domain of the same company, one is the person's main employer rather than the matched side seat |
| 4. The record was refreshed this year |
updatedAt, set by updatedAfter
|
all 20 between 10 March and 9 April 2026 |
| 5. One person per company | maxPerCompany: 1 |
20 rows, 20 domains |
| 6. No customers, no competitors | excludeCompanyDomains |
not used in this run; paste your CRM's domain list |
| 7. The list matches the count | the count before the pull, the note rows after | 785 counted; 37 read for 20 delivered, so a full walk should give roughly 400 with a work address |
Checks 2 and 3 are the ones a spreadsheet cannot do for you and the rows make easy: two columns, three minutes, and the two people whose marketing seat is a side job are out of the sequence before it starts.
Next week, only the new people
Keep the dataset id of the first run. Add it to excludeDatasets and run the same box again:
{
"...": "the same box as above",
"maxResults": 10,
"excludeDatasets": ["<dataset id of last week's run>"]
}
Eleven seconds. Ten new rows, none of them in the first twenty, checked by personId. The note row says 37 people were skipped for having no work address: the walk starts at the top of the segment again, passes the twenty already delivered without a note, counts the seventeen no-address people a second time and twenty new ones. Put the run on a weekly schedule, add each week's dataset to the list, and the sequence gets only people it has never seen, and nobody is paid for twice.
The same in words
I gave a Claude Code session the two Actors pinned and the sentence, with the steps above as rules: count, read the market and name two things that should change the filters, fix and count again, pull ten with work addresses and one person per company under a spend cap, then answer the seven checks with a yes or no from the rows. No shell or file tools, no names or addresses in the report, and a short saved note of mine about these Actors.
Twelve turns, five minutes, four Actor runs. What it did that I had not told it to:
- wrote seven title spellings instead of my five, adding "vice president marketing", "director of marketing" and "marketing director", and set the floor at 50. Its count was 2,646 against my 1,683, and the gap is the directors
- read the 187 market rows in one call with a
fieldslist, then ran all of its fixes as one free count withsegments, so each change shows its own number: the title exclusions took 2,646 to 2,267, the membership words and three community domains barely moved it (one person: memberships are second positions, and the title words had already handled them), seniority plus a current position on record took it to 1,691, and the date filter to 885. It kept the 1,691 box and said the date filter would halve it again - passed the spend cap in the call options, then wrote that the field is not in the Actor's schema and it could not confirm the cap had applied. True on both counts; the run delivered 10 and skipped 9 for no work address
- read the rows with
headlineandprimaryTitlenext to the title and flagged three of ten by hand: a chief marketing officer whose headline reads fractional CMO, independent investor and board member, which a title exclusion cannot see; a COO/CMO at a self-driving software company that, from what it knew of the company, shut down in 2024, while the record had been refreshed in April 2026; and a director of marketing and community at a game studio, which the industry filter accepts as software - noticed what I had not: the breakdown's positions at companies of 1 to 10 and of 201 and more are second jobs, a subsidiary page of a hardware giant sits in the 51 to 200 band with its own headcount, and the database's size bucket is 51 to 200, not 50
Its seven checks: eight of ten on the title and on the employer, ten of ten on the address (with the dead company's address "almost certain to bounce") and on freshness, though no record was newer than seven months, one per company yes, customers and competitors not checked for lack of a list, nine of ten on the numbers. Its advice for the next pull: paste the CRM's domains into excludeCompanyDomains, set updatedAfter, and exclude "fractional" by headline through People Database Search, because the leads Actor has no headline exclusion.
That is the seven checks done by something that reads every column of every row, in the time it takes to open the spreadsheet.
What this does not do
The count is the ceiling, not the list. Between the count and the rows sit the email test, the per-company cap and your own exclusions, and on this segment about half of the people read had a work address on record.
Addresses are on record, not verified at run time, and never guessed from a name pattern. Run your usual verifier before a big send.
updatedAfter is a proxy for "still there", not a promise. It keeps records touched this year; a person who moved last month and whose record was refreshed in March still passes. Check 2 catches part of that, a reply catches the rest.
The market breakdown counts positions, so its shares pass 100 %, and its title and employer lists are close rather than exact. The whole-word total on its first row is the number the pull will walk.
And the free Apify plan caps a run at a hundred rows from the Console and twenty-five through the API, and an account at five hundred rows a month on each of these Actors. Counts and market reads stay outside the cap. Any paid plan lifts it.
FAQ
How many filters is enough? The four in the sentence. Add one more only when the count says the market is too big to work, or the breakdown shows a group you do not want. Every extra filter also drops the people whose record lacks that field.
Why is the count bigger than the list? The count is everyone who matches the filters. The list is the ones with an address of the type you asked for, one per company, not in your exclusions. Read the note rows: they say how many were skipped and why, and they cost nothing.
Work or personal email? work when the message is about their job and the domain must match the company. any when you want the person more than the mailbox: a new leader's work address is often not on record yet, and a personal one still reaches them.
What if the count is zero? Usually the industry or the size. Try the count without the industry, then without the size, and see which one emptied the box. A title spelled one way in your head and another way on profiles is the other usual cause, and the market read on a wider box shows the spellings.
Do I need Claude Code? No. The input is JSON: the form on the Actor page, a schedule, n8n, Make, Zapier or a curl call all take it. Claude Code is the fastest way to turn the sentence into the filters and to compare two boxes by their free counts.
For AI agents reading this
Pin both Actors in the server URL so the client loads only these tools:
claude mcp add --transport http apify "https://mcp.apify.com?tools=b2bsearch/people-database-search,b2bsearch/b2b-leads-finder" --header "Authorization: Bearer <APIFY_TOKEN>"
Any other MCP client (Cursor, Claude Desktop, ChatGPT, LangChain, CrewAI, the OpenAI Agents SDK) takes the same server:
{
"mcpServers": {
"apify": {
"type": "http",
"url": "https://mcp.apify.com?tools=b2bsearch/people-database-search,b2bsearch/b2b-leads-finder",
"headers": { "Authorization": "Bearer <APIFY_TOKEN>" }
}
}
}
The tools are b2bsearch--people-database-search and b2bsearch--b2b-leads-finder (double hyphen). The workflow:
- turn the sentence into
countries,employerIndustries,employeeCountMin,employeeCountMax,titleKeywords; call People Database Search withmode: "count", thenmode: "market"; both are free of per-row charges - from the market rows (
dimension,value,count,share) take the title spellings to add or to put inexcludeTitleKeywords, and read the email share - call B2B Leads Finder with the same box under its names (
jobTitles,industries,companySizeMin,companySizeMax,excludeJobTitles),emailType,maxPerCompany,updatedAfter, a smallmaxResults, andcallOptions.maxTotalChargeUsdon the call - read the rows with
get-dataset-itemsand afieldslist; rows whose_statusis notfoundare free notes that say why - rerun with
excludeDatasetsfor new people only
Platform docs: Apify MCP server and apify.com/agents.md. Per-plan pricing is on each Actor page.
Related posts in this series: B2B lead list by job title, company size and country, with work emails, 243 of 417 leads in a typical ICP box were dead before the first email, Clay alternative in Claude Code: one ICP, companies, leads and jobs and 30 ready-made B2B data tasks on Apify for inputs you can open and run.
Top comments (0)