Skip to main content
Thirdwatchthirdwatch
Business & local data

Build an ABM Buying-Committee List from Public LinkedIn Profiles

Find likely decision-makers at named accounts, qualify them by role, and export a reviewable ABM contact list without LinkedIn cookies.

Apr 28, 2026 · 4 min read · 849 words
See the scraper →

An ABM list does not need every employee at an account. It needs enough plausible stakeholders to start research: the business owner, the technical evaluator, the operator who feels the problem, and sometimes procurement or finance.

The LinkedIn Company Employees Scraper turns company names or LinkedIn company URLs into public profile rows. It is useful for discovery. It is not a magical, complete org chart, and treating it as one will make your targeting worse.

Run the ready-made Task: Find decision-makers at target accounts.

Start with the buying problem

Write down the roles involved in buying your product before collecting profiles. A developer tool might involve an engineering manager, a platform lead, a security reviewer, and a finance owner. A RevOps product will have a different map.

Do not begin with a generic list of "executives." A CFO is senior, but may be irrelevant to the first conversation about an observability tool. Role fit matters more than title prestige.

Pull a public shortlist

Use company URLs when possible. They reduce ambiguity in names such as Apple, Linear, or Ramp.

import os
import requests

actor = "thirdwatch~linkedin-company-employees-scraper"
token = os.environ["APIFY_TOKEN"]

response = requests.post(
    f"https://api.apify.com/v2/acts/{actor}/run-sync-get-dataset-items",
    params={"token": token},
    json={
        "queries": [
            "https://www.linkedin.com/company/stripe/",
            "https://www.linkedin.com/company/datadog/",
            "https://www.linkedin.com/company/cloudflare/",
        ],
        "mode": "basic",
        "jobTitle": "Vice President",
        "maxResults": 50,
    },
    timeout=1800,
)
response.raise_for_status()
people = response.json()

jobTitle is a public-search refinement, not an exact filter. Keep the returned dataset, then apply rules you control.

Classify roles locally

The normalized currentTitle field is convenient, while headline is useful as a fallback.

import re

ROLE_RULES = {
    "economic_buyer": re.compile(
        r"\b(chief|cfo|cio|cto|vp|vice president|head of)\b", re.I
    ),
    "technical_evaluator": re.compile(
        r"\b(platform|infrastructure|security|engineering|architect|sre)\b", re.I
    ),
    "operator": re.compile(
        r"\b(revops|sales operations|developer experience|data platform)\b", re.I
    ),
}

def classify(person):
    text = " ".join(
        filter(None, [person.get("currentTitle"), person.get("headline")])
    )
    return [name for name, pattern in ROLE_RULES.items() if pattern.search(text)]

shortlist = []
for person in people:
    roles = classify(person)
    if roles:
        shortlist.append({**person, "buyingRoles": roles})

This is deliberately transparent. Your team can inspect why a person matched, adjust a regex, and rerun the classification without paying to scrape again.

Keep a canonical identity

Names are poor deduplication keys. Use url or publicIdentifier.

deduplicated = {}
for person in shortlist:
    deduplicated[person["url"]] = person

shortlist = list(deduplicated.values())

Store sourceCompany and sourceQuery too. They explain why the profile entered the list and help you spot false positives from ambiguous company searches.

Enrich only profiles that matter

Basic mode returns the fields needed for first-pass qualification: name, headline, profile URL, current role, and source company. Full mode attempts public-profile enrichment for work history, education, skills, certifications, languages, and location.

Do not run Full mode across thousands of unreviewed profiles. Run it on the smaller set where deeper context changes an outreach decision.

{
  "queries": ["https://www.linkedin.com/company/anthropic/"],
  "mode": "full",
  "jobTitle": "Product Manager",
  "maxResults": 20
}

If LinkedIn restricts a profile and enrichment fails, the Actor returns a Basic row and charges the Basic event. Check resultMode before relying on Full-only fields.

Add email after human review

Anonymous public LinkedIn profiles do not expose private email addresses. The clean workflow is:

  1. discover public profiles;
  2. filter for role fit;
  3. review the shortlist;
  4. use the LinkedIn Email Finder on the smaller set where you have a lawful outreach purpose;
  5. honor opt-outs and applicable privacy and marketing rules.

Separating discovery from email enrichment avoids paying to guess addresses for irrelevant people.

Watch for newly visible stakeholders

For active target accounts, change the input to monitorMode: "new-employees" and attach a weekly Apify Schedule. The first run establishes a baseline. Later runs return only profiles that appear in two consecutive complete snapshots.

That is useful for catching a newly visible VP or functional lead. It is not a headcount product and does not report departures, because public search absence cannot prove somebody left.

Export a reviewable ABM file

Keep the raw fields and your derived classification separate:

import csv

fields = [
    "sourceCompany",
    "fullName",
    "currentTitle",
    "headline",
    "url",
    "buyingRoles",
]

with open("abm-shortlist.csv", "w", newline="") as handle:
    writer = csv.DictWriter(handle, fieldnames=fields)
    writer.writeheader()
    for person in shortlist:
        writer.writerow({
            **{field: person.get(field, "") for field in fields},
            "buyingRoles": ",".join(person["buyingRoles"]),
        })

The CSV is an input to research, not an automatic outreach queue. Verify the current role and relevance before contacting someone.

Pricing

Basic profiles cost $0.003 each on FREE and $0.0015 on GOLD. Full profiles cost $0.008 to $0.005. Each successfully completed company search adds $0.020 to $0.015, depending on tier.

At FREE pricing, three completed company searches returning 50 Basic rows each cost about $0.51. Full-mode fallback rows are charged at the Basic rate.

What tends to go wrong

  • A common company name pulls unrelated profiles. Prefer a company URL.
  • A title refinement returns adjacent roles. Filter the output locally.
  • The same person appears under two account searches. Deduplicate by LinkedIn URL.
  • Teams mistake visible results for a complete org chart. Label the dataset as a public discovery sample.
  • Broad Full-mode runs cost more without improving decisions. Enrich after shortlisting.

Frequently asked questions

Does this produce a complete company org chart?

No. It returns profiles visible through public search. Use the results as a discovery list and verify priority contacts before outreach.

Can I search by job title?

Yes. The jobTitle input refines public search, but it is not a strict database filter. Apply your own title rules to the returned currentTitle and headline fields.

Does the Actor return email addresses?

No. Anonymous LinkedIn profiles do not expose private email addresses. Enrich the smaller, reviewed shortlist with a separate lawful email-finding workflow.

Should I use Basic or Full mode?

Use Basic for broad account discovery. Use Full only for shortlisted profiles where public work history, education, skills, or location changes your qualification decision.

Related

Try it yourself

100 free credits, no credit card.

About 30 real searches. Add the MCP to Claude or Cursor in two minutes.