Lever Jobs Scraper - Postings API & New-Job Tracking avatar

Lever Jobs Scraper - Postings API & New-Job Tracking

Pricing

from $1.50 / 1,000 jobs

Go to Apify Store
Lever Jobs Scraper - Postings API & New-Job Tracking

Lever Jobs Scraper - Postings API & New-Job Tracking

Every open posting on any company's Lever job site, global or EU, read from the official Lever Postings API: title, team, locations, workplace type, commitment, salary where published, description and dates. Built-in change tracking reports new, updated and removed jobs.

Pricing

from $1.50 / 1,000 jobs

Rating

0.0

(0)

Developer

Cedarfetch Labs

Cedarfetch Labs

Maintained by Community

Actor stats

0

Bookmarked

1

Total users

0

Monthly active users

an hour ago

Last modified

Categories

Features

Limited permissions

Share

Get every open posting on any company's Lever job site, straight from the official Lever Postings API (api.lever.co, and api.eu.lever.co for Lever's EU instance), the same public feed that powers the company's jobs.lever.co page. One clean row per job: title, department, team, locations, workplace type (remote, hybrid, on-site), commitment as a normalised employment type, salary range where the company publishes one, description as text and HTML, created date and links.

Turn on change tracking and schedule it: every row then says whether the job is new, updated (with the fields that changed), unchanged, or removed since the last run. With "Output only changes", a daily run returns just the postings that opened and closed.

No browser, no proxies, no login. Postings are read 100 per request.

What you can use it for

  • Recruiters and sourcers: watch the Lever sites of target companies and see new roles the day they open.
  • Job boards and aggregators: clean, normalised postings from hundreds of Lever companies, with closed jobs flagged so you can expire them.
  • Sales and investors: hiring as a buying or growth signal ("which accounts opened sales roles in London this month?").
  • Labour-market analysts: remote share, team mix and published salary ranges across Lever employers.
  • AI agents and pipelines: one stable JSON shape you can rely on.

Why this one

  • Official API only. Reads the documented Lever Postings API. Nothing is scraped from HTML, nothing is bypassed.
  • Global and EU instances, automatically. A jobs.eu.lever.co URL goes to the EU API. A bare token is tried on the global instance first and on the EU instance when the global one has no such site, so you never need to know where a company is hosted.
  • Every input form works. Job-site URLs, single-posting links, apply links, API URLs, or just the site token.
  • Normalised fields. Lever's free-text commitment ("Full-time", "Permanent", "Intern"...) becomes employmentType; workplaceType comes from Lever's own field; the raw labels are kept alongside.
  • Change tracking built in. New, updated, unchanged and removed postings, with the changed fields and their previous values. Removed postings are only reported after a complete, error-free read of the site.
  • Honest failures. Unknown sites, empty sites and typos are reported per site in plain words, never as a silent empty dataset. If nothing at all could be read, the run fails and says why.
  • Same schema as ATS Jobs Scraper. Datasets from this Actor, the Greenhouse and Ashby editions, and the multi-system ATS Jobs Scraper combine without any mapping.

How to find a company's Lever site token

Open the company's careers page and click any job. If it opens on jobs.lever.co/palantir/... (or jobs.eu.lever.co/<token>/...), the token is the first part of the path: palantir. Paste the whole URL; the Actor reads the token and the instance from it.

To check a token by hand: https://api.lever.co/v0/postings/<token>?mode=json returns a JSON list for a real site (an empty list when it has no open postings) and 404 otherwise. Try api.eu.lever.co if the global one says 404.

Input

Only companies is required. One entry per line:

You pasteRead as
https://jobs.lever.co/palantirsite palantir, global instance
https://jobs.lever.co/palantir/<posting-id> or .../<posting-id>/applysite palantir (a single-posting link reads the whole site)
https://jobs.eu.lever.co/leverdemosite leverdemo, EU instance
https://api.lever.co/v0/postings/palantir?mode=jsonsite palantir, global instance
palantir or lever:palantirsite palantir: global instance, then EU if the global one has no such site
lever-eu:acmesite acme, EU instance only

Duplicate entries for the same site are merged. A URL for a different system (Greenhouse, Ashby, SmartRecruiters...) is not read; it gets a free status row that names the Actor that reads it.

