DEV Community

Cover image for Apollo alternative in Claude Code: 7 lead lists from one Apify Actor
Kevin Hall
Kevin Hall

Posted on

Apollo alternative in Claude Code: 7 lead lists from one Apify Actor

Apollo and Sales Navigator sell the same thing from two directions: a search box over people, and a way to get the matches out. A seat per rep, a quota per month, an export that needs credits or a browser extension that needs cookies. The lists that come out of the box are not exotic. A sales team pulls the same seven shapes for years: the ICP box, the box minus the accounts it already has, the weekly "net new", the account list, the keyword list, the territory, and the search a colleague built last quarter and pasted into chat.

On 2026-10-26 I pulled those seven lists from Claude Code with one Apify Actor, B2B Leads Finder, plus two helpers for the free count and the company record. No Apollo seat, no Sales Navigator seat, no login anywhere: the Actor reads a database of professional records, and it charges per lead delivered with a live email. Every number below is from that day's runs, each capped at 20 rows because I ran them on a free Apify plan. The rows shown carry no names, no addresses and no employer names.

What people actually pull

I know these are the seven shapes because the Actor's own request log says so. Of about 3,500 searches it ran for other people in the last week, with the filter fingerprint logged (field names and list sizes, never values):

The search carried Share of searches
a country 84 %
job titles 75 %
a company-size band 69 %
headline keywords ("what I do", not the title) 49 %
a city or region 36 %
an industry list 30 %
excluded titles 30 %
a list of company domains (an account list) 21 %
datasets of earlier runs to skip (the weekly delta) 18 %
excluded company domains 13 %
a pasted Apollo URL under 1 %

The searches without a country are the account lists: a domain list anchors a search on its own. And where the runs came from, over a two-week sample of 19,000 requests that carried an origin: 53 % from the API, 34 % from an MCP client, 13 % from the Apify Console. A third of this Actor's traffic is an agent filling the box.

The wiring, once

Apify runs an MCP server at https://mcp.apify.com; the tools parameter pins the Actors a session may reach. In Claude Code:

claude mcp add --transport http apify \
  "https://mcp.apify.com?tools=b2bsearch/b2b-leads-finder,b2bsearch/people-database-search,b2bsearch/domain-to-company"
Enter fullscreen mode Exit fullscreen mode

Any other MCP client takes the same URL in its server config, with OAuth on first connect or an Authorization: Bearer <APIFY_TOKEN> header:

{
  "mcpServers": {
    "apify": {
      "url": "https://mcp.apify.com?tools=b2bsearch/b2b-leads-finder,b2bsearch/people-database-search,b2bsearch/domain-to-company"
    }
  }
}
Enter fullscreen mode Exit fullscreen mode

The Actors appear as tools named b2bsearch--b2b-leads-finder, b2bsearch--people-database-search and b2bsearch--domain-to-company (the slash becomes a double hyphen); the server adds get-dataset-items and get-actor-run next to them. A tool call returns the run and its datasetId; the rows come from get-dataset-items.

The first two lists below were pulled by Claude Code itself, from one sentence each, in a session I watched: the inputs are the ones it built. The other five I typed into the same fields by hand, because the point of those is the field, not the agent.

1. The ICP box

The sentence I gave Claude Code: "I sell to marketing leaders at mid-size US software companies. Up to 20 heads of marketing (head of marketing, VP marketing, CMO) at US software companies with 51–200 employees, one per company, work email required. Size it first with a free count."

It called two tools at once. The count, on People Database Search, charges nothing per row:

{
  "mode": "count",
  "countries": ["us"],
  "titleKeywords": ["head of marketing", "vp marketing", "vice president marketing", "cmo", "chief marketing officer"],
  "employeeCountMin": 51,
  "employeeCountMax": 200,
  "employerIndustries": ["Software Development"]
}
Enter fullscreen mode Exit fullscreen mode

1,506 people. Then the pull, the same box in the lead Actor's field names:

{
  "countries": ["us"],
  "jobTitles": ["head of marketing", "vp marketing", "vice president marketing", "cmo", "chief marketing officer"],
  "companySizeMin": 51,
  "companySizeMax": 200,
  "industries": ["Software Development"],
  "emailType": "work",
  "maxResults": 20,
  "maxPerCompany": 1
}
Enter fullscreen mode Exit fullscreen mode

