Greenhouse, Lever and Ashby Jobs Scraper avatar

Greenhouse, Lever and Ashby Jobs Scraper

Pricing

from $1.50 / 1,000 job returneds

Go to Apify Store
Greenhouse, Lever and Ashby Jobs Scraper

Greenhouse, Lever and Ashby Jobs Scraper

Pricing

from $1.50 / 1,000 job returneds

Rating

0.0

(0)

Developer

Bravona

Bravona

Maintained by Community

Actor stats

0

Bookmarked

2

Total users

1

Monthly active users

15 hours ago

Last modified

Share

Greenhouse, Lever and Ashby Jobs Scraper collects every open job from company career boards hosted on Greenhouse, Lever and Ashby and returns them in one normalized dataset, with salary ranges and new / open / closed tracking between runs. Enter company board names or careers URLs and download the jobs as JSON, CSV or Excel.

Unofficial tool. Not affiliated with, endorsed by, or sponsored by Greenhouse, Lever or Ashby.

What does Greenhouse, Lever and Ashby Jobs Scraper do?

  • Reads public job boards on Greenhouse, Lever and Ashby through each platform's public job-board API. No login, no browser, no proxy.
  • Detects which ATS a company uses from its board name (slug) or careers URL.
  • Returns one schema for all three platforms.
  • Adds salary min, max, currency and period: structured where the ATS provides it, otherwise parsed from the job text, with a salarySource field saying which.
  • Marks each job as new, open or closed compared with your previous run, so a scheduled run shows what changed since yesterday.
  • Leaves out recruiter and hiring-manager fields and removes email addresses from description text.
  • Runs on Apify: call it via API, schedule runs, export to JSON, CSV or Excel, and connect integrations.

Who is it for?

Job seekers and job-search tools: watch the companies you care about and see new openings first. Job-market and labor analysts: track hiring by company, department, location and salary over time. Job-board and aggregator builders: feed one clean jobs feed from many company boards.

What data can you get from Greenhouse, Lever and Ashby job boards?

One record per job. Fields that can be empty are null; allLocations can be an empty list.

FieldTypeDescription
companystringCompany board name (slug), lowercased
atsstringWhich platform hosts the board: greenhouse, lever or ashby
jobIdstringThe job's ID on that platform
titlestringJob title
departmentstring or nullDepartment (Greenhouse: first department; Lever and Ashby: department)
teamstring or nullTeam (Greenhouse: second department; Lever and Ashby: team)
locationstring or nullPrimary job location as the ATS shows it
allLocationslist of stringsPrimary plus any other listed locations
isRemotebooleanTrue when the ATS marks the workplace as Remote or a listed location says remote, anywhere or work from home. Hybrid alone is not remote
employmentTypestring or nullFullTime, PartTime, Contract, Intern or Temporary. Greenhouse's API has no standard field, so it is often null there
salaryMinnumber or nullLowest salary in the range (null for "up to" amounts)
salaryMaxnumber or nullHighest salary in the range (null for "from" amounts)
salaryCurrencystring or nullISO code, e.g. USD, EUR, GBP
salaryPeriodstring or nullyear, month, week, day, hour, or null
salarySourcestring or nullstructured (from the ATS), parsed (from the job text), or null (no salary)
postedAtstring or nullWhen the job was published, ISO 8601 UTC (Greenhouse first_published, Lever createdAt, Ashby publishedAt)
statusstringnew, open or closed compared with the previous run
descriptionText / descriptionHtmlstring or nullJob description in the format you picked, with email addresses replaced by [email removed]. Only present when Job description is text or html; null on closed records
applyUrlstring or nullLink to apply (Greenhouse: the job's public URL, which may be on the company's own careers site)
jobUrlstring or nullLink to the job posting
firstSeenAtstringWhen this Actor first output the job (ISO 8601 UTC)
lastSeenAtstringThe last run that saw the job on its board
scrapedAtstringWhen this run read the board

No recruiter or hiring-manager fields are output.