Example: engineering roles at two companies

{
"companies": ["https://jobs.lever.co/palantir", "spotify"],
"titleKeywords": ["engineer*"],
"excludeTitleKeywords": ["intern"],
"includeDescription": false
}

Example: remote full-time roles posted this week

{
"companies": ["palantir", "spotify", "zoox"],
"remoteOnly": true,
"employmentTypes": ["full_time"],
"postedAfter": "7 days"
}

All options

FieldDefaultWhat it does
companies(required)Lever sites in any form above. Up to 2,000 per run.
maxJobsPerCompany0 (all)Stop after this many matching postings per site.
includeDescriptiontrueAdd descriptionText and descriptionHtml (no extra request on Lever).
titleKeywordsnoneKeep titles containing these words.
titleKeywordsModeanyany or all of the keywords.
excludeTitleKeywordsnoneDrop titles containing any of these words.
locationsnoneKeep postings whose locations or country match any of these words.
departmentsnoneKeep postings whose department or team matches.
remoteOnlyfalseKeep only isRemote: true.
postedAfternoneISO date (2026-11-01) or relative (7 days, 24 hours, 2 weeks), compared with the posting's creation time.
employmentTypesnoneAny of full_time, part_time, contract, temporary, internship, apprenticeship, volunteer, other.
trackChangesfalseCompare with the previous run and label every row.
changesOnlyfalseWith tracking on, deliver only the selected change types.
changeTypesnew, updated, removedWhich changes changesOnly delivers.
snapshotStoreNamelever-jobs-scraper-snapshotsNamed key-value store for snapshots. Use different names for independent feeds.
concurrency2Parallel requests per site.
requestDelayMs250Pause after each request to the same site.
maxConcurrentCompanies4Sites read at the same time.
requestTimeoutSecs60Timeout per request (Lever pages can take 20+ seconds).
maxRetries4Retries for 429, 5xx and network errors.

Keyword matching (titles, locations, departments) is case- and accent-insensitive and works on whole words: engineer matches "Senior Engineer" but not "Engineering"; engineer* matches both; US matches "Remote (US)" but not "Austin"; phrases such as data scientist work. Filters run before maxJobsPerCompany, so the cap counts matching postings.

Output

One row per posting in the default dataset. Download as JSON, CSV, Excel or XML from the Output tab or the API. Views: Jobs, Changes, Job details, Companies that could not be read.

A real row from a local run on 2026-11-11 (spotify; description shortened):

{
"recordType": "job",
"ats": "lever",
"companyToken": "spotify",
"companyName": null,
"jobId": "2a55d6c9-c564-443a-be3e-a3851db5a93b",
"requisitionId": null,
"title": "Account Executive - Commercial, Spotify Technology",
"department": "Operations and Business Support",
"team": "Platform",
"location": "New York, NY",
"locations": ["New York, NY"],
"country": "US",
"isRemote": false,
"workplaceType": "hybrid",
"employmentType": "full_time",
"employmentTypeRaw": "Permanent",
"compensation": null,
"descriptionText": "Spotify Technology is our business-to-business software team, helping ...",
"descriptionHtml": "<div>\n<p>Spotify Technology is our busin...",
"postedAt": "2026-10-23T15:00:40.768Z",
"updatedAt": null,
"jobUrl": "https://jobs.lever.co/spotify/2a55d6c9-c564-443a-be3e-a3851db5a93b",
"applyUrl": "https://jobs.lever.co/spotify/2a55d6c9-c564-443a-be3e-a3851db5a93b/apply",
"changeType": null,
"changedFields": null,
"previous": null,
"firstSeenAt": null,
"removedAt": null,
"matchedBy": "default_ats",
"input": "spotify",
"source": "api",
"scrapedAt": "2026-11-12T00:41:22.791Z"
}

Dates are ISO 8601 in UTC; numbers are numbers; jobId is always a string. The schema is identical to ATS Jobs Scraper, which is why ats is a field.

What Lever exposes, and what it does not