49 seconds, 20 leads delivered. The run read 62 people to deliver them: 26 matched but had no work address at the current employer on record, 16 had already left the role the search matched. Both groups were skipped free and reported in two note rows at the end of the dataset. That denominator is the number an Apollo export never shows you.

Title Company size Industry Country Email Freshness
Chief Marketing Officer 117 Software Development us work updated_1y
Vice President Marketing 187 Software Development us work updated_1y
Head of Marketing 76 Software Development us work updated_1y
Chief Marketing Officer 151 Data Security Software Products us work updated_1y
Co-Founder, CMO & CCO 197 Software Development us work older

Half the 20 rows are updated_1y, half older; 18 of 20 carry a personal address next to the work one; 10 have a mobile number on record. One of 20 titles holds none of the five phrases ("Marketing / Demand Generation"): the matcher reads tokens, not strings, so a review line before the first email is part of the recipe, as in every post of this series.

The free count also answers the question a sales lead asks before buying the seat: the same box in any industry is 12,205 people; with companies whose headcount is not on file added back (includeUnknownEmployerSize) it is 36,720, three times the box, because about half the companies on record carry no headcount and a size filter drops all of them; records refreshed in 2026 alone are 7,197.

2. The account list

Sales Navigator's account list is companyDomains here. The sentence: "Here are 12 target accounts by domain (notion.so, figma.com, airtable.com, asana.com, monday.com, miro.com, loom.com, calendly.com, intercom.com, zapier.com, webflow.com, gong.io). Confirm each resolves to a company record, then pull up to 2 sales leaders per company, up to 20, any email type."

Claude Code ran Domain to Company and the lead pull in parallel, since neither waits for the other:

{
  "companyDomains": ["notion.so", "figma.com", "airtable.com", "asana.com", "monday.com", "miro.com",
                     "loom.com", "calendly.com", "intercom.com", "zapier.com", "webflow.com", "gong.io"],
  "jobTitles": ["head of sales", "vp sales", "chief revenue officer", "director of sales"],
  "maxPerCompany": 2,
  "maxResults": 20,
  "emailType": "any"
}
Enter fullscreen mode Exit fullscreen mode

Domain to Company returned a record for all 12 in 17 seconds. Ten were the company you would expect, with headcount bands from 51–200 to 1,001–5,000, one of them plainly understated against the company's public size. Two were not: figma.com resolved to a community chapter's record (a "Friends of …" group, 223 people, headquartered in Türkiye), and webflow.com to a product page's record with 4 employees. Several records share a domain and the Actor picks one; each row carries _matchCandidates for exactly this case. The agent reported both rows as if they were the companies. It had no reason not to; the check is yours, and it is one line: does the headcount band look like the company you know?

The lead pull delivered 14 in 34 seconds from 26 read: 6 had no reachable address, 2 had left the role, 4 were over the two-per-company limit. The 14 are regional heads, which is what "head of sales" means inside a 2,000-person company: Australia, LATAM, Brazil, France, Ireland, the Netherlands, Singapore, and seven in the US.

Title Company size Country Email Freshness
Chief Revenue Officer 926 us work updated_1y
Director of Sales, North America 3,897 us work updated_1y
Head of Sales, Americas 1,099 us work updated_1y
Head of Sales, Australia 4,408 au work updated_1y
Head of Sales, APAC 2,601 sg personal updated_1y

The free count on the same 12 domains: 28,458 people work at them, 66 carry one of the four titles, 42 of those in the US. If the territory is the US, countries: ["us"] belongs in the input, and the count said so before the pull.

3. The keyword list: what people say they do

The box above reads the job title. Sales Navigator's keyword field reads the whole profile; here keywords reads the headline, the line where people describe their practice. I counted "revops" in the US and UK at companies of 51–1,000 people, headline against title, with segments, which compares up to ten filters in one free run:

Where "revops" or "revenue operations" appears People
in the headline 7,395
in the job title 8,246
in both 4,489
in the headline, not in the title 2,746

A title box misses 2,746 people who say they do revenue operations under a title that says "Director, Strategic Analysis" or "Marketing Operations Lead". The pull:

