LinkedIn

connect

linkedin.com
InstallationInstalls just this skill for your agent
Side effectwrite-irreversible
Authsession
Needsnavigate · evaluate
Summary

Send LinkedIn connection requests (one or MANY), optional note per person.

Params
{"items":"array of { slug, note? }; each one a request (max 20 per run)"}
Returns
{"sent":"int (how many went out ok)","total":"int","results":"array of { slug, sent, status, urn, note, response, error? }","skipped":"int (trimmed by the cap of 20)"}
SKILL.md47 lines

Intent

Send LinkedIn connection requests (one or MANY), optional note per person. API VERSION + PACED BATCH. Single PARAM: items = array of objects { slug, note }. slug = vanityName (e.g. 'mwseibel', comes from the profileUrl of linkedin-search-people). note = optional, max 300 chars ('' or absent = no note). A one-off send = items with 1 element. SEQUENTIAL, PACED SENDING (on purpose, NOT in parallel): sends one at a time with ~2.4-5.6s of jittered delay between each, to look human and avoid flagging the account (writes/invites are watched far more than reads). Cap 20 per run (reports skipped). For each item: (1) resolve slug->profileUrn (GET /voyager/api/identity/dash/profiles?q=memberIdentity&memberIdentity={slug}); (2) POST /voyager/api/voyagerRelationshipsDashMemberRelationships?action=verifyQuotaAndCreateV2 with {invitee:{inviteeUnion:{memberProfile:URN}}} (+ customMessage if there's a note). HEADS UP IRREVERSIBLE: each item SENDS a real request to a real person. Does NOT work for 1st-degree connections. HARD LIMITS: free account ~100-200 invites/week (fewer on new accounts); the batch doesn't skip them, it just burns your quota. The personalized note is written by Claude (the loop) per person based on goal+profile, and passed in each item. Verified without note (POST 200). customMessage (note) INFERRED, not verified. Returns { total, sent, skipped?, results:[{slug, sent, status, urn, note, response, error? }] }. Requires a session. decorationId may rotate; if it breaks, recapture.

Preconditions

  • Logged-in session in the browser you drive (Logged-in LinkedIn session (www.linkedin.com cookies)).
  • If not logged in: navigate to https://linkedin.com and let the user log in, then retry.

Execute

  1. navigate → https://www.linkedin.com/feed/
  2. evaluate the Extractor below with params. It does the in-page work and returns the JSON in returns.

Extractor

( async (root, params) => { let items = params && params.items; if (typeof items === 'string') { try { items = JSON.parse(items); } catch(e){ items = []; } } if (!Array.isArray(items)) items = []; const CAP = 20; let skipped = 0; if (items.length > CAP) { skipped = items.length - CAP; items = items.slice(0, CAP); } const DELAY = 4000; const sleep = ms => new Promise(r=>setTimeout(r,ms)); const csrf = (document.cookie.match(/JSESSIONID="?([^";]+)"?/)||[])[1] || ''; const H = { 'csrf-token':csrf, 'x-restli-protocol-version':'2.0.0', 'accept':'application/vnd.linkedin.normalized+json+2.1' }; const results = []; for (let i=0; i<items.length; i++) { const it = items[i] || {}; const sl = String(it.slug||'').trim(); const nt = String(it.note||'').trim(); if (!sl) { results.push({ slug:null, sent:false, error:'missing slug' }); continue; } if (i>0) await sleep(DELAY*0.6 + Math.random()DELAY0.8); try { const pr = await fetch('/voyager/api/identity/dash/profiles?q=memberIdentity&memberIdentity='+encodeURIComponent(sl), {headers:H, credentials:'include'}); const pt = await pr.text(); const urn = (pt.match(/urn:li:fsd_profile:[A-Za-z0-9_-]+/)||[])[0]; if (!urn) { results.push({ slug:sl, sent:false, error:'could not resolve URN', status:pr.status }); continue; } const body = nt ? { invitee:{ inviteeUnion:{ memberProfile: urn } }, customMessage: nt } : { invitee:{ inviteeUnion:{ memberProfile: urn } } }; const ir = await fetch('/voyager/api/voyagerRelationshipsDashMemberRelationships?action=verifyQuotaAndCreateV2&decorationId=com.linkedin.voyager.dash.deco.relationships.InvitationCreationResultWithInvitee-2', { method:'POST', headers:{ 'csrf-token':csrf, 'x-restli-protocol-version':'2.0.0', 'accept':'application/vnd.linkedin.normalized+json+2.1', 'content-type':'application/json; charset=UTF-8' }, credentials:'include', body: JSON.stringify(body) }); const itx = await ir.text(); results.push({ slug:sl, sent:ir.ok, status:ir.status, urn, note:nt||null, response:(itx||'').slice(0,200) }); } catch(e) { results.push({ slug:sl, sent:false, error:String(e) }); } } const ret = { total: results.length, sent: results.filter(r=>r.sent).length, results }; if (skipped) ret.skipped = skipped; return ret; } )

Success assertion

Response text contains "LinkedIn".