Salary

  • Structured first. Greenhouse pay ranges (pay_transparency=true, the first range listed; amounts converted from cents), Lever salaryRange, Ashby compensation summary (the Salary component).
  • Otherwise parsed from the salary text and description. Supported formats: ranges ($120,000 - $150,000, between $100,000 and $130,000), the k suffix ($120k–$150k, $120-150K, $211.4K), single values (Salary: $95,000, up to £60k, from €50.000), hourly / daily / weekly / monthly / yearly wording ($45/hr, per day, monthly, p.a.), currencies USD, EUR, GBP, CAD and AUD by symbol or ISO code before or after the number ($, US$, CA$, C$, A$, AU$, €, £; a bare $ is read as USD); "up to" sets only salaryMax, "from" / "starting at" sets only salaryMin, and labels: a range marked base wins over one marked OTE.
  • Ambiguous text gives null: no currency, mixed currencies, funding or revenue amounts, benefits (bonus, HRA, relocation, budgets), max below min, a max more than 5× the min, hourly values over 1,000, or yearly values under 10,000. A single amount needs a pay word (salary, pay, compensation, base...) nearby or a period.
  • salaryPeriod comes from the ATS's interval field (Lever per-hour-wage, Ashby 1 YEAR, a Greenhouse range titled "Monthly Pay Range") or from period words in the text. When neither exists, amounts of 15,000 or more are treated as yearly and smaller amounts keep salaryPeriod: null.

Sources and terms

SourceAccess methodBravona's pacingWhat the source says
GreenhousePublic Job Board API (boards-api.greenhouse.io/v1/boards/{token}/jobs)2 requests per secondGreenhouse docs: "Job Board data is publicly available, so authentication is not required for any GET endpoints."
LeverPublic Postings API (api.lever.co/v0/postings/{company}; EU boards on api.eu.lever.co)1 request per secondLever postings API README: "all job postings in the published state are publicly viewable." The API's robots.txt sets Crawl-delay: 1.
AshbyPublic Job Postings API (api.ashbyhq.com/posting-api/job-board/{org})1 request per secondAshby docs: the API returns "data for all currently published Job Postings" for an organization.

Pacing is the Actor's own request rate, with backoff and retries on HTTP 429 and 5xx. Greenhouse and Ashby publish no rate limit. Requests identify the Actor with the User-Agent Bravona-ATSJobs/0.1 (+apify.com/bravona).

How to find a company's board name

Enter either the company's careers URL on Greenhouse, Lever or Ashby, or just its board name (slug), e.g. airbnb. The board name is the first part of the path in the board URL, which isn't always the company name.

ATSBoard URLExample
Greenhouseboards.greenhouse.io/<name> or job-boards.greenhouse.io/<name>https://job-boards.greenhouse.io/discord
Leverjobs.lever.co/<name> (EU: jobs.eu.lever.co/<name>)https://jobs.lever.co/spotify
Ashbyjobs.ashbyhq.com/<name>https://jobs.ashbyhq.com/ramp
  • Greenhouse: open any job on the company's careers page. If the address is boards.greenhouse.io/<name>/jobs/... or job-boards.greenhouse.io/<name>/jobs/..., <name> is the board name. If the careers page is on the company's own site, look for gh_jid= in job links or an embed boards.greenhouse.io/embed/job_board?for=<name>. You can paste the embed URL too.
  • Lever: job links look like jobs.lever.co/<name>/<job id>. For boards in Lever's EU region (jobs.eu.lever.co/<name>), paste the URL so the EU API is used directly.
  • Ashby: job links look like jobs.ashbyhq.com/<name>/<job id>.

With ATS on auto, a URL decides the platform. For a bare name the Actor checks Greenhouse, Lever (US, then EU) and Ashby and uses the board with the most jobs, because a name can exist on more than one ATS (Ramp has a leftover 1-job Greenhouse board and its real board on Ashby). The result is cached and reused on later runs; if the cached board disappears or goes empty, the Actor checks again.