{
  "countries": ["us", "gb"],
  "keywords": ["revops", "revenue operations"],
  "companySizeMin": 51,
  "companySizeMax": 1000,
  "emailType": "any",
  "maxResults": 20,
  "maxPerCompany": 1
}
Enter fullscreen mode Exit fullscreen mode

25 seconds, 20 delivered from 50 read (25 without a reachable address, 5 moved). 11 of the 20 titles say revenue operations, 9 do not; that is the gap the keyword field exists for. Two things to know before trusting it. Rows arrive grouped by country, the sorted head of the segment, so a 20-row run on two countries is 10 and 10, not a sample. And 5 of the 20 rows show a headline without the phrase: the match was made on the record, the row shows the headline as last refreshed, and the two can differ. Freshness on those rows is the tell.

4. The territory

Apollo's "person location" and Sales Navigator's geography radius are cities, read as text inside the chosen countries. Sales leaders at 11–1,000-person companies in four Texas cities:

Segment People
whole US, same titles and sizes 128,413
Austin, Dallas, Houston, San Antonio together 7,185
Dallas 2,787
Houston 2,014
Austin 1,767
San Antonio 617
{
  "countries": ["us"],
  "cities": ["austin", "dallas", "houston", "san antonio"],
  "jobTitles": ["director of sales", "vp sales", "head of sales", "sales director"],
  "companySizeMin": 11,
  "companySizeMax": 1000,
  "emailType": "any",
  "maxResults": 20,
  "maxPerCompany": 1
}
Enter fullscreen mode Exit fullscreen mode

14 seconds, 20 delivered from 37 read; all 20 locations are in the four cities (9 Austin, 8 Dallas, 3 Houston, none from San Antonio in a 20-row head). Half work, half personal addresses, because emailType: any takes a personal address when no current work address is on record. Three of 20 titles carry none of the four phrases (a marketing director, a chief commercial officer, a vice president of a portfolio company): the title matched on another current position of the same person, and the review line catches them.

5. The box minus your customers, your competitors and the juniors

Apollo's exclusion filters are three fields here: excludeJobTitles, excludeCompanyDomains, and, on the count Actor, excludeLowSignal. I measured what each removes from the 12,205-person marketing box of list 1 (any industry):

Box People
as is 12,205
minus assistant, coordinator, intern, junior, associate, specialist 11,802
minus five customer and competitor domains as well 11,801
minus low-signal profiles (under 10 connections, no career start) instead 11,129

On a senior box the title exclusions barely bite, 3 %; they matter when the title list is one word ("marketing"). Five excluded domains removed one person, which is the right answer for a mid-size box and a list of large vendors. The exclusion that moved the number is excludeLowSignal, 673 people, the self-declared "CMO at a famous brand" profiles.

{
  "countries": ["us"],
  "jobTitles": ["head of marketing", "vp marketing", "vice president marketing", "cmo", "chief marketing officer"],
  "companySizeMin": 51,
  "companySizeMax": 200,
  "excludeJobTitles": ["assistant", "coordinator", "intern", "junior", "associate", "specialist"],
  "excludeCompanyDomains": ["hubspot.com", "salesforce.com", "gong.io", "zoominfo.com", "apollo.io"],
  "emailType": "work",
  "maxResults": 20,
  "maxPerCompany": 1
}
Enter fullscreen mode Exit fullscreen mode

20 seconds, 20 delivered from 71 read (35 no work address, 16 moved). Three of the 20 work addresses sit on a domain that is not the row's company domain. One is a rebrand, the old and the new domain of one company. One is an address at a different company than the row's employer, the kind that bounces or lands with a stranger. One is a profile whose company domain is a link-in-bio page. The address-domain-versus-company-domain check is the cheapest bounce test there is, and it is one column against another.

6. The weekly delta

Apollo's "net new" needs Apollo to remember what you exported. Here the memory is the dataset: excludeDatasets takes the ids of earlier runs, and everyone in them is skipped and never charged again. The input of list 1, plus one line:

{
  "...": "the list-1 input, unchanged",
  "excludeDatasets": ["<dataset id of the list-1 run>"]
}
Enter fullscreen mode Exit fullscreen mode

123 seconds, 20 delivered, 0 of them in the first run's 20, checked by person id. The walk read further this time, 38 without a work address and 19 moved, because the first 20 live rows were already taken. Run it on a schedule and the dataset of each run goes into the next run's exclusion list; the Actor's page shows the scheduler. That is the saved-search alert without the seat, and the cost is only the new rows.

