---
name: moltsets-linkedin-thought-leader-expansion
description: Turn LinkedIn post engagers into enriched, ICP-qualified contact lists with segmentation signals. Scrapes a thought leader's recent posts, harvests reactions with full profile data, enriches all profiles with MoltSets to get company and industry data, qualifies against your ICP, optionally segments by pricing transparency and LinkedIn ad activity, then finds verified emails for qualified contacts. Uses Apify + MoltSets + Jina.
---

# LinkedIn Thought Leader Expansion

People who engage with industry content on LinkedIn are raising their hand. This skill finds them, enriches them, qualifies them against your ICP, and gives you an outreach-ready list with verified emails.

## Setup

Before running this skill, check that these three tools are connected:

1. **Apify** — connect via Claude Code: `claude mcp add apify --url https://mcp.apify.com/sse`. You'll need an Apify account (free tier works). Sign up at apify.com.
2. **MoltSets** — save your API key as `MOLTSETS_API_KEY` in your `.env` file. Get a key at moltsets.com (free trial included).
3. **Jina** — save your API key as `JINA_API_KEY` in your `.env` file. Get a key at jina.ai (generous free tier). Only needed if you want the pricing page segmentation.

Before the first run, verify all three are working:
- Apify: ask Claude "search Apify actors for linkedin" — if it returns results, you're good
- MoltSets: run `source .env && curl -s -X POST "https://api.moltsets.com/api/v1/tools/get_account" -H "Authorization: Bearer $MOLTSETS_API_KEY" -H "Content-Type: application/json" -d '{}'` — should return your account info
- Jina: run `source .env && curl -s "https://r.jina.ai/https://example.com" -H "Authorization: Bearer $JINA_API_KEY" -H "Accept: text/plain"` — should return page content

## Triggers

"harvest engagers from this post", "pull reactions from this LinkedIn post", "find people engaging with [thought leader]", "build a list from LinkedIn engagement", "who's engaging with content about [topic]", "signal-based list from LinkedIn".

## Chain

```
LinkedIn profile or post URL
  → Apify: harvestapi/linkedin-profile-posts      (get recent posts with engagement counts)
  → User picks which post(s) to harvest
  → Apify: harvestapi/linkedin-post-reactions      (scrape reactions with full profile data)
  → Deduplicate and exclude domains
  → MoltSets: reverse_linkedin_lookup on ALL       (enrich: company, industry, domain, seniority — and collect emails it already returns)
  → ICP qualification filter                       (title + seniority + industry + location — on real enriched data)
  → [optional] Jina: pricing page check            (does their company show public pricing?)
  → [optional] Apify: LinkedIn ad library          (is their company running LinkedIn ads?)
  → MoltSets: linkedin_to_business_email           (fill email gaps — only contacts missing email after reverse lookup)
  → output: enriched, segmented, outreach-ready list
```

## Set Expectations

Before any calls, set expectations briefly:
- 4-5 phases: get posts, scrape reactions, enrich all profiles with MoltSets, ICP filter on real data, then fill email gaps
- MoltSets misses are free, so we enrich everyone first and filter after — no wasted spend on bad data
- Segmentation enrichment (pricing page + ads) is optional

## Step 0 — Gather Context

Ask the user these questions in one message. Do not proceed without answers:

> "Before I harvest, I need a few things:
>
> 1. **What's the source?** Paste a LinkedIn profile URL or a specific post URL. If it's a profile, I'll pull their recent posts and let you pick which to harvest.
>
> 2. **Who are you looking for?** Describe your ICP so I can filter after enrichment:
>    - **Titles/roles** — e.g. VP Sales, Head of Marketing, RevOps Manager
>    - **Seniority** — e.g. Director and above, Manager and above, any
>    - **Industry** — e.g. SaaS/IT only, exclude agencies and consultancies, or "any"
>    - **Geography** — e.g. US only, EMEA, North America, or "any"
>
> 3. **How many contacts do you want?** e.g. 20, 50, 100 (I'll scrape more than this and filter down)
>
> 4. **Any domains to exclude?** e.g. your own company, the thought leader's company, competitors you don't want to contact
>
> 5. **Do you want segmentation enrichment?** I can check each company for:
>    - Whether they publicly list pricing on their website (self-serve vs sales-led signal)
>    - Whether they're running LinkedIn ads (growth investment signal)
>    - Say yes, no, or pick one
>
> 6. **Do you need mobile numbers?** (say yes or no)
>
> 7. **How do you want to run this?**
>    - **YOLO mode** — I run the full pipeline start to finish, no stops
>    - **Step-by-step** — I check in with you at each stage before continuing"

Wait for the user's response before proceeding.

### YOLO mode behavior

When the user selects YOLO mode, skip all confirmation gates throughout the pipeline:
- **Step 1:** Automatically pick the top 2-3 posts by engagement count
- **Step 4:** Enrich all, apply ICP filter, and continue with all fits — no approval needed
- **Step 5:** Run segmentation without asking (if selected in Step 0)
- **Step 6:** Run email enrichment on all qualified contacts
- **Step 7:** Output the final table

One prompt in, one table out.

### If input is vague

If the user gives a URL but no ICP criteria, ask:

> "Got the post. But I need your ICP to filter — otherwise you'll get every reactor including students, job seekers, and people outside your market. What titles, industries, and locations matter to you?"

Do not proceed to scraping without at least one ICP filter (title/role, industry, or geography).

## Step 1 — Get Posts

Call Apify actor `harvestapi/linkedin-profile-posts`:

```json
{
  "targetUrls": ["<LinkedIn profile or post URL>"],
  "maxPosts": 5,
  "postedLimit": "month",
  "scrapeReactions": false,
  "scrapeComments": false
}
```

No cookies or login required.

Wait for the Apify run to complete. Pull results from the dataset.

### If the user gave a profile URL

Present the posts with engagement counts:

> "Here are their recent posts:
>
> 1. **[post snippet — first 80 chars]** — [X] reactions, [Y] comments
> 2. **[post snippet]** — [X] reactions, [Y] comments
>
> Which post(s) should I harvest? Pick one or more, or say 'all'."

Wait for the user to choose before proceeding.

### If the user gave a post URL

Skip this step and go straight to Step 2 with the given post URL.

## Step 2 — Scrape Engagers

Call Apify actor `harvestapi/linkedin-post-reactions` with `profileScraperMode: "main"` to get full profile data on each reactor:

```json
{
  "posts": ["<selected post URL(s)>"],
  "maxItems": 200,
  "profileScraperMode": "main"
}
```

Set `maxItems` to ~2x the user's target count to leave room for ICP filtering.

`profileScraperMode: "main"` returns full profile data for each reactor:
- `actor.publicIdentifier` — the LinkedIn slug (needed for MoltSets)
- `actor.name`, `actor.firstName`, `actor.lastName`
- `actor.headline`, `actor.position`
- `actor.location.linkedinText`, `actor.location.countryCode`, `actor.location.parsed` (country, city, state)
- `actor.connectionsCount`, `actor.followerCount`
- `actor.experience[]` — current and past roles with `companyName`, `companyId`, `companyLinkedinUrl`, `position`

Wait for the Apify run to complete. Pull results from the dataset.

Report:

> "Scraped [X] reactions from [N] post(s). [Y] unique profiles after dedup. Moving to MoltSets enrichment."

## Step 3 — Deduplicate and Exclude

Remove duplicate LinkedIn profiles by `publicIdentifier`. If a person reacted to multiple posts, keep one row.

Remove any contacts from excluded domains specified in Step 0. Match against `actor.experience[0].companyName`.

## Step 4 — Enrich ALL Profiles with MoltSets

Enrich every unique profile — do not pre-filter. MoltSets misses are free, so there's no cost to enriching the full list. Filtering on real data is more accurate than guessing from headlines.

For each profile, call MoltSets `reverse_linkedin_lookup` using the `publicIdentifier` slug:

```
POST https://api.moltsets.com/api/v1/tools/reverse_linkedin_lookup
Authorization: Bearer <MOLTSETS_API_KEY>
Content-Type: application/json

{"linkedin_url": "https://www.linkedin.com/in/<publicIdentifier>"}
```

Read the API key from environment variable `MOLTSETS_API_KEY`. Run calls in parallel (MoltSets handles 100 req/s). Misses return 404 — no charge.

This returns:
- **Person:** full_name, title, seniority, country
- **Company:** name, industry, website_url (domain), linkedin_url
- **Business email** — `reverse_linkedin_lookup` already returns a verified business email when it has one. Collect these now — they save a call later.

Save the `website_url` from each response for the optional pricing page check. Save the `business_email` where returned.

Report:

> "Enriched [X]/[Y] profiles. [Z] already have emails. Running ICP filter on enriched data."

## Step 5 — ICP Qualification

Now apply the user's ICP criteria using the enriched MoltSets data combined with the Apify profile data:

- **Title/role match** — use **semantic matching, not keyword matching**. Read the person's title (from MoltSets) and headline (from Apify) together and judge whether they work in the function the user specified. "Builder of Scalable B2B Sales Machines" is a sales leader. "I re-architect B2B commercial operating models" is a GTM operator. Don't require exact keywords — use judgment.
- **Seniority** — use the `seniority` field from MoltSets (Director, VP, Head, C Suite, Owner, Partner, Manager, Senior, Entry)
- **Industry** — use the `industry` field from MoltSets (e.g. "Information Technology" for SaaS, "Marketing & Advertising" for agencies)
- **Geography** — use `country` from MoltSets or `location.parsed.country` from Apify

Classify each contact as:
- **Fits ICP** — matches the user's criteria
- **No fit** — dropped

Present results before proceeding:

> "ICP filter results:
>
> - **Fits ICP:** [X] contacts
> - **No fit (dropped):** [Z] contacts
>
> | Name | Title | Company | Industry | Seniority | Country | Email |
> |---|---|---|---|---|---|---|
> | ... | ... | ... | ... | ... | ... | ... |
>
> Should I continue with these [X] contacts, or adjust the ICP criteria and re-filter?"

Wait for the user's decision.

## Step 6 — Segmentation Enrichment (optional)

Skip this step if the user said no to segmentation in Step 0.

### LinkedIn Ads Check

For each unique company, check whether they're running LinkedIn ads using the `companyId` from the Apify profile data (`actor.experience[0].companyId`).

Call Apify actor `silva95gustavo/linkedin-ad-library-scraper`:

```json
{
  "startUrls": [
    {"url": "https://www.linkedin.com/ad-library/search?companyIds=<companyId>&dateOption=past-year"}
  ],
  "resultsLimit": 1,
  "skipDetails": true
}
```

Batch all companies into one Apify run by passing multiple `startUrls`.

Classify:
- **"Running LinkedIn Ads"** — at least 1 ad found for that `startUrl`
- **"No LinkedIn Ads"** — no ads found

### Pricing Transparency Check

For each unique company domain (from MoltSets `reverse_linkedin_lookup` in Step 4), check whether they publicly list pricing.

Use the Jina Reader API:

```
GET https://r.jina.ai/https://{domain}/pricing
Headers:
  Authorization: Bearer <JINA_API_KEY>
  Accept: text/plain
  X-Engine: browser
  X-Wait-For-Selector: main
  X-Timeout: 15
```

Read the API key from environment variable `JINA_API_KEY`.

Read the returned content and classify:
- **"Shows Pricing"** — the page lists actual dollar amounts, plan tiers with prices
- **"No Public Pricing"** — the page says "contact us", "request a demo", "get a quote", or has no pricing page at all
- **"No Pricing Page"** — the domain has no `/pricing` path (404 or redirect to homepage)

If `/pricing` returns nothing useful, try `/plans` as a fallback.

#### Batching for speed

Process pricing checks in batches of 10 companies. Use parallel sub-agents (Task tool) when processing 20+ companies — run batches in parallel, merge results.

Report:

> "Segmentation complete:
>
> | Signal | Count |
> |---|---|
> | Shows Pricing | X |
> | No Public Pricing | Y |
> | Running LinkedIn Ads | X |
> | No LinkedIn Ads | Y |
>
> Moving to email enrichment."

## Step 7 — Email Enrichment

Many contacts already have emails from `reverse_linkedin_lookup` in Step 4. Only run `linkedin_to_business_email` on the contacts that are still missing an email:

```
POST https://api.moltsets.com/api/v1/tools/linkedin_to_business_email
Authorization: Bearer <MOLTSETS_API_KEY>
Content-Type: application/json

{"linkedin_url": "https://www.linkedin.com/in/<publicIdentifier>"}
```

Run in parallel. Misses are free.

If the user requested mobile numbers, also call `linkedin_to_mobile_phone`.

## Step 8 — Output

Present the final list:

| Name | Title | Company | Industry | Location | LinkedIn | Email |
|---|---|---|---|---|---|---|

If segmentation was selected, add these columns:

| Shows Pricing | Running LinkedIn Ads |
|---|---|

Include comment text where available for personalization.

**Summary:**
```
Posts scraped:           X
Engagers scraped:        X
Profiles enriched:       X
ICP qualified:           X
Emails found:            X/N (XX%)
MoltSets API calls:      X
```

If segmentation was selected, also include:
```
Shows Pricing:           X
No Public Pricing:       X
Running LinkedIn Ads:    X
No LinkedIn Ads:         X
```

Offer to export as CSV.

## Edge Cases

- **Thought leader selection matters** — niche industry voices produce better-fit engagers than mainstream influencers. 50 reactions from a RevOps consultant beats 10,000 from a generic thought leader
- **Multiple posts compound** — running across 3-5 posts from the same thought leader catches people who engage repeatedly, which is a strong intent signal