FieldOn Lever
companyNameNot exposed by the Postings API: always null. Use companyToken.
requisitionIdNot exposed: always null.
department, teamLever's department and team categories.
location, locations, countryPrimary location, every listed location (allLocations), and the country code when set. Not geocoded.
workplaceType, isRemoteFrom Lever's own workplaceType (remote, hybrid, on-site), plus location names such as "Remote"; null when the company left it unspecified. In a full run of four sites, 388 of 712 postings had a known remote status.
employmentTypeMapped from the commitment label (650 of 712 postings had one); employmentTypeRaw keeps Lever's text.
compensationLever's salaryRange (min, max, currency, interval) and salary text, when the company publishes them. Many companies do not.
postedAtThe posting's creation time (Lever exposes no separate publish date).
updatedAtNot exposed: always null. Change tracking compares content instead.
descriptionText / descriptionHtmlYes: the opening, the lists (responsibilities, requirements) and the closing, included in the list at no extra cost.

Sites that could not be read

A free company_status row is added for every entry that could not be read, with a status and a plain-language message:

statusMeaning
not_foundNeither Lever instance has a public site with that token (typo, or the company uses another system).
no_jobsThe site exists but has no open postings.
blockedLever refused the request (401/403) or answered with a bot-protection challenge. Never worked around.
rate_limitedStill rate-limited after polite retries. Try later or lower concurrency.
unreachable / errorNetwork failure, timeout or an unexpected answer.
invalid_inputNot a Lever URL or token, or a board on another system (the message names the Actor that reads it).
partial(in OUTPUT) Some postings were read, then the run hit an error or the charge limit.

If a run finds postings but delivers none (all filtered out, or nothing changed in changes-only mode), one free company_status row per site explains it.

Run summary

The default key-value store record OUTPUT has, per site: the instance used (region: global or eu), how the input was resolved, status, postings found / matched / delivered, removed postings reported, requests, retries, duration, whether the site was read completely, filter counts, change counts and warnings. Use it to monitor scheduled runs.

Change tracking

Turn on Track changes between runs and schedule the Actor. Each run compares every matching posting with the snapshot from the previous run of the same site and filters, and adds:

FieldMeaning
changeTypenew, updated, unchanged or removed.
changedFieldsFor updated: e.g. ["title", "compensation"]. Tracked: title, department, team, locations, workplaceType, isRemote, employmentType, compensation, description.
previousFor updated: the previous values of the changed fields (for description only the change is detected; old text is not stored).
firstSeenAtWhen the posting first appeared in this feed.
removedAtFor removed rows: when the removal was detected. Removed rows carry the last known title, location, department, dates and links.

Rules you can rely on:

  • The first tracked run labels every posting new. It sets the baseline.
  • removed needs a complete read. Lever sites are read in pages of 100. If the site could not be read, or the read was cut short by maxJobsPerCompany or the charge limit before the last page, nothing is labelled removed; the next complete run reports it. Leave maxJobsPerCompany at 0 on tracked runs of large sites.
  • Nothing is lost on a budget stop. The snapshot only records postings that were delivered, so a change that did not fit in this run is reported in the next one.
  • Filters define the feed. A posting that stops matching your filters is not "removed"; only postings that disappear from the site are. Each combination of site and filters has its own snapshot.
  • Descriptions are compared by hash, even with includeDescription off (Lever sends them with the list).
  • Snapshots live in the named key-value store snapshotStoreName in your Apify account (compressed, about 60 bytes per posting). Delete it, or pick a new name, to start over.

Tested locally on 2026-11-11 with leverdemo and spotify (95 postings): run 1 = 95 new, run 2 = 95 unchanged; after editing the snapshot, a changes-only run delivered exactly the 2 new, 2 updated and 2 removed postings.

Recipe: daily new-jobs feed

  1. Create a task with your sites and filters, plus:
{
"companies": ["palantir", "spotify", "zoox"],
"titleKeywords": ["engineer*", "data"],
"trackChanges": true,
"changesOnly": true,
"changeTypes": ["new"],
"includeDescription": false
}
  1. Run it once to set the baseline (every posting is new on that run).
  2. Schedule it daily. From then on each run delivers only the postings that opened since the previous run, and you pay only for those. Add "removed" to changeTypes to also get closed postings.
  3. Connect the dataset to Slack, email, a Google Sheet or a webhook through Apify integrations.

Pricing

