{"componentChunkName":"component---src-gatsby-theme-chronoblog-templates-note-js","path":"/notes/lever-public-jobs-api-nodejs/","result":{"data":{"mdx":{"parent":{"__typename":"File","fields":{"gitLogLatestDate":"2026-07-01 22:50:32 +0200"}},"id":"994a457f-9e53-5efc-a6bd-b6beafe36645","excerpt":"Lever  is an ATS with a public  Postings API  for read-only access to published job listings. No API key is required - you only need the…","frontmatter":{"title":"Integration with Lever public jobs API","date":"2026-07-02 09:00:00 UTC","job_ad":null,"job_ad_id":null,"job_ad_url":null,"tags":["lever","ats","api","node"],"cover":{"childImageSharp":{"fluid":{"base64":"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAKCAYAAAC0VX7mAAAACXBIWXMAAAPoAAAD6AG1e1JrAAAAfklEQVQoz+WRUQrEIAxEe/+j1rZs1KhR6yuWZU/g3waGhDcwDGRj8WzLA8cYrNT6hl4aJXfM7KuKamfypB0rN8G3V/OezPtGDJ1q97unN5nZYHN7IsZKCB4RIfiAfIx9j4gYqpXDKYdLJG0vc065zkJOjevMP7/k+y+fsjrwAeD6F9M6RDH0AAAAAElFTkSuQmCC","aspectRatio":2.0869565217391304,"src":"/static/f09632976ca044b4bf7c7d5e89b3a6e2/c4ecb/cover.png","srcSet":"/static/f09632976ca044b4bf7c7d5e89b3a6e2/57ab0/cover.png 192w,\n/static/f09632976ca044b4bf7c7d5e89b3a6e2/f4739/cover.png 384w,\n/static/f09632976ca044b4bf7c7d5e89b3a6e2/c4ecb/cover.png 768w","srcWebp":"/static/f09632976ca044b4bf7c7d5e89b3a6e2/dd090/cover.webp","srcSetWebp":"/static/f09632976ca044b4bf7c7d5e89b3a6e2/ae504/cover.webp 192w,\n/static/f09632976ca044b4bf7c7d5e89b3a6e2/fef30/cover.webp 384w,\n/static/f09632976ca044b4bf7c7d5e89b3a6e2/dd090/cover.webp 768w","sizes":"(max-width: 768px) 100vw, 768px","presentationWidth":768,"presentationHeight":366},"resize":{"src":"/static/f09632976ca044b4bf7c7d5e89b3a6e2/c4ecb/cover.png"}}}},"fields":{"slug":"/notes/lever-public-jobs-api-nodejs/","readingTime":{"text":"4 min read"}},"body":"function _extends() { _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; return _extends.apply(this, arguments); }\n\nfunction _objectWithoutProperties(source, excluded) { if (source == null) return {}; var target = _objectWithoutPropertiesLoose(source, excluded); var key, i; if (Object.getOwnPropertySymbols) { var sourceSymbolKeys = Object.getOwnPropertySymbols(source); for (i = 0; i < sourceSymbolKeys.length; i++) { key = sourceSymbolKeys[i]; if (excluded.indexOf(key) >= 0) continue; if (!Object.prototype.propertyIsEnumerable.call(source, key)) continue; target[key] = source[key]; } } return target; }\n\nfunction _objectWithoutPropertiesLoose(source, excluded) { if (source == null) return {}; var target = {}; var sourceKeys = Object.keys(source); var key, i; for (i = 0; i < sourceKeys.length; i++) { key = sourceKeys[i]; if (excluded.indexOf(key) >= 0) continue; target[key] = source[key]; } return target; }\n\n/* @jsxRuntime classic */\n\n/* @jsx mdx */\nvar _frontmatter = {\n  \"title\": \"Integration with Lever public jobs API\",\n  \"date\": \"2026-07-02 09:00:00 UTC\",\n  \"cover\": \"./cover.png\",\n  \"tags\": [\"lever\", \"ats\", \"api\", \"node\"],\n  \"canonical_url\": \"https://sevic.dev/notes/lever-public-jobs-api-nodejs/\"\n};\nvar layoutProps = {\n  _frontmatter: _frontmatter\n};\nvar MDXLayout = \"wrapper\";\nreturn function MDXContent(_ref) {\n  var components = _ref.components,\n      props = _objectWithoutProperties(_ref, [\"components\"]);\n\n  return mdx(MDXLayout, _extends({}, layoutProps, props, {\n    components: components,\n    mdxType: \"MDXLayout\"\n  }), mdx(\"p\", null, mdx(\"a\", _extends({\n    parentName: \"p\"\n  }, {\n    \"href\": \"https://www.lever.co/\"\n  }), \"Lever\"), \" is an ATS with a public \", mdx(\"a\", _extends({\n    parentName: \"p\"\n  }, {\n    \"href\": \"https://github.com/lever/postings-api\"\n  }), \"Postings API\"), \" for read-only access to published job listings. No API key is required - you only need the company's Lever site slug.\"), mdx(\"p\", null, \"This post covers fetching postings with pagination, optional filters, and EU vs US API hosts. For other ATS public feeds, see the \", mdx(\"a\", _extends({\n    parentName: \"p\"\n  }, {\n    \"href\": \"https://sevic.dev/notes/ashby-public-jobs-api-nodejs/\"\n  }), \"Ashby\"), \", \", mdx(\"a\", _extends({\n    parentName: \"p\"\n  }, {\n    \"href\": \"https://sevic.dev/notes/greenhouse-public-jobs-api-nodejs/\"\n  }), \"Greenhouse\"), \", and \", mdx(\"a\", _extends({\n    parentName: \"p\"\n  }, {\n    \"href\": \"https://sevic.dev/notes/workable-public-jobs-api-nodejs/\"\n  }), \"Workable\"), \" posts.\"), mdx(\"p\", null, \"Lever's authenticated \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, \"Data API\"), \" (\", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"https://api.lever.co/v1/...\"), \") manages candidates and pipeline data and is separate from the public postings feed.\"), mdx(\"h3\", {\n    \"id\": \"prerequisites\"\n  }, \"Prerequisites\"), mdx(\"ul\", null, mdx(\"li\", {\n    parentName: \"ul\"\n  }, \"Node.js version 26\"), mdx(\"li\", {\n    parentName: \"ul\"\n  }, \"A company's Lever \", mdx(\"strong\", {\n    parentName: \"li\"\n  }, \"site slug\"), \" (see below)\"), mdx(\"li\", {\n    parentName: \"ul\"\n  }, \"No API key required for the Postings API\")), mdx(\"h3\", {\n    \"id\": \"find-the-site-slug\"\n  }, \"Find the site slug\"), mdx(\"p\", null, \"Lever-hosted career pages use \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"https://jobs.lever.co/{site_slug}/\"), \". The first path segment after \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"jobs.lever.co\"), \" is the slug passed to the API.\"), mdx(\"p\", null, \"Examples: \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"unlimit\"), \" \\u2192 \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"https://jobs.lever.co/unlimit/\"), \", API \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"https://api.lever.co/v0/postings/unlimit\"), \".\"), mdx(\"p\", null, \"Some accounts are hosted in the EU region and answer on \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"https://api.eu.lever.co/v0/postings/{site_slug}\"), \" instead.\"), mdx(\"h3\", {\n    \"id\": \"api-overview\"\n  }, \"API overview\"), mdx(\"table\", null, mdx(\"thead\", {\n    parentName: \"table\"\n  }, mdx(\"tr\", {\n    parentName: \"thead\"\n  }, mdx(\"th\", _extends({\n    parentName: \"tr\"\n  }, {\n    \"align\": null\n  }), \"Item\"), mdx(\"th\", _extends({\n    parentName: \"tr\"\n  }, {\n    \"align\": null\n  }), \"Value\"))), mdx(\"tbody\", {\n    parentName: \"table\"\n  }, mdx(\"tr\", {\n    parentName: \"tbody\"\n  }, mdx(\"td\", _extends({\n    parentName: \"tr\"\n  }, {\n    \"align\": null\n  }), \"US base\"), mdx(\"td\", _extends({\n    parentName: \"tr\"\n  }, {\n    \"align\": null\n  }), mdx(\"inlineCode\", {\n    parentName: \"td\"\n  }, \"https://api.lever.co/v0/postings/{site_slug}\"))), mdx(\"tr\", {\n    parentName: \"tbody\"\n  }, mdx(\"td\", _extends({\n    parentName: \"tr\"\n  }, {\n    \"align\": null\n  }), \"EU base\"), mdx(\"td\", _extends({\n    parentName: \"tr\"\n  }, {\n    \"align\": null\n  }), mdx(\"inlineCode\", {\n    parentName: \"td\"\n  }, \"https://api.eu.lever.co/v0/postings/{site_slug}\"))), mdx(\"tr\", {\n    parentName: \"tbody\"\n  }, mdx(\"td\", _extends({\n    parentName: \"tr\"\n  }, {\n    \"align\": null\n  }), \"Auth\"), mdx(\"td\", _extends({\n    parentName: \"tr\"\n  }, {\n    \"align\": null\n  }), \"None\")), mdx(\"tr\", {\n    parentName: \"tbody\"\n  }, mdx(\"td\", _extends({\n    parentName: \"tr\"\n  }, {\n    \"align\": null\n  }), \"Format\"), mdx(\"td\", _extends({\n    parentName: \"tr\"\n  }, {\n    \"align\": null\n  }), \"JSON array (\", mdx(\"inlineCode\", {\n    parentName: \"td\"\n  }, \"mode=json\"), \")\")))), mdx(\"p\", null, \"Common query parameters:\"), mdx(\"table\", null, mdx(\"thead\", {\n    parentName: \"table\"\n  }, mdx(\"tr\", {\n    parentName: \"thead\"\n  }, mdx(\"th\", _extends({\n    parentName: \"tr\"\n  }, {\n    \"align\": null\n  }), \"Parameter\"), mdx(\"th\", _extends({\n    parentName: \"tr\"\n  }, {\n    \"align\": null\n  }), \"Description\"))), mdx(\"tbody\", {\n    parentName: \"table\"\n  }, mdx(\"tr\", {\n    parentName: \"tbody\"\n  }, mdx(\"td\", _extends({\n    parentName: \"tr\"\n  }, {\n    \"align\": null\n  }), mdx(\"inlineCode\", {\n    parentName: \"td\"\n  }, \"mode=json\")), mdx(\"td\", _extends({\n    parentName: \"tr\"\n  }, {\n    \"align\": null\n  }), \"Return JSON instead of HTML\")), mdx(\"tr\", {\n    parentName: \"tbody\"\n  }, mdx(\"td\", _extends({\n    parentName: \"tr\"\n  }, {\n    \"align\": null\n  }), mdx(\"inlineCode\", {\n    parentName: \"td\"\n  }, \"skip\"), \", \", mdx(\"inlineCode\", {\n    parentName: \"td\"\n  }, \"limit\")), mdx(\"td\", _extends({\n    parentName: \"tr\"\n  }, {\n    \"align\": null\n  }), \"Pagination (\", mdx(\"inlineCode\", {\n    parentName: \"td\"\n  }, \"limit\"), \" defaults to 100)\")), mdx(\"tr\", {\n    parentName: \"tbody\"\n  }, mdx(\"td\", _extends({\n    parentName: \"tr\"\n  }, {\n    \"align\": null\n  }), mdx(\"inlineCode\", {\n    parentName: \"td\"\n  }, \"team\"), \", \", mdx(\"inlineCode\", {\n    parentName: \"td\"\n  }, \"department\"), \", \", mdx(\"inlineCode\", {\n    parentName: \"td\"\n  }, \"location\"), \", \", mdx(\"inlineCode\", {\n    parentName: \"td\"\n  }, \"commitment\")), mdx(\"td\", _extends({\n    parentName: \"tr\"\n  }, {\n    \"align\": null\n  }), \"Filters; repeat a key for multiple values\")), mdx(\"tr\", {\n    parentName: \"tbody\"\n  }, mdx(\"td\", _extends({\n    parentName: \"tr\"\n  }, {\n    \"align\": null\n  }), mdx(\"inlineCode\", {\n    parentName: \"td\"\n  }, \"group\")), mdx(\"td\", _extends({\n    parentName: \"tr\"\n  }, {\n    \"align\": null\n  }), \"Group results by \", mdx(\"inlineCode\", {\n    parentName: \"td\"\n  }, \"location\"), \", \", mdx(\"inlineCode\", {\n    parentName: \"td\"\n  }, \"team\"), \", or \", mdx(\"inlineCode\", {\n    parentName: \"td\"\n  }, \"commitment\"))))), mdx(\"p\", null, \"Each posting includes \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"id\"), \", \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"text\"), \" (title), \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"hostedUrl\"), \", \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"applyUrl\"), \", \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"categories\"), \" (team, department, location), \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"workplaceType\"), \", \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"country\"), \", and plain-text description fields.\"), mdx(\"h3\", {\n    \"id\": \"basic-integration\"\n  }, \"Basic integration\"), mdx(\"p\", null, \"Fetch one page of postings:\"), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-js\"\n  }), \"const siteSlug = process.env.LEVER_SITE_SLUG ?? 'unlimit';\\nconst url = new URL(`https://api.lever.co/v0/postings/${encodeURIComponent(siteSlug)}`);\\nurl.searchParams.set('mode', 'json');\\nurl.searchParams.set('limit', '100');\\n\\nconst response = await fetch(url);\\n\\nif (!response.ok) {\\n  throw new Error(`Lever API ${response.status}: ${response.statusText}`);\\n}\\n\\nconst postings = await response.json();\\n\\nfor (const posting of postings) {\\n  console.log(posting.text, '-', posting.categories?.location, '-', posting.hostedUrl);\\n}\\n\")), mdx(\"p\", null, \"Paginate until a page returns fewer rows than your limit:\"), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-js\"\n  }), \"const PAGE_SIZE = 100;\\n\\nasync function fetchAllLeverPostings(siteSlug, baseUrl = 'https://api.lever.co/v0/postings') {\\n  const all = [];\\n  let skip = 0;\\n\\n  for (;;) {\\n    const url = new URL(`${baseUrl}/${encodeURIComponent(siteSlug)}`);\\n    url.searchParams.set('mode', 'json');\\n    url.searchParams.set('skip', String(skip));\\n    url.searchParams.set('limit', String(PAGE_SIZE));\\n\\n    const response = await fetch(url);\\n    if (!response.ok) {\\n      throw new Error(`Lever API ${response.status}`);\\n    }\\n\\n    const page = await response.json();\\n    if (!Array.isArray(page) || page.length === 0) break;\\n\\n    all.push(...page);\\n    if (page.length < PAGE_SIZE) break;\\n    skip += PAGE_SIZE;\\n  }\\n\\n  return all;\\n}\\n\")), mdx(\"p\", null, \"If the US host returns \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"404\"), \", retry against the EU host:\"), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-js\"\n  }), \"const EU_BASE = 'https://api.eu.lever.co/v0/postings';\\nconst US_BASE = 'https://api.lever.co/v0/postings';\\n\\nasync function fetchPostingsWithRegionFallback(siteSlug) {\\n  try {\\n    return await fetchAllLeverPostings(siteSlug, US_BASE);\\n  } catch (error) {\\n    if (String(error.message).includes('404')) {\\n      return fetchAllLeverPostings(siteSlug, EU_BASE);\\n    }\\n    throw error;\\n  }\\n}\\n\")), mdx(\"h3\", {\n    \"id\": \"filter-at-the-source\"\n  }, \"Filter at the source\"), mdx(\"p\", null, \"Request only roles in a team or location:\"), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-js\"\n  }), \"url.searchParams.append('team', 'Engineering');\\nurl.searchParams.append('location', 'Berlin');\\nurl.searchParams.append('location', 'Remote');\\n\")), mdx(\"p\", null, \"Fetch a single posting:\"), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-js\"\n  }), \"const detailUrl = `https://api.lever.co/v0/postings/${siteSlug}/${postingId}?mode=json`;\\n\")), mdx(\"h3\", {\n    \"id\": \"normalize-to-a-stable-shape\"\n  }, \"Normalize to a stable shape\"), mdx(\"pre\", null, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-js\"\n  }), \"function buildDescription(posting) {\\n  const parts = [\\n    posting.descriptionPlain,\\n    posting.openingPlain,\\n    posting.descriptionBodyPlain,\\n    posting.additionalPlain,\\n  ].filter(Boolean);\\n\\n  for (const list of posting.lists ?? []) {\\n    if (list.text && list.content) {\\n      parts.push(`${list.text}\\\\n${list.content.replace(/<[^>]+>/g, ' ')}`);\\n    }\\n  }\\n\\n  return parts.join('\\\\n\\\\n');\\n}\\n\\nfunction normalizeLeverPosting(posting, companyName) {\\n  const primary = posting.categories?.location?.trim() ?? '';\\n  const extras = (posting.categories?.allLocations ?? []).filter(\\n    (loc) => loc.trim().toLowerCase() !== primary.toLowerCase(),\\n  );\\n  const location = [primary, ...extras].filter(Boolean).join(' | ') || 'Unknown';\\n  const isRemote =\\n    posting.workplaceType?.toLowerCase() === 'remote' ||\\n    /remote/i.test(location);\\n\\n  return {\\n    id: posting.id ?? `${posting.text}:${posting.createdAt}`,\\n    title: posting.text.trim(),\\n    company: companyName,\\n    location,\\n    country: posting.country ?? null,\\n    isRemote,\\n    url: posting.hostedUrl || posting.applyUrl,\\n    postedAt: posting.createdAt ? new Date(posting.createdAt) : null,\\n    team: posting.categories?.team ?? null,\\n    commitment: posting.categories?.commitment ?? null,\\n    description: buildDescription(posting),\\n  };\\n}\\n\")), mdx(\"p\", null, \"Only \", mdx(\"strong\", {\n    parentName: \"p\"\n  }, \"published\"), \" postings appear in this API. Confidential or internal-only roles are never returned.\"));\n}\n;\nMDXContent.isMDXComponent = true;"}},"pageContext":{"id":"994a457f-9e53-5efc-a6bd-b6beafe36645"}},"staticQueryHashes":["1961101537","2542493696"]}