Careers Page Scraper: Greenhouse Jobs, Lever & Workday Jobs
Pricing
from $1.60 / 1,000 job returneds
Careers Page Scraper: Greenhouse Jobs, Lever & Workday Jobs
Careers page scraper and career site jobs API: Greenhouse jobs, Lever, Ashby and Workday jobs, plus Workable, SmartRecruiters, Recruitee, Personio, Teamtailor, softgarden and d.vinci. Give domains or careers URLs. Filter by title, location, remote and posted date before you pay. Alerts for new jobs.
Pricing
from $1.60 / 1,000 job returneds
Rating
0.0
(0)
Developer
Your Evenings
Maintained by CommunityActor stats
0
Bookmarked
2
Total users
1
Monthly active users
3 hours ago
Last modified
Categories
Features
Share
Careers Page Scraper is a career site jobs API that reads open jobs straight from company career sites that run on Greenhouse, Lever, Ashby or Workday, and also Workable, SmartRecruiters, Recruitee, Personio, Teamtailor, softgarden and d.vinci. Give it stripe.com, a careers page URL or a job-board link. It finds the company's applicant tracking system (ATS) and reads every open job from the ATS's public job-board API (for Workday, the career site's own public job search), then returns one normalised row per job: title, department, location, remote flag, employment type, salary (when the employer posts it), job URL, apply URL and dates. Use it as a careers page scraper to export Greenhouse jobs, Workday jobs, Lever jobs and Ashby jobs from a list of companies, as JSON, CSV or Excel.
Supported job boards
One Actor reads the public job boards of exactly these eleven applicant tracking systems:
| ATS | Typical board URL | Notes |
|---|---|---|
| Greenhouse | boards.greenhouse.io/{token}, job-boards.greenhouse.io/{token} | Posted pay ranges included |
| Lever | jobs.lever.co/{token} (and the EU instance jobs.eu.lever.co) | Posted salary range included |
| Ashby | jobs.ashbyhq.com/{token} | Posted compensation included |
| Workable | apply.workable.com/{token} | |
| SmartRecruiters | jobs.smartrecruiters.com/{token} | |
| Recruitee | {token}.recruitee.com | Posted salary included |
| Workday | {tenant}.wd{N}.myworkdayjobs.com/{site} | Department from the job family (when the site has one). Location shows "3 Locations" for multi-location jobs unless a location or remote filter is set (then each job's page is read). Posted date for the last 30 days. No salary fields |
| Personio | {token}.jobs.personio.de (or .com) | Only when the employer has switched on Personio's public XML job feed (see below). Posted salary included |
| Teamtailor | {token}.teamtailor.com, or the employer's own career-site domain (e.g. jobs.tibber.com) | Read from the career site's public RSS job feed (see below). No salary, employment type or update date in the feed |
| d.vinci | {token}.dvinci-hr.com | Read from the portal's public JSON job list (see below). Only when the employer has switched that list on. Description is the ad's short summary, not the full ad. Posted salary included |
| softgarden | {token}.career.softgarden.de | Read from the career site's public job feed (see below). Older *.softgarden.io sites are not supported (free unsupported row). Posted salary included |
Companies whose board isn't found on these systems get a free not_found row.
Why use this career site jobs API
- Filters that actually apply, before you're charged. Filter by title keywords (and excludes), location, remote, department and posted date. Jobs your filters remove are never returned or charged, and each company's row shows how many were filtered out. Matching ignores accents, handles German compound titles ("entwickler" finds "Softwareentwickler") and knows local place names and remote words (München = Munich, Homeoffice = remote).
- Workday career sites too, including boards with more than 2,000 jobs. Give a company name, a domain or a
myworkdayjobs.comlink. It reads the career site's own public job search and works around Workday's 2,000-results-per-search limit; if any jobs can't be read, the company's note says how many and why. - Read live from each job board. Every run reads each company's board directly, so you get the jobs it lists at the moment of the run.
- Every company you enter gets a status, and you can see the board's full size. The
COMPANY-STATUSCSV has one line per input, including repeats and companies not reached, with its status, the board's open-job count, jobs returned, jobs filtered out and a note. Every job row carriescompanyTotalJobs. If a board token you gave has moved to another supported job board, the jobs are read from the new board and the status saysboard_moved. Companies we can't find are free. - Your limits are honoured, with no hidden extra runs or double charges.
maxJobsPerCompanycaps rows per company in normal mode, charging stops at your maximum cost per run, one run never starts other runs, and two inputs that point to the same board are read and charged once (the second gets a freeduplicaterow). - Alerts for new and removed jobs. Turn on
monitorModeand schedule it daily: after a first run that saves (and by default returns) the current jobs, you get only jobs opened or closed since the last run, withfirstSeenAtandlastSeenAt. - No AI-guessed fields. Salary and workplace type are copied from the board's own fields, and salary is filled only when the employer posts it.
How do I scrape Greenhouse, Lever, Ashby or Workday jobs?
Paste this into the JSON view of the Input tab and press Start. It reads two companies' job boards (found from a domain and from a board link) and returns up to 5 jobs with "engineer" in the title from each, plus one status row per company.
{"companies": ["figma.com","https://jobs.ashbyhq.com/ramp"],"titleKeywords": ["engineer"],"maxJobsPerCompany": 5}
Or just press Start with the prefilled input. The form comes filled in with five example companies and maxJobsPerCompany: 20, so a first run returns up to 100 job rows, and the COMPANY-STATUS file lists each company with its board's open-job count. In our test run on 26 October 2026 it returned 100 job rows, which costs about $0.21 at the base price (see Pricing). Leave maxJobsPerCompany at 0 to get every open job: the same five boards had 469 open jobs on 23 October.
For more ready-made inputs, see the example tasks.
Works with: Greenhouse · Lever · Ashby · Workday · Workable · SmartRecruiters · Recruitee · Personio · Teamtailor · d.vinci · softgarden
Runs on
Apify. Use it from Make, Zapier,
n8n or the Apify API.
Use it as a Greenhouse jobs API, a Lever jobs scraper, an Ashby jobs API or a Workday jobs scraper without building separate integrations, or as a careers page jobs feed for a list of target accounts.
Who it's for
- Sales, partnerships and recruiting teams who watch a list of target companies for new openings.
- Job boards and aggregators that want jobs straight from employers' own boards, in one schema.
- Researchers and analysts comparing roles, locations, remote share and posted pay ranges across companies.
Use cases
- Daily hiring alerts for target accounts. Put your account list in
companies, turn onmonitorModeand schedule the task daily. Each run returns only the jobs opened or closed since the last one, so a new "Head of Data" posting at a prospect reaches your CRM or Slack through an Apify integration. - A niche job board fed from employers' own boards. List the companies you cover, filter by
titleKeywords,locationsorremote, and import the rows. Every row links to the employer's own job and apply pages. - Posted salary ranges for one role across companies. Filter by title (for example
"account executive") across a list of companies and comparesalaryMin,salaryMaxandsalaryCurrencywhere the employer posts them.salaryMin,salaryMaxandsalaryCurrencyare copied from the board, never estimated.salaryPeriodis taken from the board when it gives a period; otherwise it is inferred from the amount (under 500: hour, otherwise year) andsalaryPeriodInferredis true.
Also useful for open roles by team and location: group the rows by department and location to see where a company is hiring right now.
What data does this careers page scraper return?
Five rows from real runs, values as the boards published them (four from the prefilled input on 26 October 2026, the NVIDIA row from its Workday board on 31 October 2026):
| Company | ATS | Title | Location | Posted salary | Posted | Board total |
|---|---|---|---|---|---|---|
| Discord | greenhouse | Commercial Policy Lead | San Francisco Bay Area | USD 192,000–216,000 | 2026-09-15 | 46 |
| Figma | greenhouse | Account Executive, Enterprise | San Francisco, CA • New York, NY • United States | USD 165,000–190,000 | 2025-01-28 | 138 |
| Linear | ashby | Senior / Staff Fullstack Engineer | Europe | – | 2021-04-27 | 34 |
| NVIDIA | workday | Senior Data Scientist, Data Quality and AI Evaluation | US, CA, Santa Clara | – | 2026-10-30 | 2684 |
| Spotify | lever | Account Executive - Commercial, Spotify Technology | New York, NY | – | 2026-10-23 | 90 |
One job row from a run on 26 October 2026 (build 0.1.15), as JSON. Some fields are left out to keep it short; the full list is under Output.
{"recordType": "job","status": "ok","company": "Discord","companyDomain": "discord.com","ats": "greenhouse","boardToken": "discord","detectionMethod": "token-guess","jobId": "8806482002","title": "Commercial Policy Lead","department": "Policy","location": "San Francisco Bay Area","isRemote": false,"salaryMin": 192000,"salaryMax": 216000,"salaryCurrency": "USD","salaryPeriod": "year","url": "https://job-boards.greenhouse.io/discord/jobs/8806482002","applyUrl": "https://job-boards.greenhouse.io/discord/jobs/8806482002","postedAt": "2026-09-15T16:52:11.000Z","updatedAt": "2026-10-21T00:27:20.000Z","companyTotalJobs": 46,"input": "discord.com"}
Input examples: Greenhouse jobs, Workday jobs and more
Paste any of these into the JSON view of the Input tab. They are based on the ready-made example tasks below.
Export Greenhouse jobs from four companies
{"companies": ["greenhouse:stripe", "greenhouse:airbnb", "greenhouse:databricks", "greenhouse:duolingo"],"maxJobsPerCompany": 100}
Set maxJobsPerCompany to 0 to get every open job on each board.
Export Workday jobs (career-site link or token)
A myworkdayjobs.com career-site link or a workday: token. A host is skipped, with a free error row and no charge, if its robots.txt can't be read cleanly.
{"companies": ["https://workday.wd5.myworkdayjobs.com/Workday", "workday:adobe.wd5/external_experienced", "workday:workday"],"maxJobsPerCompany": 100}
Lever and Ashby jobs in the same run
Mix board URLs and ats:token values for any of the supported systems in one list:
{"companies": ["https://jobs.lever.co/spotify", "ashby:ramp"],"maxJobsPerCompany": 50}
Daily alerts for new and removed jobs at target accounts
Schedule this one daily. The first run returns the current jobs; later runs return only jobs opened or closed since the run before.
{"companies": ["asana.com", "twilio.com", "mongodb.com", "samsara.com", "mercury.com", "linear.app"],"monitorMode": true,"emitBaselineOnFirstRun": true,"stateStoreName": "hiring-signals-watchlist"}
Example tasks (ready-made inputs)
Open one, click Try for free, then swap in your own companies:
- Track open jobs at top AI startups
- Export Greenhouse job boards to CSV or JSON
- Collect posted salary ranges from tech job boards
- Daily hiring alerts for your target accounts
- Find open jobs at UK and European fintechs
- Export Workday jobs to CSV or JSON
- Greenhouse jobs posted in the last 24 hours: daily feed
- Which ATS does a company use? Greenhouse, Lever, Ashby checker
How it finds each company's jobs
For every line of input the Actor:
- Uses the board directly if you give an explicit token (
greenhouse:stripe) or a job-board URL (https://jobs.lever.co/spotify). If that board no longer exists or lists no jobs, the same token is tried on the other supported job boards (see Moved boards). - Otherwise derives likely board tokens from the domain (for example
figma.com→figma) and checks them against each ATS's public API (d.vinci and softgarden are not guessed from names; they are found from a board URL, a token, or a link on the company's website). Boards whose company name or job links clearly belong to a different organisation are skipped. - If that fails, reads up to 4 pages of the company's website (your careers URL,
/careers,/jobsand the homepage) and looks for links to a supported job board, including Workday career sites, boards embedded with an iframe or script (such as Greenhouse'sembed/job_board?for=code),job-boards.greenhouse.ioand Lever EU (jobs.eu.lever.co) links, and the public job APIs an embedded widget calls. Pages that the website'srobots.txtdisallows, or all pages if thatrobots.txtcan't be read cleanly, are not read. - If that fails too, checks whether Workday has a tenant with that name: each Workday host's public
robots.txtsays whether the tenant exists and lists its career sites. The main external site (the one with the most jobs, skipping intern, campus and internal sites) is used. - If nothing is found, returns one row with
status: "not_found". Not-found companies are free.
Each row says how its board was found (detectionMethod) and how sure the Actor is that the board is the right company (detectionConfidence):
- high: you gave the token or board URL; the company's website links to the board; the board's job links are on the company's own domain; or the board's company name matches the domain exactly.
- medium: a token guessed from the domain answered on Lever, Ashby or Workday. These boards publish no company name to cross-check, so verify them once.
- low: the board's company name only partly matches the domain. Check these before relying on them. For the most reliable runs, pass the board token or board URL once you know it; the
boardTokenfield in the output gives you the value to reuse.
Input
| Field | What it does | Default |
|---|---|---|
companies | Domains, careers URLs, job-board URLs or ats:token values, one per line | 5 example companies |
includeDescription | Adds a plain-text descriptionText field (emails and phone numbers removed) | off |
maxJobsPerCompany | Caps rows per company (0 = all jobs), applied after the filters below. The prefilled form uses 20 | 0 |
titleKeywords | Keep jobs whose title contains any of these words or phrases | none |
excludeTitleKeywords | Drop jobs whose title contains any of these words or phrases | none |
locations | Keep jobs whose location contains any of these places | none |
remoteOnly | Keep only remote jobs | off |
departments | Keep jobs whose department or team contains any of these texts | none |
postedWithinDays | Keep jobs posted within the last N days (0 = any date) | 0 |
keepJobsWithoutPostedDate | With postedWithinDays set, keep jobs whose board gives no posted date | on |
monitorMode | Return only new and removed jobs since the previous run | off |
emitBaselineOnFirstRun | In monitor mode, return all current jobs on the first run | on |
stateStoreName | Named key-value store that remembers seen jobs | careers-jobs-api-state |
persistState | Keep firstSeenAt accurate across normal runs | on |
scanCareersPage | Look for job-board links on the company website if guessing fails | on |
maxConcurrency | Max parallel HTTP requests | 5 |
requestTimeoutSecs | Per-request timeout; failed requests are retried 3 times with backoff | 15 |
Example input:
{"companies": ["figma.com","https://www.notion.so/careers","https://jobs.lever.co/spotify","greenhouse:stripe","ashby:ramp"],"includeDescription": false,"monitorMode": false}
Accepted explicit prefixes: greenhouse:, lever:, lever-eu:, ashby:, workable:, smartrecruiters:, recruitee:, personio:, workday:, teamtailor:, dvinci:, softgarden:.
Job filters (applied before you're charged)
The Actor reads each board in full, applies your filters, and only then pushes and charges job rows. Jobs a filter removes are never returned or charged. All filters are optional and combine with AND; values inside one filter combine with OR.
{"companies": ["stripe.com", "figma.com"],"titleKeywords": ["engineer", "engineering"],"excludeTitleKeywords": ["intern"],"locations": ["London", "Remote"],"postedWithinDays": 7}
How each filter matches:
titleKeywords/excludeTitleKeywords: whole words or phrases in the job title, ignoring case and accents (developpeurmatches "Développeur").engineermatches "Software Engineer" but not "Engineering Manager", so list both forms if you want both. A single word of 5 or more letters also matches at the end of a compound word and with the German endings -in/-innen:entwicklermatches "Softwareentwickler (m/w/d)" and "Entwicklerin", andberatermatches "Steuerberater". It doesn't match at the start of a word, sojavadoesn't match "JavaScript". There's no translation:developerdoesn't match "Entwickler".locations: whole words in the board's location text, ignoring case and accents (Zurichmatches "Zürich"). About 50 groups of place names are matched together: local and English names (München/Munich, Köln/Cologne, Wien/Vienna, Warszawa/Warsaw, Lisboa/Lisbon, Deutschland/Germany, Schweiz/Suisse/Switzerland and others), short forms (NYC/New York, UK/United Kingdom/England/Scotland/Wales/Northern Ireland, USA/US/United States, UAE, SF/San Francisco) and old names (Bangalore/Bengaluru, Bombay/Mumbai). A city isn't matched to its country, soUnited Kingdomdoes not match a job listed only as "London".Remotealso matches jobs the board flags as remote.remoteOnly: keeps jobs withisRemote: trueorworkplaceType: "remote". Hybrid jobs are not included. Remote words in other languages count when they're in the location or the board's workplace field: Homeoffice, mobiles Arbeiten, ortsunabhängig, télétravail, teletrabajo, remoto, teletrabalho, thuiswerk, telelavoro, smart working, praca zdalna, på distans, etätyö and similar. Text that also says hybrid stays hybrid.departments: case- and accent-insensitive partial match on the board's department or team name (engmatches "Engineering"). Department names are whatever each employer uses.postedWithinDays: compares the board'spostedAtwith the run time, so a scheduled run always looks at the latest days.
Missing fields. If a filter needs a field the board doesn't give for a job: with locations set, jobs with no location are dropped; with departments set, jobs with no department or team are dropped; with postedWithinDays set, jobs with no posted date are kept and flagged postedAtUnknown: true, unless you turn off keepJobsWithoutPostedDate.
What you see. Every job row has companyTotalJobs (open jobs on the board before filters and limits). When any filter is set, each company also gets one free company row with companyTotalJobs, filteredOut and jobsReturned. If nothing matches, the company row has status: "no_matching_jobs" and a note with the board's job count. SUMMARY shows the filters used and the total filtered out.
In monitor mode the filters apply to new and removed jobs: only matching changes are returned and charged. Changes your filters exclude are recorded, so they aren't reported later either.
Output
One row per job. Example (Greenhouse):
{"recordType": "job","status": "ok","changeType": null,"company": "Figma","companyDomain": "figma.com","ats": "greenhouse","boardToken": "figma","detectionMethod": "token-guess","detectionConfidence": "high","jobId": "5426468004","title": "Account Executive, Enterprise","department": "Sales","team": null,"location": "San Francisco, CA • New York, NY • United States","isRemote": false,"workplaceType": null,"employmentType": null,"salaryMin": 165000,"salaryMax": 190000,"salaryCurrency": "USD","salaryPeriod": "year","salaryText": "Annual Base Salary Range: USD 165,000 - USD 190,000","url": "https://boards.greenhouse.io/figma/jobs/5426468004?gh_jid=5426468004","applyUrl": "https://boards.greenhouse.io/figma/jobs/5426468004?gh_jid=5426468004","postedAt": "2025-01-28T23:57:29.000Z","postedAtUnknown": false,"postedAtBasis": "board-date","companyTotalJobs": 139,"updatedAt": "2026-07-22T09:37:08.000Z","firstSeenAt": "2026-10-21T17:04:19.642Z","lastSeenAt": "2026-10-21T17:04:19.642Z","input": "figma.com","note": null}
Field notes:
- Salary fields are filled only when the employer publishes pay on its board. Amounts and currency are copied, never estimated.
salaryPeriodis taken from the board when it gives a period (including Lever'sbi-week-salary, which maps tobiweekly). If the board gives no period,salaryPeriodis inferred from the amount (under 500:hour, otherwiseyear) andsalaryPeriodInferredis true. Where an employer posts several ranges (for example by pay zone),salaryMinandsalaryMaxspan them andsalaryTextlists each one. isRemoteistruewhen the board marks the job remote, a listed location says Remote, or the location uses a local remote word such as Homeoffice or télétravail (not when it also says hybrid).workplaceTypeisremote,hybridoronsitewhen the board says so.- Dates are ISO 8601 in UTC. Not every ATS publishes an update date, so
updatedAtcan be null. postedAtUnknownistruewhen the board gives no posted date for the job.postedAtis the board's own posting date (for Greenhousefirst_published, neverupdated_at, which Greenhouse changes across whole boards at once).postedAtBasissays where it came from:board-date,personio-created-at(the job's creation date in Personio),teamtailor-published(the publication date in the Teamtailor feed),dvinci-publication-start(the start date of the d.vinci job ad),dvinci-opening-created(d.vinci ads without a start date: the date the opening was created, which can be much earlier),softgarden-date-posted(the posting date in the softgarden feed), orapproximate-from-listing-textfor Workday jobs whose listing only says "Posted 3 Days Ago" (converted to a UTC date, accurate to about a day;postedWithinDaysallows one extra day for these). Workday jobs listed as "Posted 30+ Days Ago" have nopostedAtunless their job page is read.companyTotalJobsis the number of open jobs the company's board lists at run time, before your filters andmaxJobsPerCompany. It is on every job row and on company rows for boards that were read (null fornot_found,duplicateanderror). Compare it with the rows you got to check nothing was left out. For SmartRecruiters it is the board's owntotalFoundcount; the Actor reads at most 10,000 jobs from one SmartRecruiters board.- Company rows. Inputs that don't produce jobs return one row with
recordType: "company"and astatusofnot_found,no_open_jobs,no_matching_jobs(filters set, nothing matched),no_change,partial,empty_unconfirmedorfetch_failed(monitor mode, see Monitor mode),baseline_saved,duplicate,feed_disabled(Personio or d.vinci career page found but its public job feed is switched off),unsupported(an older softgarden.io career site, which has no public feed) orerror, plus anoteexplaining why. A board token you gave that was found on a different job board gets aboard_movedrow withfromAts,toAtsandfromBoardToken(see Moved boards). When job filters are set, companies with matching jobs also get a company row (statusok,changesorpartial_budget) withcompanyTotalJobs,filteredOutandjobsReturned. Company rows are never charged. - No personal contact data. The Actor never outputs recruiter or hiring-manager names or contact details, and removes email addresses and phone numbers from description text.
The Output tab has four views: Jobs, Salaries, Monitor changes and Detection. A run summary (status per company, detected board, timings) is saved as SUMMARY in the run's key-value store.
Per-company status: COMPANY-STATUS CSV
Every run also saves COMPANY-STATUS, a CSV in the run's key-value store (linked from the Output tab and from the run's final status message). It has one line per company you entered, in your input order:
| Column | Meaning |
|---|---|
input | The line you entered |
company, ats, boardToken | The board it resolved to (empty if not found) |
status | ok, board_moved, partial_budget, no_open_jobs, no_matching_jobs, no_change, changes, partial, empty_unconfirmed, fetch_failed, baseline_saved, not_found, feed_disabled, unsupported, duplicate, error, skipped_budget or skipped_limit |
totalJobs | Open jobs on the board before filters and limits (empty when the board wasn't read) |
returned | Job rows returned for this input |
filteredOut | Jobs (or monitor changes) your filters excluded |
note | Plain-English explanation |
fromAts, toAts | For board_moved lines: the job board you gave and the one the jobs were read from (empty otherwise) |
Exact repeats in your input are read once and listed as duplicate. Companies the run didn't reach because it hit your maximum cost are listed as skipped_budget. A run reads at most 5,000 companies; any beyond that are listed as skipped_limit, so split bigger lists across runs. None of these are charged.
Workday career sites
Give a company name (Mastercard), a domain (nvidia.com), a career-site link (https://workday.wd5.myworkdayjobs.com/Workday) or an explicit token (workday:adobe.wd5/external_experienced, or just workday:intel to find the host and site). The boardToken in the output (tenant.wdN/site) is the exact value to reuse.
Jobs are read from the public job search the career site itself uses (/wday/cxs/{tenant}/{site}/jobs), with no login and no candidate data. Requests go one at a time per Workday site with a short gap, back off when the site answers HTTP 429 (at most 3 retries per request, waiting at most 30 seconds each), and respect the site's robots.txt (sites it disallows are skipped and returned as not_found; sites whose robots.txt can't be read are skipped as described in the FAQ). Career sites on wd{N}.myworkdaysite.com currently answer robots.txt with an error (HTTP 422), so they are skipped. A board is not retried endlessly: if the site answers HTTP 403, still answers HTTP 429 after those retries, or answers with a page that isn't a job list (for example a redirect to a maintenance page), reading that board stops and the company gets an error row whose note starts "Board read stopped:" and says which of the three happened (in monitor mode, a stop partway through reading the board gives a fetch_failed row instead, with the saved list unchanged). These rows are not charged. If job pages (read for all locations or descriptions) answer 403 or keep answering 429, the remaining job pages on that site are not requested and the company note says how many could not be read.
Boards with more than 2,000 jobs. One Workday search returns at most 2,000 results, 20 at a time; later pages repeat the first page. The Actor splits the search by job family (and, when needed, by other filters the site offers, such as time type or location) so each part stays under 2,000, then merges the results by job ID. The board's true total comes from Workday's own filter counts, and companyTotalJobs reports it. In our tests (22 Oct 2026) all of NVIDIA's roughly 2,700 open jobs were read (about 150 requests, 2.5 minutes). If a board changes while it's being read, a second pass picks up jobs that moved between pages. If some jobs still can't be read, or the run is about to time out, the company row and COMPANY-STATUS note say how many of the total were read and why.
What Workday rows contain. department comes from the job family, when the site has a job-family filter (a filter for remote type, time type or location is never used as the department). For a job listed in several places, location shows the count (for example "3 Locations") unless you set a locations filter or remoteOnly, in which case that job's own page is read to get every location. postedAt is filled for jobs posted in the last 30 days (Workday's listing shows "Posted 30+ Days Ago" for older ones). With postedWithinDays above 30, older jobs' pages are read for the exact date. includeDescription reads each job's page for the full text, which is slower on big boards. Workday shows no salary fields in its search, so salary is empty.
Personio, Teamtailor, softgarden and d.vinci jobs
Personio career pages
Give a company name (thermondo), a domain (thermondo.de), a career-page link (https://mway.jobs.personio.de) or an explicit token (personio:mway).
Jobs are read from the employer's public Personio XML job feed (https://{token}.jobs.personio.de/xml, or .jobs.personio.com), the feed Personio offers for job boards and websites. No login, no candidate data, and no scraping of the career page's HTML. Employers can switch this feed off in Personio. When it is off, the jobs can't be read:
- If you gave a Personio token or link, or the company's website links to its Personio page, you get one free row with
status: "feed_disabled"and a note saying the career page exists but its public feed is switched off. - If the Personio page was only a guess from a name or domain, you get a free
not_foundrow whose note mentions the Personio page with the feed switched off. - Unknown Personio names return a free
not_foundrow.
Requests to Personio go one at a time with a short gap, and each career site's robots.txt is respected.
What Personio rows contain. department and team (Personio's recruiting category) as published; location is the main office plus any additional offices, separated by "; "; employmentType combines Personio's type and schedule (for example Full-time, Part-time, Internship, Working student, Trainee, Contract); salary only when the employer adds it to the feed; url and applyUrl are the job's public Personio page. postedAt is the job's creation date in Personio (postedAtBasis: "personio-created-at"), which can be earlier than the date it was first published. includeDescription adds the description sections from the feed.
Teamtailor career sites
Give a company name (Oatly), a domain (tibber.com), a career-site link (https://polestar.teamtailor.com/jobs) or an explicit token: teamtailor:polestar, or the career site's own host for sites on the employer's domain (teamtailor:jobs.tibber.com).
Jobs are read from the career site's public RSS job feed ({career site}/jobs.rss), which Teamtailor documents for sharing a career site's public jobs with other systems. No login, no API key and no scraping of the career pages. The feed is paged 100 jobs at a time, so career sites with up to 5,000 open jobs are read in full. Requests go one at a time per career site with a short gap, and each site's robots.txt is respected. A career site on the employer's own domain is found when the company's careers page is (or redirects to) a Teamtailor-built site.
What Teamtailor rows contain. title; department; team (Teamtailor's division, often empty); location as city and country for each listed location, separated by "; "; workplaceType from Teamtailor's remote status (fully remote = remote, hybrid = hybrid, none = onsite; "temporarily remote" is left empty); url and applyUrl are the job's page on the career site; postedAt is the publication date in the feed (postedAtBasis: "teamtailor-published"); includeDescription adds the job description. company is the career site's name as the feed gives it (for example "Oatly AB").
What Teamtailor's feed doesn't have: salary, employment type (full-time, part-time and so on), an update date (updatedAt is empty) and a separate apply link. Those fields are always empty for Teamtailor jobs. Office street addresses in the feed are not copied.
d.vinci job portals
Give a portal link (https://ofa.dvinci-hr.com/de/jobs), an explicit token (dvinci:ofa), or a company website that links to its d.vinci portal. Company names alone are not guessed.
Jobs are read from the portal's public JSON job list (https://{token}.dvinci-hr.com/jobPublication/list.json), the public interface d.vinci portals offer for showing an employer's job ads on its own website. No login and no scraping of the portal's pages. Requests go one at a time per portal with a short gap, and each portal's robots.txt is respected. Employers can leave this interface switched off. Then you get one free row with status: "feed_disabled" saying so. A name with no d.vinci portal gets a free not_found row.
What d.vinci rows contain. title (the ad's position title); department; team (the opening's categories, separated by "; "); location as city and country; employmentType from the working-time settings (for example Full-time, Part-time); salary when the employer adds it (salaryText is the employer's own wording, for example "1.200,00 bis 1.390,00 EUR brutto pro Monat"); url is the public ad and applyUrl the application form. postedAt is the ad's start date (postedAtBasis: "dvinci-publication-start") or, when the ad has none, the date the opening was created (dvinci-opening-created). includeDescription adds the ad's short summary text from the list (many ads have none); the full ad page is not read. One opening published as two ads (for example in two languages) gives two rows. The list's contact-person fields are never requested or output.
softgarden career sites
Give a career-site link (https://stellenportal-losteria.career.softgarden.de/), an explicit token (softgarden:stellenportal-losteria), or a company website that links to its softgarden career site. Company names alone are not guessed.
Jobs are read from the career site's public job feed (https://{token}.career.softgarden.de/jobs.feed.json), the schema.org job feed softgarden publishes on its newer career sites for search engines and AI assistants. No login and no scraping of the career pages. Requests go one at a time per site with a short gap, and each site's robots.txt is respected. If the feed lists fewer jobs than its own count, the company row says so and monitor mode treats the read as partial.
Older softgarden sites on *.softgarden.io are not supported: they have no public job feed. A softgarden.io link or token (or a company website that links only to one) gets one free row with status: "unsupported" and a note saying so.
What softgarden rows contain. title; location as city and country for each listed location, separated by "; " (street addresses in the feed are not copied); workplaceType: "remote" for jobs marked as telecommute; employmentType; salary when the employer adds it; url and applyUrl are the job's page on the career site; postedAt is the feed's posting date (postedAtBasis: "softgarden-date-posted") and updatedAt its last-modified date; includeDescription adds the job description. company is the employer name the feed gives; for groups that post each branch as its own employer (for example "Brand - Berlin", "Brand - Köln") it is the shared brand name. Not in the feed: department and team, which are always empty for softgarden jobs.
Moved boards
Companies sometimes switch job boards (for example from Lever to Ashby) and keep the same name in the URL. When you give an explicit token (lever:notion) or a board URL (https://jobs.lever.co/notion) and that board answers "not found" or lists no open jobs, the Actor tries the same token on the other supported public job-board feeds: Greenhouse, Lever (US and EU), Ashby, Workable, SmartRecruiters, Recruitee, Personio and Teamtailor.
- Found with open jobs: the jobs are read from the new board, with your filters applied before charging and charged as usual. The company gets one free row with
status: "board_moved",fromAts(the board you gave),toAts(where the jobs came from),fromBoardTokenand a note. Job rows show the new board inatsanddetectionMethod: "moved-board". Use the newatsandboardTokenin future runs. - Not found anywhere: you get the same free
not_foundrow as before, and the note lists the job boards that were checked. - The old board still exists but is empty, and nothing is found elsewhere: you get
no_open_jobsas before. - A board under the same token that belongs to a different company (its company name or job links don't match) is not used. On Workable, SmartRecruiters, Recruitee, Personio and Teamtailor, where short names are reused more often, a board is used only when its company name matches the token.
- Workday is not searched, because a token alone doesn't say which Workday host a company would be on. A Workday token that's gone (
workday:tenant.wd5/site) is tried on the other boards using the tenant name. - This applies only to tokens and board URLs you give. Domains and company names are already looked up on every supported job board.
- The lookup makes a request or two to each job board's public API, at the normal rate, and isn't charged.
- In monitor mode, the first run after a move saves the new board as the baseline (job IDs differ between job boards, so the old board's history can't be carried over).
Can I get alerts for new jobs?
Turn on monitorMode and schedule the Actor (daily or weekly) with the same input:
- The first run for a company saves its current jobs as a baseline and, by default, returns them all with
changeType: "new". - Later runs return only jobs that appeared (
changeType: "new") or disappeared (changeType: "removed") since the previous run. Removed rows keep the job's title, location, URL and first/last-seen dates. - A company with no changes returns one
no_changerow, charged at the low monitor-check price.
When a board can't be read in full. The saved job list is only replaced after a complete read of the board:
- Read failed (for example the job board returned an error): the company gets a free
fetch_failedrow, the saved list is left as it was, and no jobs are reported as removed. The board is checked again on the next run. - Only part of the board was read (a Workday or Teamtailor board cut short by the run's time limit, a Workday board where some jobs can't be reached through its public search, or a SmartRecruiters board that stops paging early): new jobs seen in the part that was read are returned as usual, but no jobs are reported as removed, and saved jobs that weren't seen are kept. The company row has status
partialand a note; if there are no new jobs, that row is free. On boards that are always read only in part, closed jobs are therefore not reported as removed. - A board suddenly lists no jobs after the last run saw some: this is treated as unconfirmed. The company gets a free
empty_unconfirmedrow, nothing is reported as removed and the saved list is kept. If the next run also reads 0 jobs, the saved jobs are then returned asremoved.
Job rows are only charged when they are delivered, so none of these cases charges for rows that weren't returned.
The job lists are kept in a named key-value store in your account (stateStoreName). Use a different store name for each separate watch list. If a run stops early because it reached your maximum charge, the changes it didn't deliver are reported on the next run, not lost.
How much does it cost?
This Actor uses pay-per-event pricing. You pay for results, not for compute:
| Event | When it is charged | Price |
|---|---|---|
| Actor start | Once per run (per GB of memory; the default 256 MB run counts as one start) | $0.00005 |
| Job | Each job row returned (in monitor mode, each new or removed job). Jobs removed by your filters are not charged | $0.002 |
| Company resolved | Each company whose board is found and read (normal mode, and the first monitor run), including when no jobs match your filters | $0.002 |
| Monitor check, no change | Monitor mode: a company with no new or removed jobs | $0.0005 |
Companies that can't be found are never charged. Higher Apify plans get tier discounts (Silver −10%, Gold −20%). Prices on the Actor's Pricing tab are authoritative.
Examples at the base price:
- One run over 20 companies with 100 open jobs each: $0.00005 + 20 × $0.002 + 2,000 × $0.002 = about $4.04.
- A daily monitor run over the same 20 companies, where 3 companies changed by 2 jobs each: $0.00005 + 17 × $0.0005 + 6 × $0.002 = about $0.021.
- The first example with filters that keep 10 jobs per company: $0.00005 + 20 × $0.002 + 200 × $0.002 = about $0.44.
The Actor respects the maximum cost per run you set: it stops adding rows when the limit is reached and says so in the run's status message.
Use the career site jobs API from code, spreadsheets or an AI agent
-
Schedule it: Console > Schedules, daily, with
monitorModeon, to get a new-jobs feed. -
Google Sheets, Make, Zapier, n8n, Slack: use the Actor's Integrations tab to send each run's dataset where you need it.
-
API (Python):
from apify_client import ApifyClientclient = ApifyClient("<YOUR_APIFY_TOKEN>")run = client.actor("yourevenings/careers-jobs-api").call(run_input={"companies": ["stripe.com", "ashby:ramp"]})for item in client.dataset(run["defaultDatasetId"]).iterate_items():print(item["company"], item.get("title"), item.get("location"), item.get("salaryMin")) -
AI agents: the Actor can be run through the Apify MCP server, so Claude, ChatGPT or Cursor agents can call it with their own input.
AI agents can pay for runs themselves
AI agents can run this Actor and pay with stablecoins such as USDC through the x402 or MPP protocols, with no Apify account needed. The agent buys a prepaid, spend-capped Apify API token from Apify's Agent General Interface (AGI) and then runs the Actor with it as normal. Prepaid tokens only run Actors that use limited permissions, and this Actor does. Agentic payments are an experimental Apify feature, and the terms (minimum amount, expiry, usage limits) are set by Apify. See Apify's docs: Let your AI agent pay for Actor runs, x402 and MPP.
Tips
- Run once with domains, then switch to the
ats:tokenvalues from theboardTokencolumn. Lookups are then exact and faster. - Large boards can have 500–1,000+ jobs. Use the job filters to get (and pay for) only the jobs you need, and
maxJobsPerCompanywhile you test. - Turn on
includeDescriptiononly if you need the full text. It makes the dataset much larger.
FAQ
How do I scrape a careers page?
Give a company website, a job-board URL, or an ats:token value in companies. The Actor uses the company's public job board when it can find one of the eleven supported systems. If it can't, you get a free not_found row. The quick-start example and the input examples above are ready to paste.
How do I export Workday jobs?
Give a company name, a domain, a myworkdayjobs.com link or a workday: token (see the Workday input example above). The Actor reads the career site's own public job search, splits searches on boards with more than 2,000 jobs, and returns one row per job. Use the dataset's export button for CSV, JSON or Excel. Workday shows no salary in its search, so salary fields are empty for Workday rows. A Workday host is skipped if its robots.txt can't be read cleanly.
Can I get Lever jobs and Ashby jobs in the same run as Greenhouse jobs?
Yes. Mix domains, board URLs and ats:token values for any of the eleven systems in one companies list. Every row has the same fields, and ats says which system it came from. See the Lever and Ashby input example above.
Do I need a Greenhouse, Lever or Ashby API key or login?
No. The Actor reads the public job-board endpoints the ATS vendors publish for careers sites and job boards. No login, no browser automation and no proxies are used.
Can I watch a list of target companies for new roles?
Yes. Put the companies in companies, set titleKeywords to the roles you care about (for example marketing, brand, content) and either postedWithinDays: 7 or monitorMode (returns only jobs that are new or removed since the last run), then schedule it weekly.
Is there a free Greenhouse jobs API?
Greenhouse, Lever and Ashby publish public job-board endpoints, but each has its own format and you have to know every company's board token. This Actor tries to find the token from the domain (or takes the token or board URL if you know it) and returns one schema for all eleven systems. You pay per job returned and per company resolved; companies that can't be found are free.
How is salaryPeriod set?
It is taken from the board when the employer gives a period. Lever's bi-week-salary interval is stored as biweekly, not week. If the board gives no period, salaryPeriod is inferred from the amount (under 500: hour, otherwise year) and salaryPeriodInferred is true. Amounts and currency are still copied from the board, never estimated. Workday and Teamtailor rows have no salary.
Is this an official Greenhouse, Lever, Ashby or Workday product?
No. It's an independent tool. It isn't affiliated with or endorsed by Greenhouse, Lever, Ashby, Workable, SmartRecruiters, Recruitee, Workday, Personio, Teamtailor, d.vinci or softgarden. The names are used only to describe which public job boards it reads.
Where does the data come from?
From the public job-board endpoints the ATS vendors publish so employers' openings can be shown on careers sites and job boards, and for Workday from the public job search each career site uses. No login, no browser automation and no proxies are used. Requests are rate-limited, and robots.txt is respected on Workday, Personio, Teamtailor, d.vinci and softgarden sites and on the company's own website when its careers pages are scanned for a board link.
We only read a site if its robots.txt can be read cleanly: no robots.txt (HTTP 404) means no rules, and any other error or a timeout means that board is skipped with an error row and not charged. On a company's own website, careers pages that its robots.txt disallows, or every page if that robots.txt can't be read cleanly, are not read; the Actor still tries board names guessed from the company name, and if no board is found the company's not_found row says the scan was skipped.
Why was my company not found?
It may use a system other than the eleven listed above, its Workday career site may use a different tenant name from its domain, or its board token may not match its domain. If you know the board URL, pass it directly (for example https://jobs.ashbyhq.com/yourcompany or https://yourcompany.wd5.myworkdayjobs.com/External).
Could a guessed board belong to a different company with the same name?
It can happen with very generic names. The Actor checks company names and job-link domains to reduce this, and every row shows detectionMethod and boardToken so you can verify. Explicit tokens and board URLs avoid the issue entirely.
How do I know I got every job?
Each job row has companyTotalJobs, the board's open-job count before your filters and limits, and COMPANY-STATUS shows per company how many jobs the board had, how many were returned and how many your filters removed. In normal mode, if returned plus filteredOut is lower than totalJobs, the note says why: maxJobsPerCompany, your maximum cost per run, or (on Workday) jobs its public search couldn't reach in time. (In monitor mode returned counts new and removed jobs, so it isn't compared with the board total.)
How fresh is the data?
Every run reads the boards live, so jobs are current as of the run.
Is it legal to use this data?
The Actor reads only job postings that employers publish on public job boards, and it drops personal contact details. You're responsible for using the data in line with the laws that apply to you (for example GDPR) and the terms of the job boards and sites you use it with.
Can I get only jobs posted in the last 24 hours?
Yes. Set postedWithinDays to 1 and turn off keepJobsWithoutPostedDate, then schedule a daily run (see the last-24-hours example task). The window counts back from the run time. Workday boards often show only "Posted 3 Days Ago", so those jobs get one extra day of leeway. Jobs outside the window aren't returned or charged.
Am I charged for jobs my filters remove?
No. Filters run before any job row is pushed, so filtered-out jobs are never returned or charged. Each company whose board is found is still charged the company-resolved event once (also when no jobs match), plus the per-run start fee. Companies that can't be found are free.
Can a run cost more than the maximum I set?
No. Set Maximum cost per run in the run options: the Actor stops adding rows when it's reached, says so in the run's status message, and lists the companies it didn't reach as skipped_budget in COMPANY-STATUS (free). One run never starts other runs. To test cheaply first, set maxJobsPerCompany to a small number.
Which ATSs are supported?
Exactly eleven: Greenhouse, Lever (including its EU instance), Ashby, Workable, SmartRecruiters, Recruitee, Workday, Personio (only for employers whose public Personio job feed is switched on), Teamtailor (from the career site's public RSS feed, which has no salary or employment type), d.vinci (only for employers whose public JSON job list is switched on) and softgarden (the newer career.softgarden.de sites only). To request another system, open an issue on the Actor's Issues tab.
How do I export Greenhouse, Lever and Ashby jobs to CSV, Excel or JSON?
Each run saves its rows to an Apify dataset. Download it as JSON, CSV, Excel, XML or HTML from the run's Output or Storage tab, or fetch it through the Apify API. Integrations can also send each run's results to Google Sheets, Make, Zapier or webhooks.
Can AI agents use this career site jobs API?
Yes. Add Apify's MCP server with this Actor as a tool, and Claude, ChatGPT or Cursor agents can call it with their own input. The Use from code, spreadsheets or an AI agent section has the set-up.
Feedback
Found a wrong match, a missing field or a company that should work? Open an issue on the Issues tab with the input line and run ID, so we can rerun the same input and check it.