linkedin-member-data-portability — independently scanned and version-tracked by SaferSkills.
SaferSkills independently audited linkedin-member-data-portability (Agent Skill) and scored it 100/100 (green). The audit ran 55 deterministic rules across Security, Supply Chain, Maintenance, Transparency, and Community; it found 0 high-severity and 0 lower-severity findings. The full rule-by-rule trace and per-finding evidence are below. Free, methodology-open.
Findings & checks · 0 flagged
Every scanned point with the score it earned and what moved between them.
First recorded scan — no prior version to compare against.
The primary manifest — the file an agent reads to learn what this artifact does.
Use this skill whenever you implement or review LinkedIn API behavior for Unlinked.
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.
r_dma_portability_self_serve for member self-serve access.r_dma_portability_member and r_dma_portability_3rd_party permissions. Treat 403 responses as likely product/scope/consent problems and explain that clearly.LINKEDIN_TOKEN environment variable (never a tool input). Send it only as Authorization: Bearer <access_token> to LinkedIn.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.
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:
elements contains snapshot records with snapshotDomain and snapshotData.snapshotData is a list of data generated for the requested domain.paging.links entries whose rel is next or prev.paging.total as a complete page count; the docs say offline systems can make it incomplete.Verified live (2026-10-05), overriding the docs where they differ:
{snapshotDomain, snapshotData[]}, and every domain arrived on one page (1,716 ALL_LIKES rows). paging.count and total are meaningless.EVENTS returns 400 "domain is not supported" although documented. Never request it.First Name, ShareLink, Date/Time (sometimes an empty "" key). Normalize to camelCase, drop empty values, de-duplicate.Date is YYYY-MM-DD HH:MM:SS in UTC, equal to the changelog capturedAt. Rows are newest-first.Link values are https://www.linkedin.com/feed/update/<percent-encoded URN>; a reaction on a comment adds ?commentUrn=urn:li:comment:(activity:N,M).Status is always empty (direction unknown).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.
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:
startTime is an inclusive epoch-millisecond timestamp.400.count=10; the upper limit is 50.processedAt as the next startTime cursor. If no event is returned, keep the same cursor for the next poll.capturedAt is the recommended event activity time when embedded activity timestamps are missing.id, capturedAt, processedAt, owner, actor, resourceName, resourceId, resourceUri, method, activity, activityId, and activityStatus. Live there is no `processedActivity`; only `activity` exists, so read activity (unwrap patch.$set if present).processedAt and paged with start; total is 0 (meaningless). Clamp count to 1..50. A 404 here is an error, not "empty".people (PARTIAL_UPDATE), people/positions (PARTIAL_UPDATE), socialActions/likes and socialActions/comments (CREATE), invitations (ACTION, URNs only), messages (inbox content: drop it). ugcPosts has not been seen live; its shape comes from the docs. Verify the `ugcPosts` merge live once a post event is seen.activityId, keep the last non-FAILURE record (FAILURE can be followed by SUCCESSFUL_REPLAY).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.
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.500 or 504: LinkedIn-side failure or timeout; include request id headers when available, but never include tokens.--mcp. Both are thin adapters over src/service.ts.McpServer and StdioServerTransport from @modelcontextprotocol/sdk.experience, reactions); internal domain names live only in src/linkedin/sections.ts. Sensitive domains are blocked there.content and machine-readable structuredContent; failures return isError: true.profile; large sections are paged with cursors.~30 seconds. Free. No account. Every finding cites a rule and a line of evidence.