Career Site Job Scraper – Greenhouse, Lever, Ashby, Workable avatar

Career Site Job Scraper – Greenhouse, Lever, Ashby, Workable

Pricing

from $4.80 / 1,000 result delivereds

Go to Apify Store
Career Site Job Scraper – Greenhouse, Lever, Ashby, Workable

Career Site Job Scraper – Greenhouse, Lever, Ashby, Workable

Career site jobs scraper that reads the job feed behind an employer's own careers page: Greenhouse, Lever, Ashby, Workable and more. Paste careers pages, board URLs or provider:board pairs and get one row per vacancy - title, department, location, remote, apply link, posting date and pay range.

Pricing

from $4.80 / 1,000 result delivereds

Rating

0.0

(0)

Developer

Locomint

Locomint

Maintained by Community

Actor stats

0

Bookmarked

2

Total users

1

Monthly active users

3 days ago

Last modified

Share

This career site jobs scraper reads the job feed behind an employer's own careers page and hands you one row per open role: title, company, department, location, remote flag, employment type, the full description, the apply link, the posting date and the pay range where the employer publishes one. Six applicant tracking systems are supported - Greenhouse, Lever, Ashby, Workable, Recruitee and Personio - and no job board sits in between, so the jobs are the employer's own words on the day you run it.

Give it whatever you have: a company careers page, a board URL, or a provider:board pair.

What it does

Every line of input is turned into one board to read:

  • A careers page such as https://monzo.com/careers is read once and searched for the applicant tracking system behind it. Verified live: it resolves to greenhouse:monzo.
  • A board URL such as https://jobs.lever.co/matchgroup, https://jobs.ashbyhq.com/posthog, https://job-boards.greenhouse.io/duolingo or https://apply.workable.com/blueground/ is recognised from the URL itself, with no request at all.
  • A provider:board pair such as greenhouse:duolingo is used as given.

Duplicates are removed. An entry that repeats another exactly is read once, and where two different-looking entries turn out to be the same board - ashby:linear and a careers page that resolves to ashby:Linear - every vacancy is still delivered, and charged for, exactly once. Anything that is neither a URL nor a known pair is named in the log and skipped.

The board's feed is then read once over a direct datacenter connection - no browser, no proxy, no login - and parsed into one row per job.

FieldWhat goes in it
job_idThe provider's own id for the opening, as a string. Unique within a board.
titleThe job title as posted.
companyThe employer as the feed names them. Some feeds carry no company name, and those fall back to the board token.
company_domainThe employer's own website, when the row can show it: the careers page you pasted, or an apply link that is not on the platform. null otherwise.
departmentThe team or department the feed names.
locationThe primary location, as the employer wrote it.
locationsEvery location this one opening is posted for, primary first. One Greenhouse posting listing three cities comes back as three entries.
remotetrue when the feed says remote, false when it says on-site or hybrid, null when it does not say. Null is not "no".
employment_typeFull-time, Part-time, Contract, Internship, and so on.
descriptionThe posting body as readable Markdown. null when you turn descriptions off.
apply_urlWhere a candidate applies - the employer's own link.
posted_at, updated_atISO 8601. updated_at exists on Greenhouse and Recruitee; it is null elsewhere.
salary_min, salary_max, salary_currency, salary_intervalThe published pay range, split into columns. Only ever what the employer stated, never estimated.
provider, boardWhich applicant tracking system, and which board token.
source_urlThe public feed the row was read from. Fetch it yourself and you get the same bytes.
fetched_atISO 8601 UTC, when the feed was read.
inputThe line you typed, so a row can be matched back to your sheet.

Filters are free

Filters are applied before a job is handed back, so a job you filtered out never appears in your dataset and is never charged for. A run that reads a 795-job board and keeps four nurses in Lisbon costs four jobs.

FilterWhat it keeps
titleIncludesJobs whose title contains one of these words.
titleExcludesDrops jobs whose title contains one of these words.
locationIncludesJobs matching one of these in any location the job lists.
departmentIncludesJobs in a matching department.
remoteOnlyJobs the employer marks remote, or whose location says remote.
postedWithinDaysJobs posted inside that many days.

Two of them leave out jobs where the feed is silent, and deliberately: a job with no posting date cannot be shown to be from the last seven days, and calling it recent because the field is empty would be inventing the answer. The same goes for a department filter against a job with no department.

A board that gives nothing costs nothing

Only jobs are charged for. A board that no longer exists, one that answered with nothing readable, one that has no vacancies today, a careers page with no feed behind it and a line that turns out to be a board you already named all produce no row at all - each is reported in the run log, with the reason, and costs you nothing.

Which platforms, and what each feed carries

Measured on a real board per provider on 12 September 2026. What a feed omits, the row leaves null rather than guessing.

