I built this actor; it's a paid tool on Apify with a free trial credit.
Most companies don't host their own job listings. The careers page is a thin wrapper around an applicant tracking system: Greenhouse, Lever, Ashby, Workday, SmartRecruiters, Workable, Recruitee, Personio, Teamtailor or BambooHR. Each of those publishes the board as a public JSON or XML feed, which is great, except that there are ten of them, every one with a different schema, pagination rule and date format, and half the time you don't even know which one a company uses.
I'm an 18-year-old engineering student, and I wrapped all ten behind one input and one output schema in an actor called Job Boards Scraper. This post shows how to call it from Python and Node, what the rows look like, and a small project: a daily "who started hiring for X" feed for sales prospecting.
How it works, briefly
You give it a list of entries. Each one can be an ATS board URL (boards.greenhouse.io/stripe, jobs.lever.co/palantir, jobs.ashbyhq.com/notion, nvidia.wd5.myworkdayjobs.com/NVIDIAExternalCareerSite ...), a company careers page (notion.com/careers, where the embedded board is detected from the page) or just a company website or name (stripe.com, blueground), which is tried on every platform until a board with open jobs answers. Every job is then mapped to one schema: title, company, location (plus city, region, country), remote flag, department, team, employment type, salary range when published, posting date, full description as plain text, and the job and apply URLs. No browser, no scraping of rendered pages, just the platforms' own public endpoints over HTTP, so a 700-job board takes a couple of seconds.
Python example
pip install apify-client
from apify_client import ApifyClient
client = ApifyClient("<YOUR_APIFY_TOKEN>")
run = client.actor("rel8ble/ats-job-boards-scraper").call(run_input={
"boards": [
"https://boards.greenhouse.io/stripe",
"https://jobs.ashbyhq.com/notion",
"https://www.notion.com/careers", # careers page: board detected
"blueground", # bare company name: guessed
],
"titleKeywords": ["engineer", "developer"],
"locationKeywords": ["remote"],
"maxJobsPerBoard": 100,
})
for job in client.dataset(run["defaultDatasetId"]).iterate_items():
print(job["company"], "|", job["title"], "|", job["location"], "|", job["publishedAt"][:10], "|", job["url"])
Output (shortened):
Stripe | Software Engineer, Infrastructure | Remote, US | 2026-09-24 | https://stripe.com/jobs/search?gh_jid=...
Notion | Software Engineer, Developer Platform | San Francisco, California | 2026-08-24 | https://jobs.ashbyhq.com/notion/1fc309c8-...
Node example
npm install apify-client
import { ApifyClient } from 'apify-client';
const client = new ApifyClient({ token: '<YOUR_APIFY_TOKEN>' });
const run = await client.actor('rel8ble/ats-job-boards-scraper').call({
boards: ['https://jobs.lever.co/palantir', 'https://nvidia.wd5.myworkdayjobs.com/NVIDIAExternalCareerSite'],
titleKeywords: ['sales', 'account executive'],
includeDescription: false,
});
const { items } = await client.dataset(run.defaultDatasetId).listItems();
console.log(items.length, 'jobs');
console.log(items[0]);
What a row looks like
{
"id": "1fc309c8-da20-4ff2-84c7-8b863ece2b0a",
"title": "Software Engineer, Developer Platform",
"company": "Notion",
"ats": "ashby",
"url": "https://jobs.ashbyhq.com/notion/1fc309c8-da20-4ff2-84c7-8b863ece2b0a",
"applyUrl": "https://jobs.ashbyhq.com/notion/1fc309c8-da20-4ff2-84c7-8b863ece2b0a/application",
"location": "San Francisco, California",
"locations": ["San Francisco, California", "New York, New York"],
"city": "San Francisco",
"region": "California",
"country": "United States",
"workplaceType": "hybrid",
"isRemote": true,
"department": "Engineering",
"team": "Engineering",
"employmentType": "full-time",
"salaryMin": 213000,
"salaryMax": 320000,
"salaryCurrency": "USD",
"salaryText": "$213,000 - $320,000",
"publishedAt": "2026-08-24T14:44:49.699Z",
"description": "WHO WE ARE\n\nNotion is the collaborative AI workspace ...",
"boardUrl": "https://jobs.ashbyhq.com/notion",
"scrapedAt": "2026-10-08T17:45:45.963Z"
}
Small project: a daily hiring-signal feed
The classic sales play: a company that just opened three SDR roles is about to buy sales tooling; a company hiring a "Head of Data" is about to buy data infrastructure. Here's the whole thing in 40 lines. It keeps a JSON file of job ids it has seen and prints only the new ones.
import json, os
from apify_client import ApifyClient
client = ApifyClient("<YOUR_APIFY_TOKEN>")
SEEN = "seen_jobs.json"
seen = set(json.load(open(SEEN))) if os.path.exists(SEEN) else set()
accounts = ["stripe", "notion", "https://jobs.lever.co/palantir", "bunq.com", "blueground"]
run = client.actor("rel8ble/ats-job-boards-scraper").call(run_input={
"boards": accounts,
"titleKeywords": ["sales development", "sdr", "account executive", "revenue operations"],
"includeDescription": False,
})
new = []
for job in client.dataset(run["defaultDatasetId"]).iterate_items():
key = f'{job["ats"]}:{job["id"]}'
if key in seen:
continue
seen.add(key)
new.append(job)
for job in new:
print(f'NEW {job["company"]:<12} {job["title"]:<50} {job["location"] or "-":<30} {job["url"]}')
json.dump(sorted(seen), open(SEEN, "w"))
print(f"{len(new)} new postings")
Run it from cron or an Apify schedule and pipe the output into Slack, a CRM task or a spreadsheet. The run costs $1 per 1,000 jobs, so a 500-account list with filtering costs a few cents a day.
Things to know
- Ten platforms, not all of them. iCIMS, Taleo, SuccessFactors, Jobvite and fully custom career sites are reported as unresolved in the run summary rather than scraped.
-
Descriptions are free on Greenhouse, Lever, Ashby, Recruitee, Personio and Teamtailor. On SmartRecruiters, Workday, Workable and BambooHR they cost one extra request per job (still one result); set
includeDescription: falsefor a fast count. -
Company names come from the platform where it publishes one; for Lever, Ashby and Workday they're derived from the board slug (
notion-> "Notion"). -
Dates: Workday only says "Posted 3 Days Ago", which is converted to a date; "Posted 30+ Days Ago" stays
null. - Salary is a structured field on Lever, Ashby, Recruitee and Teamtailor; elsewhere it's parsed from the description when a range like "$120,000 - $150,000" is stated.
If something breaks or a field is wrong, the Issues tab on the actor page reaches me directly.
This article was drafted with AI and published by me.
Top comments (0)