• Blog
  • Docs
  • Pricing
  • We’re hiring!
Log inSign up
foxdark410

foxdark410

linkedin-resume-ui

Presentation layer for JSON Resume data from linkedin-mdp-api
Public
Like
linkedin-resume-ui
Home
Code
2
README.md
H
main.ts
Environment variables
1
Branches
1
Pull requests
Remixes
History
Val Town is a collaborative website to build and scale JavaScript apps.
Deploy APIs, crons, & store data – all from the browser, and deployed in milliseconds.
Sign up now
Code
/
README.md
Code
/
README.md
Search
…
Viewing readonly version of main branch: v7
View latest version
README.md

linkedin-resume-ui

Presentation layer (HTTP val) that fetches JSON Resume data from linkedin-mdp-api and renders it as a styled HTML page in the browser.

Architecture: This val is a pure UI layer. It contains zero LinkedIn/OAuth logic — all data fetching and normalization lives in linkedin-mdp-api.


Live Endpoints

URLDescription
/302 redirect to the route configured in DEFAULT env var
/minimalSerif CSS-only theme (print-friendly)
/tailwindCard-based theme via Tailwind CDN
/jsonresumeServer-side render using a jsonresume.org theme

Query Parameters

ParamApplies toDescription
?photo=1/minimal, /tailwindShow basics.image as a circular avatar (96×96 px)
?theme=<name>/jsonresumejsonresume theme package name (default: even)

Known-working themes for /jsonresume: even, kendall, flat, stackoverflow. Themes that read HTML templates from disk (e.g. elegant, paper) will fail with a descriptive error — see Known Issues.


Environment Variables

Set these in the val's Environment Variables sidebar.

KeyRequiredDefaultDescription
DATA_API_URL—hardcoded linkedin-mdp-api endpointBase URL of the data API (no trailing slash)
DEFAULT—minimalDefault route for / redirect. Can include query string, e.g. tailwind?photo=1

Note: DEFAULT warning in logs ("DEFAULT" is not set) is harmless — the val falls back to minimal automatically.


Data Flow

Browser → /minimal (or /tailwind, /jsonresume)
           └─ fetch DATA_API_URL/jsonresume
                └─ linkedin-mdp-api
                     └─ LinkedIn DMA API (OAuth)

linkedin-mdp-api applies all data normalization:

  • double-space → \n paragraph trick
  • bullet character detection via BULLETS env var
  • photo extraction from RICH_MEDIA log
  • JSON Resume schema v1.0.0 compliance

linkedin-resume-ui applies presentation-only fixes on top:

  • flatSkills() — expands { name: "Skills", keywords: [...] } into chips
  • dedupDegree() — removes "X, X" duplicated degree strings from API
  • prose() — converts \n\n → <p>, \n → <br>, strips &nbsp;/\u00a0
  • dateRange() — formats ISO dates as YYYY-MM – YYYY-MM or YYYY-MM – Present
  • label normalization — \n-separated role titles → · separator

Error Handling

SituationBehaviour
LinkedIn API returns 429 / 401 / 5xxResponse body passed through as-is with original status code
Network error reaching DATA_API_URL502 + styled error page with URL shown
API returns non-JSON502 + styled error page
basics.name missing or empty502 + styled error page (guards against blank render)
Unknown route (e.g. /foo)404 + styled error page listing valid routes
jsonresume theme import fails500 + styled error page with hint

Current Version: v6

Changelog

VersionKey changes
v1–v3Initial themes, token passing, basic structure
v4Skills flatting, degree dedup, prose(), label normalization
v5DEFAULT redirect, ?photo=1, jsonresume server-side render via esm.sh, require → ESM fix
v6API error passthrough (no longer wraps in 502), basics.name validation, photo object-fit: cover scaling, photo removed from /jsonresume (theme-managed), fs-error hint for incompatible themes

TODO