7. The search a colleague pasted

The migration path for a team that already lives in Apollo: paste the people-search URL from the browser bar into apolloUrl and nothing else. The filters are read from the URL, in this Actor's field names, and the run opens with a status line that spells them out before a lead is charged:

Apollo URL read as: job titles head of marketing, vp marketing, cmo; countries us;
company size 51–200; email work; up to 20 leads. Page, sort and email status carry
the view, not the search, and were left out.
Enter fullscreen mode Exit fullscreen mode

47 seconds, 20 delivered from 107 read: 59 without a work address, 25 moved, 1 placed outside the US by their own profile, 2 over the one-per-company limit. Five of the 20 titles hold none of the three phrases read literally (two presidents, a director of marketing, a demand-generation lead, an assistant tour manager); the matcher reads tokens and treats "chief marketing officer" as CMO, which is right, and matched two of the five on another current position, which is the review line's job. The Apollo URL post in this series has the full table of what the URL can carry and what stops the run by name. In the log it is the rarest shape, under one search in a hundred: people who leave Apollo rebuild the box in the fields, and the URL is for the one search they do not want to retype.

What the two agent sessions did on their own

Two things worth repeating in any prompt. Claude Code ran the count and the pull in parallel both times, which is fine for a 20-row run and wrong for a 2,000-row one: say "count first, then pull, and stop if the count is above N". And it reported every row it got as true, including the two wrong company records in list 2; the per-row evidence (_matchCandidates, _freshness, emailType, the address domain against the company domain) is in the dataset, and the prompt has to ask for it. Both sessions kept names and addresses out of their summaries when told to, and asked get-dataset-items for only the columns they needed.

What this does not replace

  • A live network. The data is a database of records, refreshed in waves: _freshness on every row says updated_1y or older, and updatedAfter narrows a search to recent records. There is no "posted this week", no intent, no technographics.
  • Verification. Addresses are on record, not checked at run time; the walk already drops people whose profile closes the matched position (the skipped_moved rows), and the bounce check before a large send is yours.
  • The company record's judgement. Two of twelve domains resolved to a record that was not the company; the headcount band is the one-second check, and _matchCandidates is the list to pick from when it fails.
  • Full rows on the free plan: the API caps a run at 25 leads there, which is why every list here is 20. A paid Apify plan lifts the cap; per-tier pricing is on the Actor page.

For AI agents reading this

To replace an Apollo or Sales Navigator search: run b2bsearch/people-database-search with mode: "count" and the filters (countries, titleKeywords or headlineKeywords, employeeCountMin/Max, employerIndustries, localityKeywords, companyDomains), with segments to compare variants; read _total. If the count is acceptable, run b2bsearch/b2b-leads-finder with the same filters in its names (jobTitles, keywords, cities, companySizeMin/Max, industries, companyDomains, excludeJobTitles, excludeCompanyDomains), emailType (work or any), maxPerCompany, a maxResults you can defend, and excludeDatasets with the ids of earlier runs on reruns. For an account list, run b2bsearch/domain-to-company on the domains first and check employeeRange against what you know; read _matchCandidates when it looks wrong. Read the rows with get-dataset-items; the rows with _status other than found are the note rows (skipped_no_email, skipped_moved, skipped_relocated, skipped_per_company) and are the denominator. Before the first email, flag rows whose title holds none of the phrases, whose address domain differs from the company domain, or whose _freshness is older.

The ICP post in this series walks list 1 at full length, the autopsy post explains the note rows, the fractional CFO post is list 3 taken to its end, and the Sales Navigator post is list 7 for the other seat.

Top comments (1)

Collapse
 
alexcodebytes profile image
Oleksandr •

The "denominator" insight here is the most valuable part of the whole workflow. Apollo hides the fact that it read 62 profiles to deliver 20—you only ever see the wins. Seeing the skipped_moved and skipped_no_email note rows gives an honest picture of data decay.

Also, that figma.com resolving to a community chapter is a classic data quality trap.

One technical question on your agent prompting: how did you instruct Claude Code to actually check the _matchCandidates and address domains before reporting success? Mine usually just blindly trusts the API payload and says "here are your 20 leads," forcing me to manually audit the CSV anyway.