LinkedIn

get-profile

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

Fetch a LinkedIn person's FULL profile by their slug (vanityName), via API (without opening the profile page -> likely WITHOUT registering a visit; the 'viewed your profile' beacon is fired by the …

Params
{"slug":"string (the profile vanityName, the segment of linkedin.com/in/<slug>)"}
Returns
{"name":"string","slug":"string","about":"string|null (summary)","geoUrn":"string|null","honors":"array","skills":"array of string (up to 20)","courses":"array","creator":"bool","premium":"bool","student":"bool","hasPhoto":"bool","headline":"string|null","location":"string|null (countryCode)","projects":"array","languages":"array","positions":"array of { title, company, location, employmentType, from, to, duration }","volunteer":"array","educations":"array of { school, degree, field, from, to }","influencer":"bool","profileUrl":"url","industryUrn":"string|null","certifications":"array"}
SKILL.md83 lines

Intent

Fetch a LinkedIn person's FULL profile by their slug (vanityName), via API (without opening the profile page -> likely WITHOUT registering a visit; the 'viewed your profile' beacon is fired by the page, not by the data fetch; NOT 100% guaranteed). v2 ENRICHED: makes several in-page fetches with session cookies. (1) core via GET /voyager/api/identity/dash/profiles?q=memberIdentity&memberIdentity={slug} -> name, headline, about(summary), location(countryCode), geoUrn, industryUrn, premium/influencer/creator/student flags, hasPhoto, and the entityUrn (urn:li:fsd_profile:...). (2) with that profileUrn it hits the dash sub-resources ?q=viewee&profileUrn=<urn>: profilePositions (experience with title, company, location, dateRange -> computes from/to/duration), profileEducations (school, degree, field, dates), profileSkills (up to 20 names), profileCertifications, profileLanguages, profileVolunteerExperiences, profileHonors, profileProjects, profileCourses (return [] if the person has none). HEADS UP LEGACY endpoints deprecated (410): /voyager/api/identity/profiles/{slug}/positionGroups, /educations and /skills NO longer work; use the dash ?q=viewee pattern. PARAM: slug (vanityName, e.g. 'martin-sabi-91b535165', comes from the profileUrl of linkedin-search-people). Useful to qualify candidates and personalize outreach (education + dated experience + skills are the key material; NOTE: search's schoolText filter matches ANY education, so here you can see whether the degree is really from that university or a postgrad). Returns { slug, profileUrl, name, headline, about, location, geoUrn, industryUrn, premium, influencer, creator, student, hasPhoto, positions:[{title,company,location,employmentType,from,to,duration}], educations:[{school,degree,field,from,to}], skills:[string], certifications:[], languages:[], volunteer:[], honors:[], projects:[], courses:[] }. Does NOT return email (LinkedIn doesn't expose it except for a 1st-degree who shared it). Requires a session. Depends on Voyager dash endpoints; if they break (410/new shape), recapture.

Preconditions

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) => { const slug = String((params && params.slug) || '').trim(); if (!slug) throw new Error('missing slug'); const csrf = (document.cookie.match(/JSESSIONID="?([^";]+)"?/) || [])[1]; if (!csrf) throw new Error('re-auth: no JSESSIONID cookie (LinkedIn session not started)'); const H = { 'csrf-token': csrf, 'x-restli-protocol-version': '2.0.0', 'accept': 'application/json' }; const B = 'https://www.linkedin.com'; const NOW = { year: 2026, month: 6 }; const MONTHS = ['', 'Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']; const fmt = (d) => d && d.year ? ((d.month ? MONTHS[d.month] + ' ' : '') + d.year) : null; const dur = (s, e) => { if (!s || !s.year) return null; const sm = s.year * 12 + (s.month || 1); const ee = (e && e.year) ? e : NOW; let m = (ee.year * 12 + (ee.month || 6)) - sm + 1; if (m < 1) m = 1; const y = Math.floor(m / 12), mm = m % 12; return ((y ? y + (y > 1 ? ' years' : ' year') : '') + (y && mm ? ' ' : '') + (mm ? mm + ' month' + (mm > 1 ? 's' : '') : '')) || '<1 month'; }; const getJson = async (url) => { const r = await fetch(B + url, { credentials: 'include', headers: H }); if (r.status === 401 || r.status === 403) throw new Error('re-auth: HTTP ' + r.status + ' at ' + url); let j = null; try { j = await r.json(); } catch (e) {} return { status: r.status, j }; }; const core = await getJson('/voyager/api/identity/dash/profiles?q=memberIdentity&memberIdentity=' + encodeURIComponent(slug)); const el = core.j && core.j.elements && core.j.elements[0]; if (!el || !el.entityUrn) throw new Error('tool-broken: could not resolve core profile of ' + slug); const enc = encodeURIComponent(el.entityUrn); const name = [el.firstName, el.lastName].filter(Boolean).join(' ').trim(); const sub = async (path) => { try { const r = await getJson('/voyager/api/identity/dash/' + path + '?q=viewee&profileUrn=' + enc); return (r.j && r.j.elements) || []; } catch (e) { if (String(e.message).indexOf('re-auth') === 0) throw e; return []; } }; const positions = (await sub('profilePositions')).map(p => ({ title: (p.title || '').trim(), company: p.companyName || null, location: p.geoLocationName || p.locationName || null, employmentType: p.employmentTypeUrn || null, from: fmt(p.dateRange && p.dateRange.start) || null, to: (p.dateRange && p.dateRange.end) ? fmt(p.dateRange.end) : (p.dateRange && p.dateRange.start ? 'Present' : null), duration: dur(p.dateRange && p.dateRange.start, p.dateRange && p.dateRange.end) })); const educations = (await sub('profileEducations')).map(e => ({ school: e.schoolName || null, degree: e.degreeName || null, field: e.fieldOfStudy || null, from: fmt(e.dateRange && e.dateRange.start) || null, to: fmt(e.dateRange && e.dateRange.end) || null })); const skills = (await sub('profileSkills')).map(s => s.name).filter(Boolean); const certifications = (await sub('profileCertifications')).map(c => ({ name: c.name || null, authority: c.authority || null, from: fmt(c.dateRange && c.dateRange.start) || null })); const languages = (await sub('profileLanguages')).map(l => ({ name: l.name || null, proficiency: l.proficiency || null })); const volunteer = (await sub('profileVolunteerExperiences')).map(v => ({ role: v.role || null, org: v.companyName || null })); const honors = (await sub('profileHonors')).map(h => ({ title: h.title || null, issuer: h.issuer || null })); const projects = (await sub('profileProjects')).map(p => ({ title: p.title || null })); const courses = (await sub('profileCourses')).map(c => ({ name: c.name || null })); return { slug, profileUrl: B + '/in/' + slug, name, headline: el.headline || null, about: el.summary || null, location: (el.location && el.location.countryCode) || null, geoUrn: (el.geoLocation && el.geoLocation.geoUrn) || null, industryUrn: el.industryUrn || null, premium: !!el.premium, influencer: !!el.influencer, creator: !!el.creator, student: !!el.student, hasPhoto: !!el.profilePicture, positions, educations, skills, certifications, languages, volunteer, honors, projects, courses }; } )

Success assertion

{
  "type": "json",
  "jsonPath": "name"
}