A Sales Navigator saved search is often the most precise ICP a sales team owns, and the seat is the price of running it. Someone built it over an afternoon: the titles, the geography, the headcount bands, the companies to leave out. Everyone else needs a seat to see the results, and getting the people out means an export by hand or a browser extension that runs on the account's cookies and breaks when the page changes.
On 2026-10-28 I took three searches a sales team actually runs, copied their URLs, and turned each one into a list from Claude Code with one Apify Actor, Sales Navigator Search Scraper. The Actor never opens the URL. It reads the filters encoded in it and runs the same query on a database of professional records, so there is no login, no cookie and no seat involved, and a free count sizes the search before a row is charged. Every number below comes 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. Sales Navigator is a trademark of its owner; this Actor is not affiliated with it. Per-tier pricing is on the Actor page.
In one paragraph: a Sales Navigator alternative here means building the search in the tool you already know, copying the results-page URL, and letting an Actor read the filters (current company, current title, region, industry, headcount, keywords) and run them on its own people database. You get a count for free, rows paid per person delivered, a weekly rerun that returns only new people, and an agent that can do all of it over MCP.
What a Sales Navigator URL carries
Copy the address from the browser bar while the results are open. The query parameter is a Rest.li structure, percent-encoded twice. Decoded, my first search reads:
filters: List(
(type: CURRENT_TITLE, values: List((text: VP Sales, selectionType: INCLUDED),
(text: Head of Sales, selectionType: INCLUDED))),
(type: REGION, values: List((id: 103644278, text: United States, selectionType: INCLUDED))),
(type: COMPANY_HEADCOUNT, values: List((id: D, text: 51-200), (id: E, text: 201-500))),
(type: INDUSTRY, values: List((id: 4, text: Software Development)))
)
The Actor maps each filter to a field of its people search and echoes what it ran in _input on every row, so the first thing to read after a run is the search it actually executed:
{
"location_country_code": ["us"],
"title_contains_any": ["vp sales", "head of sales"],
"employer_industry_ids": [4],
"employee_count": { "min": 51, "max": 500 }
}
| Sales Navigator filter | Read as |
|---|---|
| Current company | the companies picked from the suggestions, by their id; excluded companies too |
| Current job title | text inside the current title; excluded titles are excluded |
| Geography | a country, a "City, State, Country" place, or a metro area as written on the profile |
| Industry | the employer's industry, by the same id |
| Company headcount | one range of adjacent bands (51-200 plus 201-500 reads as 51-500) |
| Keywords | plain words, matched in the headline |
Everything else stops the run before anything is charged, and the status line names it. I pasted the first search with a seniority filter added, verbatim:
Not run, nothing charged. These Sales Navigator filters cannot be read yet: seniority level. Remove them from the search, copy the URL again and rerun.
The same happens for function, past company, school, years in role, the spotlights (changed jobs, posted recently), first and last names, groups, Boolean keyword strings, continents, and a company typed as text instead of picked from the list. A saved-search link (savedSearchId=) is refused too, because it carries no filters: open the saved search and copy the address of its results page. A refusal costs nothing. A dropped filter would sell you people you excluded, so nothing is dropped quietly.
Three saved searches, measured on 2026-10-28
Each count is one run with mode: "count": no row is charged, _total holds the number, and _input holds the filters as read.
| Search | Filters in the sidebar | People matched | Count took |
|---|---|---|---|
| New logo | VP Sales or Head of Sales; United States; 51-500 employees; Software Development | 1,175 | 9 s |
| Territory | Head, VP or Director of Sales; Austin, Texas; 11-500 employees | 946 | 5 s |
| Account list | Engineering Manager; United Kingdom; eight companies picked by id | 419 | 7 s |
1. New logo: the ICP box
mode: "people", maxResults: 20, maxPerCompany: 1, profileDetail: "row". 7.5 seconds, 20 delivered, no note rows.
| Title | Company size | Industry | Country | Email on record | Freshness |
|---|---|---|---|---|---|
| Head of Sales | 148 | Software Development | us | work, personal | updated_1y |
| VP Sales Engineering | 138 | Software Development | us | work, personal | updated_1y |
| Head of Sales Americas | 424 | Software Development | us | work, personal | older |
| Global Head of Sales | 237 | Computer Games | us | work, personal | older |
| Head of Sales and Marketing | 197 | Software Development | us | none | older |
All 20 titles hold "VP Sales" or "Head of Sales", all 20 employers have 59 to 424 people, all 20 people are in the US. 17 of 20 carry a work address on record, 14 a personal one, 3 neither; 8 rows are updated_1y, 12 older. Three rows carry an industry label other than Software Development (two Computer Games, one IT Services): the filter matched the employer record's industry id, the label on the row is the employer's industry text, and the two fields can disagree. Six of the 20 list more than one current position, which is where the review line below starts.
The exclusion trick from the first Sales Navigator post still holds: "Investing" and "Partner" added as excluded titles in the sidebar take the count from 1,175 to 1,124, and the exclusion arrives as title_not_contains_any.
2. Territory: metro area or city, and why it matters
The second search is a territory: Head, VP or Director of Sales at 11-500-person companies in Austin. Sales Navigator offers "Austin, Texas Metropolitan Area" and "Austin, Texas, United States" as two geography values, and here they are not the same search:
| Geography value | Read as | People |
|---|---|---|
| Austin, Texas Metropolitan Area | locality_contains_any: ["austin, texas metropolitan area"] |
203 |
| Austin, Texas, United States |
location_country_code: ["us"] plus locality_contains_any: ["austin, texas"]
|
946 |
A metro area matches the location as people wrote it on their profile, and most wrote the city. Pick the city, or the country for full coverage. The 20-row pull on the metro version took 6 seconds: all 20 locations are the metro area as written, all 20 titles hold one of the three phrases, employers from 11 to 362 people, 8 of them in software development and the other 12 spread over construction, manufacturing, finance, accounting, retail, logistics and health care, because this search had no industry filter. 17 work addresses on record, 10 personal, 3 neither. Only 4 rows are updated_1y and 16 older: a small-company territory ages faster than the software box.
3. Account list: picked companies, by id
Sales Navigator's account list is the "current company" filter with companies picked from the suggestions, and the URL carries each one's id. I built this search by hand, eight companies and "Engineering Manager" in the United Kingdom, and learned two things. A company written as text is refused by name, because the Actor reads ids only: a name typed as text could match a namesake. And two of my eight ids matched nobody; a free count on each company alone returned 0 for those two and 17,734 people for one of the six that resolved. The sidebar writes the id for you; a hand-built URL is yours to check, and the count on one company is the check.
419 people matched the six companies that resolved. With maxPerCompany: 3 the pull delivered 18 in 58 seconds, 3 at each of the six, and one note row: 240 people were over the limit of 3 per company and were skipped, not delivered, not charged. Employee counts on these rows are the companies' global headcount, 17,285 to 785,236. 15 of 18 carry a work address on record, 9 a personal one; 11 rows updated_1y, 7 older.
The domain route to an account list, Domain to Company plus companyDomains on B2B Leads Finder, is in the Apollo alternative post; it takes the accounts as website domains and skips the id question.
Emails: the row tier says "on record", the contacts tier delivers
hasWorkEmail and hasPersonalEmail on a search row are flags: an address is on record. profileDetail: "contacts" reads the full profile and delivers only an address judged to reach the person today. The first search in the contacts tier, 20 people, one per company, 9 seconds:
- 20 delivered: 10 with a work address at the current employer, 5 with a personal address, 5 with no reachable address, shipped as a full profile at the profile price.
- 9 people skipped as
skipped_moved: their full profile closes the position the search matched. The row tier delivered the same search with zero note rows, because a search row is the record as matched; the profile tiers do the move check for you, and 9 of the 29 people read had moved. - 2 of 20 with a phone number on record.
- Of the 15 addresses, 7 sit on the row's company domain. The other 8 are 5 personal addresses and three "work" addresses on another domain: two are a rebrand or an acquisition, the old and the new domain of one business, and one is at a different company. The address domain against the company domain is the cheapest bounce check there is, one column against another.
- 4 of 20 list another current job ahead of the matched one (
primaryTitlediffers fromjobTitle), and 1 of 20 no longer lists the matched employer at all (_employerUnconfirmed: true).
The same box typed by hand into B2B Leads Finder with emailType: "work", 20 leads, one per company, 15 seconds: 20 delivered from 59 read, 24 skipped because no work address at the current employer was on record, 15 skipped as moved. 16 of the 20 addresses sit on the row's company domain. Five of these 20 people are in the URL run's 20 and nine in the contacts run's 20: a search that requires a live work address walks a different head of the same 1,175.
The weekly rerun: new people only
A saved-search alert needs the seat to remember what it already showed you. Here the memory is the dataset: excludeDatasets takes the ids of earlier runs, and everyone in them is skipped and never charged again. The first search once more, with the dataset of the first pull excluded:
{
"salesNavigatorUrl": "<the same URL, unchanged>",
"mode": "people",
"maxResults": 20,
"maxPerCompany": 1,
"profileDetail": "row",
"excludeDatasets": ["<dataset id of the first run>"]
}
2 seconds, 20 delivered, 0 of them in the first run, checked by personId. Run it on a schedule, feed each run's dataset into the next run's exclusion list, and the same URL returns only the people new to the search. That is the alert without the seat, and the cost is only the new rows.
The same search from Claude Code over MCP
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/sales-navigator-search,b2bsearch/b2b-leads-finder"
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/sales-navigator-search,b2bsearch/b2b-leads-finder"
}
}
}
The Actors appear as tools named b2bsearch--sales-navigator-search and b2bsearch--b2b-leads-finder (the slash becomes a double hyphen); the server adds get-dataset-items, get-actor-run and two more helpers next to them. A tool call returns the run and its dataset id; the rows come from get-dataset-items. What a pinned Actor costs an agent in tokens before the first call is measured in the MCP tool cost post.
I gave Claude Code the first search's URL and this brief: count first and name the filters you read; if the count is under 5,000, pull up to 20 people, one per company, search rows only; then run it again with the first run's dataset excluded and report the overlap by personId; never print names, profile URLs, company names or addresses. The session had this one Actor pinned and no shell or file tools. Seven tool calls, 130 seconds:
-
ToolSearch: loaded the three tool definitions it needed, the Actor,get-dataset-itemsandget-actor-run. - The Actor with
mode: "count", thenget-dataset-itemswithlimit: 5to read_total(1,175) and the echoed filters. - The Actor with
mode: "people",profileDetail: "row",maxResults: 20,maxPerCompany: 1. -
get-dataset-itemswithfields: "personId,_status,_freshness,jobTitle,companyEmployeeCount,companyIndustry,countryCode,hasWorkEmail,_note,_error", in parallel with the rerun carryingexcludeDatasets: ["<first dataset>"]. -
get-dataset-itemson the second dataset, four fields only.
Its report: 1,175 matched; 20 and 20 delivered; overlap by personId 0; 17 and 18 rows with a work address on record; 8 and 10 rows refreshed within the year; no note rows. Two things it flagged on its own, both right: three of 20 rows carry an industry label other than the one filtered, and the title filter reads as "contains", so "VP Sales Engineering" and "Head of Sales Strategy and Operations" came through, which it summed up as "expect a somewhat broader list than the seat gives you". Two things worth putting in every prompt: it asked for columns, not rows, which kept each dataset read to a few hundred tokens; and it started the rerun before it had read the first run's rows, which is fine at 20 and wrong at 2,000, so say "read the first run before you start the second".
What this does not replace
- A live network. The data is a database of records, refreshed in waves;
_freshnesson every row saysupdated_1yorolder, and the totals will not equal the number Sales Navigator shows for the same search. No activity, no intent, no "posted this week"; the spotlight filters are refused for that reason. - Exact matching. Titles match as text inside the current title, keywords match the headline only, a metro area matches the location as written. Expect a broader title list and a narrower keyword list than the seat gives, never other people.
- The review line. Six of 20 people in the first search list more than one current position, 9 of 29 read by the contacts tier had moved, and 3 of 15 work addresses sit on another domain. The evidence is on the row (
currentPositions,_freshness,emailType, the address domain againstcompanyDomain); the look is yours. - Full rows on the free plan. The API caps a run at 25 people there, which is why every list here is 20. A paid Apify plan lifts the cap.
FAQ
Do I need a Sales Navigator seat? To build the search URL, yes, or anyone who has one sends you the link. The run itself uses no account, cookies or seat.
Which filters carry over? Current company (picked from the suggestions, by id), current title, geography (country, city or metro area), industry, company headcount, plain keywords. Seniority, function, past company, school, years in role, spotlights, names, groups and Boolean strings stop the run by name, nothing charged.
How do I get emails? profileDetail: "contacts" delivers an address that reaches the person today, or the full profile when none is on record. For a list where every row must have a work address, type the same box into B2B Leads Finder with emailType: "work".
Does a rerun return the same people? Not with excludeDatasets: everyone in the listed datasets is skipped free, so a scheduled rerun of the same URL returns only new people; 0 of 20 repeated in the run above.
Can an AI agent run it? Yes, over MCP with the Actor pinned in the tools parameter; the wiring and a recorded session are above.
For AI agents reading this
To turn a Sales Navigator people-search URL into a list: run b2bsearch/sales-navigator-search with salesNavigatorUrl set to the URL unchanged and mode: "count"; read _total and _input.filters from the dataset. If the run's status message starts with "Not run", it names the filters in the URL that cannot be read, and the user has to remove them from the search and copy the URL again. If the count is acceptable, run mode: "people" with a maxResults you can defend, maxPerCompany for an account list, profileDetail set to row, profile or contacts, and excludeDatasets with the ids of earlier runs on reruns. Read rows with get-dataset-items and a fields list; rows with _status other than found are note rows (skipped_moved, skipped_per_company) and are the denominator. Before the first email, flag rows whose _freshness is older, whose primaryTitle differs from jobTitle, whose _employerUnconfirmed is true, or whose address domain differs from companyDomain. For the same search typed as fields with a live work address required, run b2bsearch/b2b-leads-finder with jobTitles, countries, companySizeMin, companySizeMax, industries and emailType: "work".
The first Sales Navigator post has the full filter table and the move check on 75 people, the Apollo alternative post rebuilds seven lists in the typed fields, and the ICP post walks one box from count to list at full length.
Top comments (0)