linkedin-member-data-portability
Skill4 starsChanged 26 days agoMITView on GitHub
Use when: implementing, reviewing, or documenting LinkedIn Member Data Portability API calls for the Unlinked MCP server, including snapshot domains, changelog events, access-token handling, EEA availability, and read-only profile data tools.
We split this skill into the rules it gives an agent
15 rules. About 0 are common, 7 are distinctive, and 8 are very specific to this project.
≈0
Common · estimate
Many files like this have a rule of this kind.
≈7
Distinctive · estimate
This author's own choices. Worth a look.
8
Very specific
Tied to their project's names, files or tools. Probably skip.
- Common
- Distinctive
- Very specific
- Very specific: line 8
Use this skill whenever you implement or review LinkedIn API behavior for Unlinked.
- Distinctive: line 22
Never store, log, return, or include access tokens in thrown errors, CLI output or cache files.
- Distinctive: line 27
Every LinkedIn REST call should include:
- Very specific: line 35
Use the exact header name Linkedin-Version.
- Distinctive: line 39
Use the Snapshot API for profile and professional-history data.
- Very specific: line 53
Do not trust paging.total as a complete page count.
- Distinctive: line 54
Follow next links until there is no next page or until a safety cap (50 pages) is reached.
- Very specific: line 58
Every page has exactly one element {snapshotDomain, snapshotData[]}, and every domain arrived on one page (1,716 ALL_LIKES rows).
- Distinctive: line 89
Use the Changelog API for recent post-consent activity events.
- Very specific: line 102
Use the latest returned processedAt as the next startTime cursor.
- Very specific: line 153
Use McpServer and StdioServerTransport from @modelcontextprotocol/sdk.
- Very specific: line 155
Use Zod schemas for inputs and outputs.
- Distinctive: line 156
Write diagnostics to stderr only.
- Distinctive: line 157
Keep the token in memory only.
- Very specific: line 158
Keep payloads bounded: only small résumé sections are embedded in profile.
≈ “Common” is an estimate: at least 1 in 5 of the 308,232 skill files we read have a rule of the same kind, not the same words. “Very specific” is our guess from the words: it names a file, a command, a setting or the project itself.
Files
LinkedIn Member Data Portability API
Use this skill whenever you implement or review LinkedIn API behavior for Unlinked.
Product Context
Unlinked uses LinkedIn's Member Data Portability (Member) API product. The purpose is to let a LinkedIn member connect their own professional profile and experience data to an AI assistant through a local MCP server. The API is read-only for this project.
This API product exists for DMA data portability and is currently available only to LinkedIn members located in the European Economic Area and Switzerland. User-facing setup and error messages should say this plainly.
Access And Tokens
- A LinkedIn Developer application must be provisioned with Member Data Portability API (Member).
- LinkedIn's OAuth Token Generator docs currently instruct users to request
r_dma_portability_self_servefor member self-serve access. - Snapshot and changelog docs also reference
r_dma_portability_memberandr_dma_portability_3rd_partypermissions. Treat 403 responses as likely product/scope/consent problems and explain that clearly. - The access token comes from the
LINKEDIN_TOKENenvironment variable (never a tool input). Send it only asAuthorization: Bearer <access_token>to LinkedIn. - Never store, log, return, or include access tokens in thrown errors, CLI output or cache files.
- LinkedIn data (not the token) is cached for a bounded time (default 6 h, memory for MCP, memory plus owner-only disk files for the CLI). Cache only sanitized data: redacted, normalized snapshot rows and cleaned changelog events. See AGENTS.md.
Required Headers
Every LinkedIn REST call should include:
Authorization: Bearer <access_token>
Linkedin-Version: <YYYYMM>
Content-Type: application/jsonUse the exact header name Linkedin-Version. The version is pinned to 202312: live, no other value is accepted, even though the docs have newer versions. Do not make it configurable.
Snapshot API
Use the Snapshot API for profile and professional-history data.
GET https://api.linkedin.com/rest/memberSnapshotData?q=criteria
GET https://api.linkedin.com/rest/memberSnapshotData?q=criteria&domain=PROFILEThe optional domain query parameter is case-sensitive. If omitted, LinkedIn may return all domains. Prefer explicit domains for predictable assistant-facing tools.
Important response behavior:
elementscontains snapshot records withsnapshotDomainandsnapshotData.snapshotDatais a list of data generated for the requested domain.- Responses can be paginated with
paging.linksentries whoserelisnextorprev. - Do not trust
paging.totalas a complete page count; the docs say offline systems can make it incomplete. - Follow next links until there is no next page or until a safety cap (50 pages) is reached.
Verified live (2026-10-05), overriding the docs where they differ:
- Every page has exactly one element
{snapshotDomain, snapshotData[]}, and every domain arrived on one page (1,716ALL_LIKESrows).paging.countandtotalare meaningless. - An empty or not-yet-ready domain returns 404 "No data found for this domain and memberId." Treat it as empty for snapshot calls only (a 404 on page 2+ ends the data and keeps earlier rows). Posts can take up to 24 h to appear after first consent.
EVENTSreturns 400 "domain is not supported" although documented. Never request it.- Rows are flat export-style records with no IDs and keys like
First Name,ShareLink,Date/Time(sometimes an empty""key). Normalize to camelCase, drop empty values, de-duplicate. DateisYYYY-MM-DD HH:MM:SSin UTC, equal to the changelogcapturedAt. Rows are newest-first.- Reaction and comment
Linkvalues arehttps://www.linkedin.com/feed/update/<percent-encoded URN>; a reaction on a comment adds?commentUrn=urn:li:comment:(activity:N,M). - The snapshot lags the changelog, sometimes by more than 3 weeks, which is why Unlinked merges recent events into reactions, comments, posts and the intro.
- ENDORSEMENTS rows are endorsements the member gave. RECOMMENDATIONS
Statusis always empty (direction unknown). - Featured, profile photo/banner and contact info beyond websites have no API data.
Professional-context domains to prioritize:
PROFILE: basic biographical profile information.POSITIONS: job roles, companies, titles, descriptions, locations, and dates.EDUCATION: schools, dates, degrees, and activities.SKILLS: skills added to the member profile.CERTIFICATIONS: certifications on the profile.PROJECTS: projects listed on the profile.LANGUAGES: languages and proficiency.HONORS: honors listed on the profile.COURSES: courses listed on the profile.PUBLICATIONS: publications listed on the profile.PATENTS: patents listed on the profile.ORGANIZATIONS: organizations listed on the profile.VOLUNTEERING_EXPERIENCES: volunteering roles and descriptions.RECOMMENDATIONS: recommendations received and given.
Other useful domains include CONNECTIONS, MEMBER_SHARE_INFO, ARTICLES, ALL_COMMENTS, ALL_LIKES, JOB_APPLICATIONS, JOB_POSTINGS, SAVED_JOBS, JOB_SEEKER_PREFERENCES, and PROFILE_SUMMARY. Be thoughtful before exposing broad activity or inbox-like domains by default because they may contain sensitive personal data.
Changelog API
Use the Changelog API for recent post-consent activity events.
GET https://api.linkedin.com/rest/memberChangeLogs?q=memberAndApplication
GET https://api.linkedin.com/rest/memberChangeLogs?q=memberAndApplication&startTime=<epoch_ms>&count=10Behavior to preserve:
- Events are available for up to the past 28 days.
startTimeis an inclusive epoch-millisecond timestamp.- Invalid timestamps return
400. - The docs recommend
count=10; the upper limit is50. - Use the latest returned
processedAtas the nextstartTimecursor. If no event is returned, keep the same cursor for the next poll. capturedAtis the recommended event activity time when embedded activity timestamps are missing.- Changelog records include fields such as
id,capturedAt,processedAt,owner,actor,resourceName,resourceId,resourceUri,method,activity,activityId, andactivityStatus. Live there is noprocessedActivity; onlyactivityexists, so readactivity(unwrappatch.$setif present). - Results are sorted by ascending
processedAtand paged withstart;totalis 0 (meaningless). Clampcountto 1..50. A 404 here is an error, not "empty". - Resources seen live:
people(PARTIAL_UPDATE),people/positions(PARTIAL_UPDATE),socialActions/likesandsocialActions/comments(CREATE),invitations(ACTION, URNs only),messages(inbox content: drop it).ugcPostshas not been seen live; its shape comes from the docs. Verify theugcPostsmerge live once a post event is seen. - Group by
activityId, keep the last non-FAILURE record (FAILURE can be followed by SUCCESSFUL_REPLAY).
Authorization Status API
The changelog management API can check whether changelog generation is active:
GET https://api.linkedin.com/rest/memberAuthorizations?q=memberAndApplicationThere is also a documented activation endpoint:
POST https://api.linkedin.com/rest/memberAuthorizations
Content-Type: application/json
{}Unlinked should stay read-only by default. Do not add the POST activation behavior unless the project explicitly decides that this consent-management call is acceptable and documents it as separate from LinkedIn profile data mutation.
Error Handling
LinkedIn error bodies typically contain:
{
"message": "Empty oauth2_access_token",
"serviceErrorCode": 401,
"status": 401
}Map common failures into helpful MCP errors:
400: invalid query, timestamp, count, domain, or request syntax.401: missing, expired, revoked, invalid, or malformed bearer token.403: application lacks product access, scope, or member consent.404: endpoint or restricted API issue.426: API version header is deprecated.429: rate limit; reduce duplicate calls and retry later.500or504: LinkedIn-side failure or timeout; include request id headers when available, but never include tokens.
MCP Implementation Notes
- Unlinked is dual mode: a JSON CLI by default, and an MCP server with
--mcp. Both are thin adapters oversrc/service.ts. - Use
McpServerandStdioServerTransportfrom@modelcontextprotocol/sdk. - Name things as the LinkedIn UI does (
experience,reactions); internal domain names live only insrc/linkedin/sections.ts. Sensitive domains are blocked there. - Use Zod schemas for inputs and outputs. Return both a one-line text summary plus JSON in
contentand machine-readablestructuredContent; failures returnisError: true. - Write diagnostics to stderr only. In MCP mode stdout belongs to the transport; in CLI mode it carries result JSON only.
- Keep the token in memory only; never in cache files, logs or output.
- Keep payloads bounded: only small résumé sections are embedded in
profile; large sections are paged with cursors.
In this repository4 other files it gives its agents.
- AGENTS.mdAGENTS.md
- typescript-advanced-typesSkill
- typescript-mcp-server-generatorSkill
- typescript-proSkill
Does it work?
Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.
Reviews can't be read right now.
Your trail
Files you open will collect here, so you can find them again.
Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.
Now do this for your own.
Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.