High priority

  • Caching — add a short-lived cache (e.g. blob storage, 5–15 min TTL) for API responses to survive LinkedIn's 429 Too Many Requests day limit gracefully. Currently hitting the limit renders nothing.
  • /jsonresume photo control — jsonresume themes render photo unconditionally if basics.image is set. Consider stripping basics.image from the payload unless ?photo=1 is passed (mirror /minimal//tailwind behaviour).
  • PDF export — add a /pdf route using Puppeteer/@browserless/chrome or a print-CSS ?format=pdf flag.

Medium priority

  • Theme selector UI — a small nav bar listing available routes/themes so the user doesn't have to type URLs manually.
  • /jsonresume theme list — enumerate known-working themes with links, possibly as a /jsonresume?theme=list discovery endpoint.
  • Error page improvements — show the raw API error body (status + message) when the upstream returns 4xx/5xx, not just the status code.
  • basics.image proxy — LinkedIn photo URLs may expire or require auth headers; consider proxying through the val to avoid broken images.

Low priority / Nice to have

  • Add <meta og:…> tags for social sharing preview.
  • Dark mode via @media (prefers-color-scheme: dark).
  • Print-specific CSS polish for /tailwind (page breaks, margins).
  • ?lang= param to filter multi-language data (dependent on API support).

Known Issues

429 Too Many Requests from LinkedIn

LinkedIn DMA API has a per-day rate limit per application. Once exhausted, all endpoints return 429 verbatim until midnight UTC. No workaround exists without caching (see TODO above). Plan renders in the morning when the limit resets.

jsonresume themes with fs access fail

Themes such as elegant, paper, classy internally call fs.readFileSync() to load their Handlebars/HTML templates. This is incompatible with the Deno serverless runtime. These themes throw at import time. The error page now shows a descriptive hint. Only pure-JS themes (even, kendall, flat, stackoverflow) are known to work.

DEFAULT env var warning in logs

Val Town's runtime emits WARNING: "DEFAULT" is not set on every request. This is cosmetic — the val defaults to minimal. Set the env var to silence it.

LinkedIn API data is a snapshot (not real-time)

Profile edits on LinkedIn may take hours or up to a day to appear in API output. This is a LinkedIn DMA API limitation, not a bug in this val.


Related Vals

  • foxdark410/linkedin-mdp-api — data layer: fetches LinkedIn DMA snapshot, exports JSON Resume, MAC, RenderCV, Markdown, plain text.

Development Notes

File structure

linkedin-resume-ui/
└── main.ts   # Single HTTP handler — all themes, routing, helpers in one file

All logic intentionally lives in one file to keep the val self-contained and easy to fork. If the file grows beyond ~1000 lines, consider splitting into:

  • themes/minimal.ts
  • themes/tailwind.ts
  • themes/jsonresume.ts
  • utils.ts (esc, prose, dateRange, flatSkills, dedupDegree)
  • main.ts (router + error handler)

Deno import conventions (Val Town)

  • npm packages: import X from "npm:package-name"
  • esm.sh (for CJS packages without Deno support): import("https://esm.sh/package")
  • Val Town std lib: import { sqlite } from "https://esm.town/v/std/sqlite/main.ts"

Testing without LinkedIn token

When the LinkedIn API limit is hit (429), test locally by temporarily hardcoding a fixture JSON Resume object in main.ts and bypassing the fetch call:

// Temporary fixture for local dev / rate-limit debugging const json = { basics: { name: "Test User", label: "Engineer" }, work: [] };
FeaturesVersion controlCode intelligenceCLIMCP
Use cases
TeamsAI agentsSlackGTM
DocsShowcaseTemplatesNewestTrendingAPI examplesNPM packages
AboutAlternativesPricingBlogNewsletterCareers
We’re hiring!
Brandhi@val.townStatus
X (Twitter)
Discord community
GitHub discussions
YouTube channel
Bluesky
Open Source Pledge
Terms of usePrivacy policyAbuse contact
© 2026 Val Town, Inc.