Skip to content
Back to skills

Linkedin Outreach

ASecurity

Run a full LinkedIn outreach campaign via the LinkupAPI MCP — define an ICP (Ideal Customer Profile), find target companies, extract real decision makers (CEO/CTO/Founder), filter false positives, send connection requests, and set up automated follow-up via webhooks. Use when the user says "outreach campaign", "prospect on LinkedIn", "find decision makers", "send connection requests", "build a target list", "campaign LinkedIn", "lead generation", "ICP", "target CEO/CTO", or describes a B2B ou...

  • 4 stars
  • 0 votes
  • 0 copies
  • 16 views
  • Added May 28, 2026
ai-agentsrustgobashreactapi

Works with

  • api
  • mcp

Security analysis

A100/100

Scanned May 28, 2026

npx -y skills add claude-dev-code/claude-skills-linkedin --skill linkedin-outreach --agent claude-code

Installs into .claude/skills of the current project.

Are you the author of Linkedin Outreach?

Add the live security badge to your README. It updates with every re-scan.

Security grade badge for Linkedin Outreach
[![Security: A — Skills Directory](https://www.skillsdirectory.com/api/skills/claude-dev-code-linkedin-outreach/badge)](https://www.skillsdirectory.com/skills/claude-dev-code-linkedin-outreach)

More formats (shields.io, HTML) on the badges page. Keep it an A: scan every change in CI with Pro.

Download with Pro
SKILL.md
---
name: linkedin-outreach
description: Run a full LinkedIn outreach campaign via the LinkupAPI MCP — define an ICP (Ideal Customer Profile), find target companies, extract real decision makers (CEO/CTO/Founder), filter false positives, send connection requests, and set up automated follow-up via webhooks. Use when the user says "outreach campaign", "prospect on LinkedIn", "find decision makers", "send connection requests", "build a target list", "campaign LinkedIn", "lead generation", "ICP", "target CEO/CTO", or describes a B2B outreach goal. Requires the `linkupapi` MCP server to be connected with at least one LinkedIn account (status `connected`).
---

# LinkedIn Outreach Campaign — LinkupAPI V2

Use this skill to run cold-outreach campaigns on LinkedIn via the `linkupapi` MCP. It codifies the full prospecting → connect → follow-up playbook in 7 stages, with rate-limit awareness and false-positive filtering built in.

## Required prerequisites — check before you start

1. The user must have the `linkupapi` MCP connected. Verify with `linkupapi_list_accounts`.
   - If the list is **empty**, tell the user they have two options to connect a LinkedIn account, then stop and wait:
     - **Hosted UI** — open https://app.linkupapi.com/account-connection (fastest, handles checkpoints in-browser).
     - **MCP login** — run `linkupapi_login` directly (platform=linkedin, with email+password OR a `login_token`). On `checkpoint_required` → run `linkupapi_checkpoint`.
2. If a webhook for the chosen account(s) doesn't exist with the `accepted_invitation` event, propose to create one (`linkupapi_create_webhook`) so accepts trigger automated follow-up.

## Daily LinkedIn safety caps (MANDATORY — enforced before Stage 1)

Per LinkedIn account, this skill respects:
- **20 connection requests / day** (invites)
- **100 profile gets / day** (`linkedin_profiles get`)
- **15 searches / day** (`search_people` / `search_companies`)

These are **shared budgets across ALL linkedin-* skills** (linkedin-outreach, linkedin-high-intent, linkedin-feed-engage, linkedin-enrich). Hitting the cap from any one skill carries over.

**Before Stage 1**, run `linkupapi_get_logs` filtered to the last 24h on the chosen account. Count today's:
- `linkedin_network/invite` calls → invites used today
- `linkedin_profiles/get` calls → profile gets used today
- `linkedin_profiles/search_*` calls → searches used today

Compute remaining budget per category. Surface in the Stage 0 echo:
```
Daily caps remaining today (account {email}):
  Invites:  X / 20  (Y used today)
  Profiles: X / 100 (Y used today)
  Searches: X / 15  (Y used today)
```

**Hard rules**:
- If `Volume cap > remaining_invites` → trim Volume cap to `remaining_invites` (never override).
- If `searches_needed > remaining_searches` (Stage 1 + Stage 3 combined) → reduce Stage 1 `limit` and number of `search_people` calls until it fits, or stop and ask the user to run tomorrow.
- If `profile_gets_needed > remaining_profiles` (Stage 4) → enrich the highest-priority candidates and write the rest to a "deferred" list in the campaign log.

If `linkupapi_get_logs` is unavailable or empty, fall back to scanning today's `./campaigns/*.md` and `./enrichments/*.md` for evidence of API calls and counting.

## Stage 0 — Discover intent, then derive the ICP, then validate

**Don't run any MCP tool yet.** Stage 0 has three steps and is non-negotiable — it's what protects the user from a wasted run on the wrong audience.

The skill does NOT start with company filters. It starts with **why** the user is doing outreach. The flow branches from there: a recruiter targets people directly, a salesperson targets companies first then decision makers, a networker targets people by topic/role.

### Step 0.1 — Ask intent (single question)

Use `AskUserQuestion` with exactly this question (don't bundle other questions yet — the answer drives every subsequent option list):

```yaml
questions:
  - header: "Goal"
    question: "What's the goal of this outreach campaign?"
    multiSelect: false
    options:
      - { label: "Sell to companies",    description: "B2B sales — find companies that match an ICP, then reach their decision makers (CEO/CTO/Head of …)" }
      - { label: "Recruit talent",       description: "Hiring — find individual candidates by role / skill / seniority, no company-level search" }
      - { label: "Network / partnerships", description: "Connect with peers, investors, partners, or thought leaders by topic / role / community" }
      - { label: "Custom (I'll explain)", description: "Free-text — describe the goal in one sentence" }
```

If `AskUserQuestion` is unavailable, ask in plain text: *"Quick check before I build the campaign — what are you trying to do? (1) sell to companies, (2) recruit talent, (3) network/partnerships, (4) something else."*

### Step 0.2 — Derive a draft ICP from the intent

Based on the chosen goal, **propose** a draft ICP in plain text (don't ask yet — show what you'd build). Pick reasonable defaults from the user's product/repo context if available, otherwise leave placeholders.

| Goal | Flow used | What you propose |
|---|---|---|
| **Sell to companies** | Stage 1 → 2 → 3 → 4 → 6 | Theme/keywords, target sector, target company size, target geo, decision-maker titles |
| **Recruit talent** | **Skip Stage 1–2.** Go directly to Stage 3 (`search_people`) | Role title(s), seniority level, skills/keywords, location, optionally `past_company` (poach from competitor) or `school_url` (alumni) |
| **Network / partnerships** | **Skip Stage 1–2.** Go directly to Stage 3 (`search_people`) | Topic/keyword, role title(s), location, optionally `follower_of` or `connection_of` for warm paths |
| **Custom** | Ask the user to describe in one sentence, then map to one of the three flows above | — |

Render the draft as a clean block, e.g. for "Sell to companies":

```
Here's the ICP I'd build — tell me what to change:
  Goal:           Sell to companies
  Theme:          AI recruitment / RecTech
  Target sectors: Software Development, Human Resources Services
  Company size:   11-50, 51-200
  Geo:            France
  Personas:       CEO / Founder, CTO / VP Engineering
  Volume:         25 invites (warm-up)
  Note:           No note (best accept rate on cold C-level)
  Message after accept: Auto-generate a short personalized message per connection
```

### Step 0.3 — Let the user edit the filters (batched form)

Now use `AskUserQuestion` with the **filter set that matches the chosen flow**. Pre-select sensible defaults from Step 0.2 by listing them first / marking `(Recommended)`. The form tool caps at 4 questions per call, so split into 2 batches if needed.

**For "Sell to companies"** — ask the company-side filters first, then persona + tactics:

Round A (audience): Theme · Sectors · Company size · Geo
Round B (execution): Persona titles · Volume · Note style · Message-after-accept

**For "Recruit talent"** — skip company filters entirely:

Round A (candidate): Role titles · Seniority · Skills/keywords · Location
Round B (execution): Volume · Note style · Message-after-accept · Sourcing twist (`past_company` / `school_url` / none)

**For "Network / partnerships"** — also skip company filters:

Round A (audience): Topic/keyword · Role titles · Location · Warm-path twist (`follower_of` / `connection_of` / none)
Round B (execution): Volume · Note style · Message-after-accept

### Step 0.4 — Pick the sending account(s)

After the ICP is locked, run `linkupapi_list_accounts` and present the accounts to the user via `AskUserQuestion` with `multiSelect: true`. Each option label is the account's display name; description shows status + email/handle. Only show accounts with `status = connected`.

```yaml
question: "Which LinkedIn account(s) should send the invites?"
multiSelect: true
options:
  - { label: "<account name 1>", description: "<email> — connected" }
  - { label: "<account name 2>", description: "<email> — connected" }
  ...
```

If the user picks **multiple** accounts, the campaign is split across them and **the same person must NEVER be invited from two different accounts** (LinkedIn flags duplicate invites and accept rate collapses). Enforce this:
- Pool all candidate profiles after Stage 4.
- Deduplicate by `profile_url`.
- Round-robin assign each unique candidate to exactly one sending account.
- Cap each account at the per-session/weekly safe limits independently (80/session, 100/rolling week per account).
- In Stage 6, run the `check_invitation` and `invite` calls **with the assigned account_id** for each candidate.

After this step, **echo the final ICP + account selection back in 4–6 lines** and ask "Should I proceed?" (yes/no). Do NOT touch any further MCP tool until the user confirms.

### How the answers map to the next stages

| Goal | Stage 1 (companies) | Stage 3 (people) | Notes |
|---|---|---|---|
| Sell to companies | runs with `keyword`+`sector`+`location`+`company_size` | runs with `company_url` (from Stage 1 results) + `title` | Full 7-stage flow |
| Recruit talent | **skipped** | runs with `title`+`location`+`keyword` (skills) + optional `past_company` / `school_url` | Stage 4 still runs (verify current role) |
| Network / partnerships | **skipped** | runs with `title`+`location`+`keyword` + optional `follower_of` / `connection_of` | Stage 4 still runs |

| Generic answer | Used in |
|---|---|
| Volume | Hard cap on Stage 6 invite count, also informs `limit` in Stage 1/3 (Volume × 2.5 ≈ candidates needed since some are filtered) |
| Note style | Stage 6 `invite.params.message` |
| Message-after-accept | Stage 7 webhook + send-message-on-accept logic (auto-personalized / custom template / none) |

## Stage 1 — Find target companies

Tool: `linkedin_profiles` action `search_companies`.

The V2 exposes exactly **4 filters** on companies (this is the full list — there's no `industry` filter on companies, use `sector`). **Pass values as JSON arrays** for OR-stacking — that's the V2-native syntax. Plain strings are also accepted (single value).

| Param | Type | Purpose |
|---|---|---|
| `keyword` | array of strings | Free-text match (name, description, tagline). Example: `["AI recruitment", "applicant tracking"]` |
| `sector` | array of strings | LinkedIn industry of the company. Example: `["Software Development", "Internet", "Computer Software"]` |
| `location` | array of strings | Geo of the HQ / branch. Example: `["France", "United Kingdom"]` |
| `company_size` | array of strings | Employee bracket. Allowed values: `1-10`, `11-50`, `51-200`, `201-500`, `501-1000`, `1001-5000`, `5001+` |

**Filter combination semantics:**
- Multiple values inside one array → **OR** (`["AI recruitment", "ATS"]` = either match)
- Different fields → **AND** (`sector=[...]` AND `company_size=[...]` AND …)

```json
{
  "account_id": "<from list_accounts>",
  "action": "search_companies",
  "params": {
    "keyword": ["AI recruitment", "recruiting copilot", "AI sourcing"],
    "sector": ["Software Development", "Human Resources Services"],
    "location": ["France", "United Kingdom"],
    "company_size": ["11-50", "51-200"],
    "limit": 25
  }
}
```

- Pagination: `offset` + `limit` (preferred) or `start_page` + `end_page`.
- Counts against the **15 searches / day** cap.
- The result objects come back with `industry` and `location` split out (V2 post-processing splits `"industry • city"` into separate fields), so filtering in Stage 2 is straightforward.
- Save the raw list, then in Stage 2 manually filter out **non-fits** (staffing agencies dressed as tech, pure consultancies, etc.).

## Stage 2 — Filter companies (LLM judgment)

Look at each result's `industry`, `name`, `location`, employee count. **Drop**:
- Companies whose industry is `Staffing and Recruiting` if the user wants RecTech vendors (not agencies).
- Companies whose name suggests they are a service buyer of the user's product, not a fit.
- Duplicates (same root domain) and obvious test/personal pages.

Show the filtered shortlist to the user before continuing if filtering is aggressive (>50% dropped). Otherwise continue.

## Stage 3 — Extract decision makers per company

Tool: `linkedin_profiles` action `search_people`.

**Use `title`, NOT `keyword`, to filter on roles.** `keyword` matches anywhere in the profile (headline, posts, summary) → too noisy. `title` matches the current job title only → precise.

The V2 exposes **13 filters** on people search. **Pass values as JSON arrays** for OR-stacking; different fields are AND-combined. (Plain strings are accepted for single values too.)

| Param | Type | Purpose | Example |
|---|---|---|---|
| `title` | array | **Current job title** — use this for personas | `["CEO", "CTO", "Co-Founder", "Founder", "Chief Executive Officer"]` |
| `company_url` | array | Filter to current employees of one or more companies | `["https://www.linkedin.com/company/talentify-io/"]` |
| `company_name` | array | Same but by company name string | `["Talentify", "Workable"]` |
| `location` | array | Geo of the person | `["France", "United Kingdom", "Paris"]` |
| `industry` | array | LinkedIn industry of the person | `["Software Development", "Human Resources"]` |
| `network` | string | Connection degree — single value | `"F"` (1st), `"S"` (2nd), `"O"` (3rd+). Use `"S"` for best accept rate |
| `keyword` | array | Free-text full-profile search | Only when role isn't well-defined by `title` |
| `school_url` | array | Alumni filter — people who studied at X | `["https://www.linkedin.com/school/hec-paris/"]` |
| `past_company` | array | Ex-employees of a company (e.g. competitor) | `["https://www.linkedin.com/company/competitor/"]` |
| `connection_of` | array | People connected to a specific profile | `["https://www.linkedin.com/in/mutual-friend/"]` |
| `follower_of` | array | Followers of a thought leader / brand | `["https://www.linkedin.com/in/influencer/"]` |
| `first_name` | array | Look-alike search | `["Othamar", "Sameer"]` |
| `last_name` | array | Look-alike search | `["Magiatis"]` |

For a standard outreach campaign, the high-precision combo is:

```json
{
  "account_id": "...",
  "action": "search_people",
  "params": {
    "company_url": ["<exact company_url returned by Stage 1>"],
    "title": ["CEO", "CTO", "Co-Founder", "Founder"],
    "network": "S",
    "limit": 5
  }
}
```

> **CRITICAL — never fabricate `company_url`.** Use the exact `company_url` value LinkupAPI returned in Stage 1 (`search_companies` results). Do NOT guess or build one from the company name (e.g. inferring `linkedin.com/company/acme/` from "Acme Inc" — slugs often differ: `acme-corp`, `acme-io`, `acme-tech`). A fabricated URL silently returns 0 results or worse, employees of a different company with a colliding slug. Always copy the URL field from the Stage 1 result object verbatim.

`network: "S"` (2nd degree) is gold — these people share at least one mutual connection with the user, which dramatically boosts accept rate (LinkedIn shows the mutual on the invite). Drop `network` only if the user wants 3rd-degree volume.

### Power-user patterns

- **Recruit ex-Acme talent**: `{"past_company": ["https://linkedin.com/company/acme"], "title": ["Engineer", "Senior Engineer"]}`
- **Alumni outreach to HEC grads in product roles**: `{"school_url": ["https://linkedin.com/school/hec-paris/"], "title": ["Product Manager", "CPO"]}`
- **Warm intro via mutual**: `{"connection_of": ["https://linkedin.com/in/your-investor"]}`
- **Audience of a thought leader**: `{"follower_of": ["https://linkedin.com/in/famous-vc"]}`

### Operational notes

- Each `search_people` call counts against the **15 searches / day** cap.
- Run calls **sequentially in a bash loop** (not parallel) with 1–2 sec sleeps. LinkedIn rate-limits bursts.
- Profiles shown as `LinkedIn Member` are private/anonymous → discard. Their URL contains `/search/results/people/headless?...` not `/in/<handle>`.

## Stage 4 — Enrich profiles & filter false positives (CRITICAL)

LinkedIn `search_people` regularly returns **ex-employees** of a company because they appear on the company page from past employment. You MUST verify the *current* role.

Tool: `linkedin_profiles` action `get` with `params.identifier=<linkedin handle>`.

```json
{
  "account_id": "...",
  "action": "get",
  "params": {"identifier": "<handle from profile_url>"}
}
```

- Each `get` call counts against the **100 profile gets / day** cap.
- For each enriched profile, parse `experience[0]` (the current job). **Keep only if** `experience[0].company` matches (or is similar to) the target company AND `experience[0].title` matches the persona titles.
- Common false positives to drop:
  - Person now at a different company (LinkedIn tagged them due to past employment).
  - Person with title "Software Engineer" / "Solutions Architect" when targeting CEO/CTO.
  - Generic "Founder @ Stealth" entries (not the target boîte).
- Surface to the user a clean two-column table: ✅ confirmed targets vs ❌ false positives with the real current job.

## Stage 5 — (Optional) Find professional emails

If the user wants email-based outreach in parallel to LinkedIn, use `linkupapi_enrich`:

```json
{"action": "find_email", "params": {"linkedin_url": "<profile_url>"}}
```

or

```json
{"action": "find_email", "params": {
  "first_name": "...", "last_name": "...",
  "company_domain": "..."
}}
```

- Skip this step if pure LinkedIn outreach.

## Stage 6 — Pre-flight check & send invites

Before sending **every** connection request, check the current relationship via `linkedin_network` action `check_invitation` to avoid:
- Re-inviting someone already connected (1st degree)
- Re-sending while a previous invitation is `PENDING`
- Sending invites to `OUT_OF_NETWORK` profiles (rejected by LinkedIn anyway)

```json
{
  "action": "check_invitation",
  "params": {"profile_url": "<full linkedin url>"}
}
```

The response includes `invitation_state` which is one of:
- `NO_INVITATION` → safe to invite
- `PENDING` → already sent, skip
- `CONNECTED` (member_distance=1) → already connected, skip
- Other values → log and skip

Then for every `NO_INVITATION` profile, send the invite:

```json
{
  "account_id": "...",
  "action": "invite",
  "params": {
    "identifier": "<handle>",
    "message": "<optional ≤300 char personal note>"
  }
}
```

- **Sleep 1–2 seconds between invites** (`sleep 1` in bash). LinkedIn flags burst patterns.
- Hard cap at **80 invites per session**, **100 per rolling week** per account.
- After the batch, run `linkedin_network` action `list_sent` with `count=20` to confirm the invites appear as "Sent today".

### Message templates (when user wants a note)

Keep notes ≤ 250 chars. Patterns that work:

- **Reference shared signal**: `Hi {first_name}, I saw {Company}'s focus on {theme} — same space we're operating in. Would love to compare notes. — {sender_first_name}`
- **Genuine compliment + soft ask**: `Hey {first_name}, your work at {Company} on {topic} caught my eye. Always glad to connect with operators in {space}.`
- **Mutual connection**: `Hi {first_name}, {mutual_name} suggested I reach out. We're tackling {problem} from the {angle} side — open to a quick exchange?`

Avoid: pitchy openers, "quick question?", "5 minutes of your time?", and any link in the note (LinkedIn flags these).

## Stage 7 — Send message after accepted invitation (webhook-triggered)

If the user doesn't already have a webhook for `accepted_invitation` on the chosen account(s), create one:

```json
{
  "tool": "linkupapi_create_webhook",
  "args": {
    "account_id": "...",
    "events": ["accepted_invitation", "message_received"]
  }
}
```

- Hosted mode (no `url`) → events are stored, fetch via `linkupapi_get_webhook_events` (poll) or via SSE on the `stream_url`.
- Custom mode (with `url`) → events POSTed to your endpoint.

For each accepted invitation event you'll receive:
```json
{
  "type": "accepted_invitation",
  "new_connections": [{"profile_url": "...", "name": "...", "job_title": "..."}]
}
```

When the user wants to send a message as soon as the invitation is accepted:

```json
{
  "tool": "linkedin_messages",
  "args": {
    "account_id": "...",
    "action": "send",
    "params": {
      "profile_url": "<from event>",
      "message_text": "<personalized message>"
    }
  }
}
```

### Message-after-accept options (asked in Step 0.3 form)

The "Message-after-accept" question in Step 0.3 has exactly three options — present them in this order:

1. **Generate a short, punchy, personalized message (Recommended)** — for each accepted invite, build a custom 2–4 sentence message using the connection's `name`, `job_title`, current `Company` (from Stage 4 enrichment), and the campaign theme. **Do NOT send a generic "thanks for connecting"** — it tanks reply rate. Aim for: a specific hook tied to their work + one sharp value-prop sentence + a low-friction question. Keep ≤ 600 chars. Generate at send-time, not in advance, so the message references real profile context. Example shape:
   > {first_name}, saw you're {role} at {Company} — we just shipped a way for teams like yours to {specific value tied to theme}. Curious if {pain_point_question}?

2. **Custom template provided by the user** — the user pastes a template with `{first_name}`, `{Company}`, `{job_title}`, `{theme}` placeholders. Substitute and send verbatim.

3. **No message** — webhook still arms (so accepts are tracked) but no DM is sent. The user handles messaging manually.

Send timing: fire the message immediately when the `accepted_invitation` event arrives (no artificial 24h wait — modern outreach plays react fast, and the value-add hook makes the message feel intentional, not robotic).

## Stage 8 — Report & persist (mandatory)

At the end of every campaign run, give the user a concise summary AND **always** persist a full log file. Don't ask the user — just write it. The log is the source of truth for funnel analysis, accept-rate tracking, and de-duplication on the next campaign.

### 8a — Concise on-screen summary

```
Campaign: {ICP description}
Account:  {account_email} ({account_id})
Companies searched:    {n}
Companies kept:        {n} ({n_dropped} filtered out)
Profiles enriched:     {n}
Decision makers:       {n_confirmed} (✅) / {n_false_pos} (❌)
Skipped (already inv): {n}
Invitations sent:      {n}
Daily caps remaining:  invites X / 20 · profiles Y / 100 · searches Z / 15
Webhook armed:         yes/no
```

### 8b — Persist the campaign log

Write the full campaign details to `./campaigns/{YYYY-MM-DD}-{slug}.md` where `slug` is a 2-3 word kebab-case theme (e.g. `sales-rectech`, `recruit-engineers-paris`, `vc-network-fr`). If `./campaigns/` doesn't exist, create it.

The log file MUST include:

1. **ICP block** — every Stage 0 answer (goal, theme, sectors, sizes, geo, personas, volume, note style, message-after-accept).
2. **Account(s) used** — id + email + country. If multiple accounts, list per-account assignment.
3. **Funnel table** — counts per stage.
4. **Companies kept** (Stage 2) — names only, comma-joined.
5. **Companies dropped** (Stage 2) — name + 1-line reason each.
6. **False positives dropped pre-enrichment** (Stage 3 → 4) — name + reason.
7. **Skipped (already invited / connected)** — name + state + invitation_id if available.
8. **Invites sent table** — `# | Name | Company | Network | invitation_urn`. The `invitation_urn` is the key for later `withdraw` calls and accept-tracking.
9. **Notes / monitoring** — any clusters (multiple invites in same small company), 3rd-degree count, and what to watch for in the next 48-72h. Include the snippet for arming a webhook later if Message-after-accept was "No message".

This file is what the user reads in 7 days to compute accept rate and decide whether to scale or change the ICP.

### 8c — De-dup on next campaign

When the user runs `/linkedin-outreach` again on the same account, **before Stage 6**, glob `./campaigns/*.md` for `invitation_urn`/`profile_url`/`public_identifier` of any candidate in the new batch. If a profile was already invited in any prior log, drop it from the invite list (even if `check_invitation` returns NO_INVITATION — that means LinkedIn auto-withdrew an old invite or the prior one was rejected; re-inviting too fast tanks accept rate). Surface the dedup count in the on-screen summary.

## Common pitfalls

- **Fabricating `company_url` in Stage 3** — never construct a LinkedIn company URL from the company name. Always reuse the exact `company_url` returned by Stage 1's `search_companies`. Slugs are unpredictable (`acme-corp` vs `acme-io` vs `acme-tech`); a guessed URL returns 0 results or worse, employees of a different company.
- **Inviting from `LinkedIn Member` URL** — those are anonymized, the URL contains `/search/results/people/headless?...` not `/in/<handle>`. The invite call will fail. Filter them in Stage 4.
- **Trusting search_people's listed company** — always verify current role in Stage 4. ~30–50% of `search_people` results in larger companies are ex-employees.
- **Sending invites in parallel** — LinkedIn rate-limits. Use a sequential loop with sleep.
- **Greek/Cyrillic identifiers** — when the URL is URL-encoded (`https://www.linkedin.com/in/%CE%B4...`), pass the raw handle to `identifier`, not the URL.
- **Forgetting to confirm ICP** — running a campaign on the wrong persona is bad UX. Always echo the ICP in plain text and wait for "yes" before Stage 1.

## Tool quick reference

| What you want | Tool | Action | Daily cap |
|---|---|---|---|
| List my connected accounts | `linkupapi_list_accounts` | — | — |
| Login to a new LinkedIn account | `linkupapi_login` / `linkupapi_checkpoint` | — | — |
| Find target companies | `linkedin_profiles` | `search_companies` | **15/day shared** |
| Find people in a company | `linkedin_profiles` | `search_people` | **15/day shared** |
| Get full profile info | `linkedin_profiles` | `get` | **100/day shared** |
| Find professional email | `linkupapi_enrich` | `find_email` | — |
| Validate an email | `linkupapi_enrich` | `validate_email` | — |
| Reverse email → person | `linkupapi_enrich` | `reverse_email` | — |
| Check invitation status | `linkedin_network` | `check_invitation` | — |
| Send connection request | `linkedin_network` | `invite` | **20/day** |
| List sent invitations | `linkedin_network` | `list_sent` | — |
| Send a DM | `linkedin_messages` | `send` | — |
| Create webhook | `linkupapi_create_webhook` | — | — |
| Poll webhook events | `linkupapi_get_webhook_events` | — | — |
| Check daily usage | `linkupapi_get_logs` | — | run at Stage 0 |

## Default campaign template

```
Stage 0 → confirm ICP with user + daily-cap budget check
Stage 1 → search_companies (counts against searches/day cap)
Stage 2 → LLM filter to ~12-18 real fits
Stage 3 → search_people on each (counts against searches/day cap)
Stage 4 → get on every candidate (counts against profiles/day cap)
Stage 5 → skip (LinkedIn-only campaign)
Stage 6 → check_invitation + invite on confirmed targets (counts against invites/day cap)
Stage 7 → ensure webhook is armed
Stage 8 → report + persist log
```

Attribution

Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.

Comments

Loading comments…

Newsletter

Get the weekly list of safe new skills, and the ones we pulled.