This project provides a single Val Town HTTP val that reads your LinkedIn DMA (Member Data Portability) snapshot and exports it into several developer‑friendly CV formats:
- MAC (Manfred Awesomic CV) JSON
- JSON Resume JSON
- RenderCV YAML (downloadable file)
- Raw debug views of LinkedIn
memberSnapshotDatadomains
The val is stateless: LinkedIn is the only data source, and all outputs are pure transformations of the exported snapshot.
- Format repo: https://github.com/getmanfred/mac
- This val builds a MAC-like JSON document with:
settings.languageaboutMe.profile(title, summary, images)experience.jobsandexperience.projectsknowledge.hardSkills,knowledge.studies,knowledge.courses,knowledge.certifications,knowledge.languages
- Schema: https://jsonresume.org/schema/
- Reference schema repo: https://github.com/jsonresume/resume-schema
- This val fills the core sections:
basics(name, label, summary, image)workeducationskillslanguagesprojectscertificates
- YAML input structure: https://docs.rendercv.com/user_guide/yaml_input_structure/
- Project repo: https://github.com/rendercv/rendercv
- This val builds a
cvmodel compatible with RenderCV and returns it as a downloadablerendercv.yamlfile:cv.namecv.sections.experiencecv.sections.educationcv.sections.projects
This val uses LinkedIn’s Member Data Portability (DMA) APIs and the
memberSnapshotData endpoint. These APIs are subject to important limitations
and eligibility rules.
- Member portability APIs overview (LinkedIn Help):
https://www.linkedin.com/help/linkedin/answer/a6214075 - Member Data Portability (Member) – Microsoft Learn:
https://learn.microsoft.com/en-us/linkedin/dma/member-data-portability/member-data-portability-member/ - Portability API terms:
https://www.linkedin.com/legal/l/portability-api-terms
- The DMA Member Data Portability APIs are designed to comply with the EU Digital Markets Act (DMA) and are available only to members located in the European Economic Area (EEA) and Switzerland (or other regions explicitly listed by LinkedIn at the time of use).
- Outside these regions, members may not be able to authorize or use the Member Data Portability APIs at all.
The data returned by memberSnapshotData does not replicate the LinkedIn UI
exactly:
- Many profile views in the UI are assembled from multiple internal systems and include presentation logic that is not visible through the DMA APIs.
- The profile data returned by the DMA APIs is typically in one primary language only. If you maintain multiple language versions of your profile, only the primary language may appear.
- Some profile elements and relationships (for example, how skills are attached to specific positions) may not be exposed as structured data.
- Availability of domains depends on the member’s activity and privacy settings.
For some domains you may receive empty arrays or
nulleven though the domain exists.
Design your downstream processing with these constraints in mind: the val provides a best-effort mapping of what LinkedIn exposes, not a pixel‑perfect reconstruction of the UI profile.
The val expects a LinkedIn DMA access token (Member Data Portability API). It does not implement the OAuth flow itself. You can obtain a token via:
- LinkedIn’s official OAuth Token Generator in the Developer Portal, or
- Your own OAuth 2.0 client configured for the Member Data Portability product
(for example, scope
r_dma_portability_3rd_partyorr_dma_portability_self_serve, depending on your setup).
Token lookup order for every request:
Authorization: Bearer <token>HTTP header?token=<token>query parameter- Val Town environment variable
token
If no token is found, the val returns HTTP 401.
All profile data is fetched from this endpoint:
GET https://api.linkedin.com/rest/memberSnapshotData?q=criteria&domain=<DOMAIN> LinkedIn-Version: 202312 Authorization: Bearer <token>
The val wraps this in a helper fetchRawDomain(token, domain) that returns the
snapshotData array for the domain (or null in case of 404 / no data).
To avoid repeated HTTP calls per format, the val aggregates multiple domains into a single in‑memory structure:
PROFILE– core profile (headline, summary, first and last name)SKILLS– list of skillsPOSITIONS– work experienceEDUCATION– education historyPROJECTS– project entriesCERTIFICATIONS– certificationsCOURSES– coursesLANGUAGES– languages and proficienciesRICH_MEDIA– log of uploaded images (profile photos, background photos, etc.)
This aggregate is returned directly by /profile and is also used as input for
MAC, JSON Resume, and RenderCV mappers.
The RICH_MEDIA domain acts as an activity log. The val scans it to determine:
- Profile photo – latest entry whose
Date/Timetext contains"profile photo". - Profile background photo – latest entry whose
Date/Timetext contains"profile background photo".
These URLs are then stored in:
- MAC:
aboutMe.profile.imageUrl,aboutMe.profile.bannerUrl. - JSON Resume:
basics.image.
All endpoints are GET and share the same token rules described above.
Returns the aggregated LIFullProfile JSON object:
profile,skills,positions,educationprojects,certifications,courses,languagesrichMedia
Use this for debugging, inspection, or building your own mappings.
Example:
curl "https://<your-val>.web.val.run/profile" -H "Authorization: Bearer <LINKEDIN_TOKEN>"
Returns a MAC-format JSON document derived from LIFullProfile.
Key mappings:
aboutMe.profile.title←PROFILE.HeadlineaboutMe.profile.description←PROFILE.SummaryaboutMe.profile.imageUrl/bannerUrl←RICH_MEDIAinferenceexperience.jobs←POSITIONSexperience.projects←PROJECTSknowledge.hardSkills←SKILLSknowledge.studies←EDUCATIONknowledge.courses←COURSESknowledge.certifications←CERTIFICATIONSknowledge.languages←LANGUAGES
Example:
curl "https://<your-val>.web.val.run/mac" -H "Authorization: Bearer <LINKEDIN_TOKEN>" > mac.json
Returns a JSON Resume object.
Populated sections:
basicsname= "First Name Last Name"label= LinkedIn headlineimage= profile photo URLsummary= LinkedIn summary
workfromPOSITIONSeducationfromEDUCATIONskills– a single entry with keyword list derived fromSKILLSlanguagesfromLANGUAGESprojectsfromPROJECTScertificatesfromCERTIFICATIONS
Example:
curl "https://<your-val>.web.val.run/jsonresume" -H "Authorization: Bearer <LINKEDIN_TOKEN>" > resume.json
You can then use any JSON Resume tooling (themes, renderers, HTML/PDF
converters) on resume.json.
Returns a RenderCV model encoded as YAML and forces a file download named
rendercv.yaml.
- The val maps
LIFullProfileinto acvobject with:cv.name– full namecv.sections.experience– fromPOSITIONScv.sections.education– fromEDUCATIONcv.sections.projects– fromPROJECTS
- The object is converted to YAML using the
yamlpackage and served with headers:Content-Type: text/yaml; charset=utf-8Content-Disposition: attachment; filename="rendercv.yaml"
Example:
curl -L "https://<your-val>.web.val.run/rendercv" -H "Authorization: Bearer <LINKEDIN_TOKEN>" -o rendercv.yaml
This rendercv.yaml file can be passed to the RenderCV CLI to generate a nicely
formatted CV.
Debug endpoint for raw memberSnapshotData.
Supported modes:
-
GET /debug(nodomainsparameter or empty):- Fetches a default set of domains:
PROFILE,PROFILE_SUMMARY,PROJECTS,SKILLS,POSITIONS,EDUCATION,CERTIFICATIONS,COURSES,LANGUAGES,RICH_MEDIA.
- Fetches a default set of domains:
-
GET /debug?domains=all:- Fetches all known domains listed in the code’s
KNOWN_DOMAINSarray (profile, positions, education, rich media, connections, inbox, etc.). Note that some of these may be empty or unavailable for a given account.
- Fetches all known domains listed in the code’s
-
GET /debug?domains=PROJECTS,SKILLS,LANGUAGES:- Fetches exactly the specified comma-separated domains.
Response shape:
{ "requested_domains": ["PROFILE", "PROJECTS"], "data": { "PROFILE": [/* raw snapshotData */], "PROJECTS": [/* raw snapshotData */] } }
Example:
curl "https://<your-val>.web.val.run/debug?domains=RICH_MEDIA" -H "Authorization: Bearer <LINKEDIN_TOKEN>"
Use this endpoint to explore what LinkedIn actually returns for your account before adjusting mappings.
- Create a new HTTP val on https://val.town.
- Paste the TypeScript code (the
export default async function handler(req: Request)implementation). - In the val’s Settings → Environment Variables, set:
token = <your LinkedIn DMA access token>(optional but convenient).
- Visit the val’s URL with one of the endpoints, for example:
https://<your-val>.web.val.run/machttps://<your-val>.web.val.run/jsonresumehttps://<your-val>.web.val.run/rendercv
If you do not set token as an environment variable, always pass the token
either as Authorization: Bearer header or as ?token=... in the query string.
- The val cannot override LinkedIn’s data export policies or limitations. It only reshapes what the DMA API provides.
- Expect differences between what you see in the LinkedIn UI and what appears in the exported data (missing languages, missing links between objects, or missing sections entirely).
- Before relying on the data for automation or large-scale processing, inspect
several responses from
/debugand/profilefor your account to understand exactly which domains and fields are available.