ProviderBoard token looks likeDepartmentTypePay rangeDescription
greenhousejob-boards.greenhouse.io/monzomonzoyesnonot publishedyes
leverjobs.lever.co/matchgroupmatchgroupyesyesabout half of jobsyes
ashbyjobs.ashbyhq.com/vantavantayesyesabout half of jobsyes
workableapply.workable.com/bluegroundbluegroundyesmostlyrarelyyes
recruiteeacme.recruitee.comacmeyesyessometimesyes
personioacme.jobs.personio.deacmemostlyyesrarelyusually

Greenhouse publishes the department only in the payload that carries descriptions, so turning descriptions off on a Greenhouse board leaves department empty as well - unless you set departmentIncludes, which asks for that payload anyway so the filter has something to read, and then drops the body you said you did not want.

Who it is for

  • Job boards and aggregators that want listings from the source rather than from another board, with the employer's own apply link so candidates land in the employer's pipeline.
  • Recruiters and agencies watching a list of target employers and seeing what opened this week, filtered to the roles they place.
  • Sales and market research teams using hiring as a signal: who is opening five sales roles, which companies publish pay, which are hiring in a market you are entering.
  • Compensation research, using only the ranges employers publish themselves.
  • AI and data teams who want a clean, typed job dataset with a source_url per row that anybody can check.

How to use it

In the Apify Console:

  1. Paste your boards into Careers pages, board URLs or provider:board pairs, one per line, up to 200 per run.
  2. Set Max jobs per board. You are charged per job delivered.
  3. Add filters if you only want some of the jobs. They are free.
  4. Turn Include the job description off for a much smaller dataset when you only need titles, locations and apply links.
  5. Start the run and export the dataset as CSV, Excel or JSON.

From the API, this call starts a run, waits for it and returns the rows:

curl -X POST \
"https://api.apify.com/v2/acts/locomint~career-site-jobs-scraper/run-sync-get-dataset-items?token=YOUR_APIFY_TOKEN" \
-H "Content-Type: application/json" \
-d '{"boards": ["greenhouse:monzo", "https://jobs.ashbyhq.com/vanta"], "maxJobsPerBoard": 50}'

The synchronous endpoint waits up to 300 seconds. For a long list of boards, start the run with POST https://api.apify.com/v2/acts/locomint~career-site-jobs-scraper/runs?token=YOUR_APIFY_TOKEN and read its dataset when the run finishes. With the Python client (pip install apify-client):

from apify_client import ApifyClient
client = ApifyClient("YOUR_APIFY_TOKEN")
run = client.actor("locomint/career-site-jobs-scraper").call(
run_input={
"boards": ["https://monzo.com/careers", "ashby:vanta", "lever:matchgroup"],
"titleIncludes": ["engineer"],
"remoteOnly": True,
"maxJobsPerBoard": 100,
}
)
for row in client.dataset(run.default_dataset_id).iterate_items():
print(row["title"], row["location"], row["apply_url"])

Input example

{
"boards": [
"https://monzo.com/careers",
"https://jobs.ashbyhq.com/vanta",
"lever:matchgroup",
"workable:blueground"
],
"descriptions": true,
"maxJobsPerBoard": 200,
"titleIncludes": ["engineer", "developer"],
"titleExcludes": ["intern"],
"locationIncludes": [],
"departmentIncludes": [],
"remoteOnly": false,
"postedWithinDays": 0,
"concurrency": 4,
"boardTimeout": 60
}
FieldDefaultAllowedWhat it does
boardsrequired1 to 200Careers pages, board URLs or provider:board pairs. Duplicates are removed.
descriptionstruetrue / falseKeep the posting body as Markdown. Off is a much smaller dataset for the same price.
maxJobsPerBoard5001 to 10,000Most jobs delivered from each board.
titleIncludes, titleExcludesemptyany wordsTitle filters, applied before a job is charged.
locationIncludesemptyany wordsMatched against every location a job lists.
departmentIncludesemptyany wordsJobs with no department are left out by this filter.
remoteOnlyfalsetrue / falseRemote jobs only.
postedWithinDays0 (any)0 to 3,650Jobs with no posting date are left out by this filter.
concurrency41 to 10Boards read at the same time.
boardTimeout6010 to 180 secondsA board with no answer by then is left out, and not charged.

Output example

A real row from a run on 12 September 2026. The description is 4,966 characters of Markdown and is cut here.