Careers pages on other systems (Workday, iCIMS, SmartRecruiters, a company's own site without one of the boards above) are not supported.

How to use Greenhouse, Lever and Ashby Jobs Scraper

  1. Open the Actor and go to the Input tab.
  2. Enter companies: 1 to 500 board names or careers URLs (e.g. airbnb, spotify, ramp).
  3. Leave ATS on auto, or pick Greenhouse, Lever or Ashby.
  4. Optionally filter by title keywords, locations or remote only, choose how to include descriptions, and set Max jobs.
  5. Click Start, then download the dataset as JSON, CSV or Excel, or fetch it via the Apify API.

Track new and closed jobs

Every record carries a status on every run, whatever your settings. Only new since last run does not turn status on; it only limits output to new and closed records.

  • First run for a state key: every job is new.
  • Later runs: a job not returned before is new; a job returned before and still on the board is open; a job returned before that is gone from the board is closed (once). A closed record carries the job's last known fields, with lastSeenAt = the last run that saw it.
  • onlyNewSinceLastRun: true outputs only new and closed; open jobs are still tracked.
  • Order and caps: per company, closed records come first, then new and open together, most recently posted first. So maxJobsPerCompany: 5 gives the 5 most recent jobs, and on the next run those same jobs come back as open (with any newer posting as new).
  • "Seen" means delivered: a job counts as seen only after it has been output. A job held back by maxJobs, maxJobsPerCompany, a filter, or your max-charge limit is returned as new on a later run instead of being lost.
  • A company switches ATS: if the detected ATS differs from the one stored, all previously stored jobs are output as closed (free) and all current jobs as new, and the log shows a warning.
  • If a company's board cannot be read in a run (error or not found), its state is left unchanged and the error is in the run summary.

Where state lives: the named key-value store bravona-ats-jobs-state in your Apify account (it persists across runs, unlike a run's default store). Keys:

  • <stateKey>--<company>: the jobs seen for one company, with the ATS it was on. Characters other than letters, digits and !-_.'() are replaced by _ plus a short hash.
  • <stateKey>--INDEX: the list of companies under that state key, used by resetState.
  • DETECT--<company>: the cached auto-detected ATS (shared by all state keys; not cleared by resetState).

The default stateKey is jobs- plus a hash of the sorted, lowercased company list, so the same list always uses the same state and a different list starts fresh. Set your own stateKey to keep history when you add or remove companies, or to give two tasks with the same companies separate state. resetState: true deletes that state key's records before the run.

Schedule example: save a task with your company list, a fixed stateKey (e.g. daily-fintech) and onlyNewSinceLastRun: true, then add an Apify schedule with cron 0 7 * * *. Each morning the dataset holds only jobs posted and closed since the previous run. Connect the dataset to a webhook, Google Sheets or your database to process it.

Run summary

Each run writes OUTPUT to its default key-value store: the state store and key, totals by status, how many results were charged, whether the max-charge limit or maxJobs was reached, and per company: input, detected ATS (and Lever region), how it was detected (url, explicit, cache, probe, with what each probe found), jobs on the board, jobs matching filters, output counts by status, results charged, a previous ATS if it switched, and any error. A company that can't be read doesn't fail the run; the run fails only if no company could be read.

Input

FieldTypeRequiredDefaultAllowed values / limitsWhat it does
companiesarray of stringsyesnone1 to 500 entriesBoard slugs or careers URLs (patterns above). Duplicates (case-insensitive) are removed.
atsstringnoautoauto, greenhouse, lever, ashbyauto detects per company. A fixed value skips detection for bare slugs. A URL always wins over this setting.
titleKeywordsarray of stringsno[]anyKeep a job if its title contains any keyword (case-insensitive substring).
locationsarray of stringsno[]anyKeep a job if any entry is a case-insensitive substring of its location or one of its other listed locations. The entry Remote also matches jobs flagged remote.
remoteOnlybooleannofalseKeep only jobs with isRemote: true.
includeDescriptionstringnotexttext, html, noneAdds descriptionText, descriptionHtml, or neither.
onlyNewSinceLastRunbooleannofalseOutput only new and closed jobs (skip open).
maxJobsintegerno100001 to 100000Max output records per run (all companies, all statuses).
maxJobsPerCompanyintegernono limit1 to 100000Max output records per company (all statuses).
stateKeystringnoderived from the company listletters, digits, -, _, .; up to 100 charsName of the change-tracking state.
resetStatebooleannofalseDelete the stored state for this stateKey before the run.

Filters combine with AND (titleKeywords AND locations AND remoteOnly). Jobs removed by a filter are never output, never charged and never stored as seen.

Example input (this is also the prefilled input, and the input used for the output example below):

{
"companies": ["airbnb", "spotify", "ramp"],
"ats": "auto",
"includeDescription": "none",
"maxJobsPerCompany": 5
}

Output example

One record per job. Real, unedited records from a second run of the example input on Oct 30, 2026 (includeDescription: "none", so no description fields), one per ATS. The first run returned 15 jobs in about 3.3 seconds; this rerun returned the same 15 as open.

Greenhouse (airbnb):

{
"company": "airbnb",
"ats": "greenhouse",
"jobId": "8337229",
"title": "Senior Manager, Community Policy",
"department": "Public Policy",
"team": null,
"location": "United States",
"allLocations": [
"United States"
],
"isRemote": true,
"employmentType": null,
"salaryMin": 204000,
"salaryMax": 255000,
"salaryCurrency": "USD",
"salaryPeriod": "year",
"salarySource": "structured",
"postedAt": "2026-10-30T19:19:06.000Z",
"applyUrl": "https://careers.airbnb.com/positions/8337229?gh_jid=8337229",
"jobUrl": "https://careers.airbnb.com/positions/8337229?gh_jid=8337229",
"status": "open",
"firstSeenAt": "2026-10-30T19:56:03.273Z",
"lastSeenAt": "2026-10-30T19:58:06.840Z",
"scrapedAt": "2026-10-30T19:58:06.840Z"
}

Lever (spotify):

{
"company": "spotify",
"ats": "lever",
"jobId": "2d67c471-9015-41c0-a041-b982ec4a14fd",
"title": "Account Executive - Commercial, Spotify Technology",
"department": "Operations and Business Support",
"team": "Platform",
"location": "London",
"isRemote": false,
"allLocations": [
"London"
],
"employmentType": "FullTime",
"salaryMin": null,
"salaryMax": null,
"salaryCurrency": null,
"salaryPeriod": null,
"salarySource": null,
"postedAt": "2026-10-23T21:51:17.203Z",
"applyUrl": "https://jobs.lever.co/spotify/2d67c471-9015-41c0-a041-b982ec4a14fd/apply",
"jobUrl": "https://jobs.lever.co/spotify/2d67c471-9015-41c0-a041-b982ec4a14fd",
"status": "open",
"firstSeenAt": "2026-10-30T19:56:03.273Z",
"lastSeenAt": "2026-10-30T19:58:06.840Z",
"scrapedAt": "2026-10-30T19:58:06.840Z"
}

Ashby (ramp):

{
"company": "ramp",
"ats": "ashby",
"jobId": "aa0c645d-7aa0-40b0-bdfa-9b3fb19da404",
"title": "Customer Activation Manager | Stack Accounting",
"department": "Sales",
"team": "Customer Success Management",
"location": "New York, NY (HQ)",
"isRemote": false,
"allLocations": [
"New York, NY (HQ)"
],
"employmentType": "FullTime",
"salaryMin": 121000,
"salaryMax": 184000,
"salaryCurrency": "USD",
"salaryPeriod": "year",
"salarySource": "structured",
"postedAt": "2026-10-30T19:35:16.997Z",
"applyUrl": "https://jobs.ashbyhq.com/ramp/aa0c645d-7aa0-40b0-bdfa-9b3fb19da404/application",
"jobUrl": "https://jobs.ashbyhq.com/ramp/aa0c645d-7aa0-40b0-bdfa-9b3fb19da404",
"status": "open",
"firstSeenAt": "2026-10-30T19:56:03.273Z",
"lastSeenAt": "2026-10-30T19:58:06.840Z",
"scrapedAt": "2026-10-30T19:58:06.840Z"
}

That run's OUTPUT record showed 15 records (5 per company, all open), every company detected from the cache, and nothing failed.

How much does it cost to get job postings from Greenhouse, Lever and Ashby?

You pay for new and open job records saved to your dataset. closed records are free. Jobs removed by your filters are never output or charged.

EventWhen it is chargedPrice
Actor startOnce per run$0.00005
Job returnedEach new or open job saved$0.0015 ($1.50 per 1,000)

Important: a full rerun charges again for jobs that are still open. With Only new since last run on, a run is charged only for new jobs.

Examples:

  • 100 jobs: about $0.15 plus the $0.00005 start fee. 1,000 jobs: about $1.50 plus the start fee.
  • Prefilled run (airbnb, spotify, ramp, Max jobs per company 5, descriptions off): up to 3 × 5 = 15 jobs × $0.0015 = about $0.0225, plus the $0.00005 start fee.
  • Daily schedule for 30 days, illustrative numbers: boards with 300 open jobs, about 10 new jobs a day.
    • New-only off: each run charges about 300 jobs, about $0.45 a day, about $13.50 for 30 days.
    • New-only on: the first run charges all 300 (every job is new on a first run), $0.45; then about 10 new jobs a day, $0.015 a day, about $0.89 for 30 days.
    • Plus $0.00005 per run start ($0.0015 for 30 runs).
  • A run that finds no jobs pays only the start fee.

Platform usage (compute, proxy, storage) is included in these prices. You pay only the event prices listed above, nothing extra on top.

Works on Apify's free plan; you pay a tiny start fee plus only for results returned, meaning new and open jobs saved. closed records are free, and a rerun charges again for jobs that are still open. Charges come out of your Apify account's prepaid usage; on the Free plan, once that monthly usage is used up, runs stop until the next billing cycle.

Spending limit: set a maximum cost per run. When it is reached, the Actor stops saving chargeable jobs (closed records of the company in progress still go out), skips the remaining companies and keeps their state, so nothing is lost on the next run.

Limits

  • The Actor paces itself: Greenhouse 2 requests per second, Lever and Ashby 1 per second. Greenhouse and Ashby publish no limit.
  • Each board is downloaded in full (one request per company, descriptions included) whatever the settings, so Job description: none and Max jobs per company make the output smaller, not the download faster. Only Max jobs per company (fewer records) lowers the cost; descriptions don't change the price, since charges are per record. When Max jobs or the spending limit is reached, the remaining companies are skipped without being fetched. Detecting the ATS of a new bare name takes up to 4 requests.
  • Covers only boards hosted on Greenhouse, Lever and Ashby. Other ATS and company sites are not supported.
  • Up to 500 companies per run; up to 100,000 output records (default 10,000).
  • Salary appears only when the ATS provides it or the job text states it clearly; parsed salaries can be wrong, and ambiguous values are left null.
  • Recruiter and hiring-manager fields are not collected; email addresses are removed from descriptions.

FAQ

Is it legal to use this data? The Actor reads public job boards through each platform's public API without logging in. You are responsible for how you use the data. This is not legal advice.

Does it include recruiter or contact details? No. Recruiter and hiring-manager fields are not collected, and email addresses are removed from description text.

Why is a company not found? The name must match the board name (see How to find a company's board name), and the board must be public on Greenhouse, Lever or Ashby. The log and the OUTPUT record say which platforms were checked.

What if a company switches ATS? Its old jobs are reported once as closed (free), its current jobs come through as new, and a warning is logged. See Track new and closed jobs.

How is salary parsed? Structured ATS salary data is used first. Otherwise the Actor parses ranges, "k" suffixes, single values ("up to" gives only a max, "from" only a min), and hourly, daily, weekly, monthly or yearly figures in USD, EUR, GBP, CAD or AUD, and sets salaryPeriod and salarySource. Without a period word, amounts of 15,000 or more count as yearly. Ambiguous values are left null. See Salary above.

Why is employmentType null for Greenhouse jobs? Greenhouse's public API has no standard employment type field. The Actor uses a company's custom field (e.g. "Employment Type") when there is one.

Why do closed jobs have no description? Descriptions aren't stored between runs, to keep state small. All other fields are the last known values.

Am I charged again for the same job on every run? On a full run, yes: every new or open job is charged each time. Turn on Only new since last run to be charged only for new jobs; closed records are always free. See the daily schedule example under Pricing.

Can I run different filters on the same companies? Yes. Give each task its own State key so each keeps its own history.

Do I pay for Apify compute on top? No. Platform usage is included. You pay only the listed event prices: $0.00005 per run start and $0.0015 per job.

Can I use it via API or integrations? Yes, like any Apify Actor, through the API tab, schedules and integrations.

Something broke or a field is missing? Open an issue in the Issues tab.

Changelog

  • 0.1 (Unreleased): First version. Greenhouse, Lever (US and EU) and Ashby; auto-detect with caching; normalized salary and employment type; new/open/closed tracking with State key and Reset state; per-company OUTPUT summary.

Disclaimer

Greenhouse, Lever and Ashby Jobs Scraper is an unofficial tool built by Bravona. It is not affiliated with, endorsed by, or sponsored by Greenhouse, Lever or Ashby. All product names and trademarks belong to their owners and are used only to describe compatibility. The Actor reads publicly available job postings through public job-board APIs without logging in, and follows a conservative request rate. Data is provided as-is; accuracy and availability depend on the source. You are responsible for using the data in line with each platform's terms and applicable law (including privacy law).