Pay per event: one job event per posting row delivered to the dataset (including removed rows), plus Apify's small run-start fee. See the Pricing tab for the current price.

  • company_status rows are free.
  • With changes-only output, unchanged postings are not delivered and not charged.
  • Set Maximum cost per run in the run options to cap spending. The Actor stops cleanly at the cap, keeps everything delivered so far, and notes it in OUTPUT.

Use from an AI agent or via API

Ready-to-run Python, Node.js and cURL examples: github.com/cedarfetch/ats-jobs-scraper-examples. The examples use the multi-system ATS Jobs Scraper; change the Actor id to this one.

Run synchronously and get the rows back in one HTTP call:

curl -X POST "https://api.apify.com/v2/acts/cedarfetch~lever-jobs-scraper/run-sync-get-dataset-items?token=$APIFY_TOKEN" \
-H "Content-Type: application/json" \
-d '{"companies":["spotify"],"titleKeywords":["engineer*"],"maxJobsPerCompany":50,"includeDescription":false}'

JavaScript:

import { ApifyClient } from 'apify-client';
const client = new ApifyClient({ token: process.env.APIFY_TOKEN });
const run = await client.actor('cedarfetch/lever-jobs-scraper').call({ companies: ['palantir'], trackChanges: true, changesOnly: true });
const { items } = await client.dataset(run.defaultDatasetId).listItems();

Python:

from apify_client import ApifyClient
client = ApifyClient("<APIFY_TOKEN>")
run = client.actor("cedarfetch/lever-jobs-scraper").call(run_input={"companies": ["palantir"]})
items = client.dataset(run["defaultDatasetId"]).list_items().items

The Actor is also available to agents through the Apify MCP server, which exposes Store Actors as tools. Tips for agents: pass site tokens or URLs; keep maxJobsPerCompany small and includeDescription off for exploratory calls; filter on recordType == "job"; read OUTPUT for per-site status.

Limits and honest caveats

  • Public postings only. It reads what the company publishes on its Lever site. Internal, confidential and unlisted postings are out of reach by design. Nothing about applicants or employees is collected; names that appear inside a description are not extracted into fields.
  • Lever is slow on large sites. A page of 100 postings can take 3 to 27 seconds (Palantir's 303 postings took 28 s in a local run). The default timeout is 60 s per request, with retries.
  • No company name, update date or requisition code in the Postings API (see the table above).
  • A bare token on the EU instance costs one extra request (the global instance answers 404 first). Paste the jobs.eu.lever.co URL to skip it.
  • Language and location formats are as each company writes them. Locations are not geocoded.

FAQ

Is this the Lever Data API? No. Lever's Data API needs a company's private key and exposes candidates. This Actor uses only the public Postings API, which exposes published postings and nothing about candidates.

Can I read many companies in one run? Yes, up to 2,000 sites. Four are read at a time by default.

Why does a company show fewer postings than its website? Some companies run several Lever sites, or list jobs from other systems too. You get the sites you pass. Check OUTPUT for filters and caps.

Do I need proxies? No. The Postings API is meant to be read by anyone. Sites that refuse plain requests are reported, not worked around.

What happens to a posting that is reposted? It usually gets a new Lever id, so it appears as new and the old one as removed.

Is this legal? The Actor reads job postings that companies publish through a public API intended for that purpose, and collects no personal data about applicants or employees. You are responsible for how you use the data and for the source's terms. If in doubt, ask a lawyer.

Not affiliated with Lever. "Lever" is used only to describe which public API this Actor reads.

Support

Found a site that behaves oddly? Open an issue on the Issues tab with the site token and the run ID; the OUTPUT record usually shows what happened.

More Actors from Cedarfetch Labs

Built the same way: public data only, typed output, per-source error reporting, change tracking for scheduled runs.

  • ATS Jobs Scraper: the multi-system option. Greenhouse, Lever, Ashby, SmartRecruiters, Workable, Recruitee and Personio in one run, same schema, with auto-detection of a company's system.
  • Greenhouse Jobs Scraper: every job on any Greenhouse board, with pay ranges on request, from the official Greenhouse Job Board API.
  • Ashby Jobs Scraper: every job on any Ashby board, with salary where published, from Ashby's public Job Posting API.
  • Remote Jobs Scraper: remote jobs from Remote OK, We Work Remotely and five more boards in one deduplicated feed.