{
"job_id": "dbd4e02c-0c6c-4031-a658-f57e88ec9943",
"title": "Senior Software Engineer, Developer Experience",
"company": "vanta",
"company_domain": null,
"department": "Software Engineering",
"location": "Remote U.S.",
"locations": ["Remote U.S."],
"remote": true,
"employment_type": "Full-time",
"description": "At Vanta, our mission is to help businesses earn and prove trust ...",
"apply_url": "https://jobs.ashbyhq.com/vanta/dbd4e02c-0c6c-4031-a658-f57e88ec9943/application",
"posted_at": "2026-03-17T18:17:38.753000+00:00",
"updated_at": null,
"salary_min": 224000.0,
"salary_max": 263000.0,
"salary_currency": "USD",
"salary_interval": "year",
"provider": "ashby",
"board": "vanta",
"source_url": "https://api.ashbyhq.com/posting-api/job-board/vanta?includeCompensation=true",
"fetched_at": "2026-09-12T12:57:29+00:00",
"input": "ashby:vanta"
}

A row from a careers page rather than a board token carries the employer's own site:

{
"title": "Senior / Staff Fullstack Engineer",
"company": "Linear",
"company_domain": "linear.app",
"location": "Europe",
"remote": true,
"provider": "ashby",
"board": "Linear",
"input": "https://linear.app/careers"
}

Pricing

EventPrice
Job delivered (one dataset row)$0.006, which is $6.00 per 1,000 jobs
Actor start$0.00005 per GB of run memory, charged once per run

Worked example: 50 employers with about 40 open roles each is 2,000 jobs, so 2,000 x $0.006 = $12.00 plus one start event. Users on paid Apify plans pay less per job.

You pay only these event prices; Apify compute is not billed to you separately. Jobs removed by a filter are not charged. Boards that could not be read, boards with no open jobs and careers pages with no feed behind them produce no rows and cost nothing. If you set a maximum cost per run, no row past that cost is ever delivered or charged for: the run stops there, the log says how many jobs it had found but did not hand over, and the run's status message says the same.

FAQ

Where does the data come from?

From the feed the employer's own careers page uses. Greenhouse, Lever, Ashby, Workable, Recruitee and Personio each publish one for every board, served to anyone with no key and no login - Greenhouse documents that no authentication is required. source_url on every row is the exact address it was read from, so you can check any row yourself.

Why are there no job boards in the list?

Because the big boards block automated requests, and this actor does not work around a block. A site that answers with a challenge or a 403 is backed off from and is never retried through another address. Employers' own feeds are published to be read, which is why they are the whole product here.

My careers page was not recognised. Now what?

Open the page and click through to where the jobs are actually listed, then paste that URL. Pages that build their job list in the browser with JavaScript have nothing for a plain fetch to find, and some company sites refuse datacenter connections outright. The board URL always works, and so does the provider:board pair.

Do I get every job on the board?

Yes, up to maxJobsPerBoard and after your filters. There is no paging to run out of: one board is one feed, read in one request, and the run log says how many open roles that board actually has beside how many it kept for you.

Why is the pay range empty?

Because that employer did not publish one. Greenhouse's board feed carries no pay range at all, so every Greenhouse row is null there; on Lever and Ashby about half the jobs carry one. Nothing is estimated or inferred - an empty range means the employer stated none.

Is there any contact information in the rows?

No. There is no recruiter, hiring manager or contact name in any field, and personal contact points are removed from descriptions before a row is written. The apply link is the employer's own application page.

How do I find new jobs since my last run?

Run on a schedule and compare job_id within a board, which is stable while a posting is open. postedWithinDays narrows a run to recent postings, but only where the feed publishes a date.

Limits

  • One request per board. maxJobsPerBoard caps the rows, not the fetch: every one of these feeds publishes the whole board as a single document, so it is read in full and then cut. No provider is asked to page - Lever accepts a page size and then caps it at 100 whatever you ask for, which would silently cut a 311-role board down to 100.
  • 200 boards per run, 10,000 jobs per board, 60 seconds per board by default (10 to 180).
  • Detection reads one page, and the /careers path when you pass a bare domain. A JavaScript-built careers page, or one that refuses datacenter connections, comes back as no feed found.
  • Feeds differ. Employment type is missing on Greenhouse, updated_at on everything but Greenhouse and Recruitee, and some Personio boards publish no description at all.
  • Board tokens can carry capitals (ashby:Linear), and the token is used as given.
  • Closed postings are not in the feed, so a run shows what is open at that moment. Nothing is cached between runs.
  • Datacenter connections only. A provider that answers with a challenge or a 403 is not retried from another address; that board is reported in the log and costs nothing.

Compliance

Job postings only, from feeds employers publish for their own careers pages. No field holds a person: there is no recruiter, hiring manager or contact name, and personal contact points are removed from descriptions. You are responsible for using the data lawfully.

Questions, bug reports and feature requests go on this actor's Issues tab. Business owners can send data-removal requests to info@locomint.io; that address is for removal requests only. This actor keeps no copy of what it reads between runs, so each run returns what the public feeds show at that moment.

Other Locomint actors