Careers Page Scraper: Greenhouse Jobs, Lever & Workday Jobs avatar

Careers Page Scraper: Greenhouse Jobs, Lever & Workday Jobs

Pricing

from $1.60 / 1,000 job returneds

Go to Apify Store
Careers Page Scraper: Greenhouse Jobs, Lever & Workday Jobs

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

Your Evenings

Maintained by Community

Actor stats

0

Bookmarked

2

Total users

1

Monthly active users

3 hours ago

Last modified

Features

Limited permissions

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:

ATSTypical board URLNotes
Greenhouseboards.greenhouse.io/{token}, job-boards.greenhouse.io/{token}Posted pay ranges included
Leverjobs.lever.co/{token} (and the EU instance jobs.eu.lever.co)Posted salary range included
Ashbyjobs.ashbyhq.com/{token}Posted compensation included
Workableapply.workable.com/{token}
SmartRecruitersjobs.smartrecruiters.com/{token}
Recruitee{token}.recruitee.comPosted 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.comRead 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.deRead 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.com link. 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-STATUS CSV 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 carries companyTotalJobs. If a board token you gave has moved to another supported job board, the jobs are read from the new board and the status says board_moved. Companies we can't find are free.
  • Your limits are honoured, with no hidden extra runs or double charges. maxJobsPerCompany caps 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 free duplicate row).
  • Alerts for new and removed jobs. Turn on monitorMode and 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, with firstSeenAt and lastSeenAt.
  • 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 Apify. Use it from Make, Zapier, n8n 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

  1. Daily hiring alerts for target accounts. Put your account list in companies, turn on monitorMode and 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.
  2. A niche job board fed from employers' own boards. List the companies you cover, filter by titleKeywords, locations or remote, and import the rows. Every row links to the employer's own job and apply pages.
  3. Posted salary ranges for one role across companies. Filter by title (for example "account executive") across a list of companies and compare salaryMin, salaryMax and salaryCurrency where the employer posts them. salaryMin, salaryMax and salaryCurrency are copied from the board, never estimated. salaryPeriod is taken from the board when it gives a period; otherwise it is inferred from the amount (under 500: hour, otherwise year) and salaryPeriodInferred is 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):

CompanyATSTitleLocationPosted salaryPostedBoard total
DiscordgreenhouseCommercial Policy LeadSan Francisco Bay AreaUSD 192,000–216,0002026-09-1546
FigmagreenhouseAccount Executive, EnterpriseSan Francisco, CA • New York, NY • United StatesUSD 165,000–190,0002025-01-28138
LinearashbySenior / Staff Fullstack EngineerEurope–2021-04-2734
NVIDIAworkdaySenior Data Scientist, Data Quality and AI EvaluationUS, CA, Santa Clara–2026-10-302684
SpotifyleverAccount Executive - Commercial, Spotify TechnologyNew York, NY–2026-10-2390

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.

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:

How it finds each company's jobs

For every line of input the Actor:

  1. 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).
  2. 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.
  3. If that fails, reads up to 4 pages of the company's website (your careers URL, /careers, /jobs and 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's embed/job_board?for= code), job-boards.greenhouse.io and Lever EU (jobs.eu.lever.co) links, and the public job APIs an embedded widget calls. Pages that the website's robots.txt disallows, or all pages if that robots.txt can't be read cleanly, are not read.
  4. If that fails too, checks whether Workday has a tenant with that name: each Workday host's public robots.txt says 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.
  5. 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 boardToken field in the output gives you the value to reuse.

Input

FieldWhat it doesDefault
companiesDomains, careers URLs, job-board URLs or ats:token values, one per line5 example companies
includeDescriptionAdds a plain-text descriptionText field (emails and phone numbers removed)off
maxJobsPerCompanyCaps rows per company (0 = all jobs), applied after the filters below. The prefilled form uses 200
titleKeywordsKeep jobs whose title contains any of these words or phrasesnone
excludeTitleKeywordsDrop jobs whose title contains any of these words or phrasesnone
locationsKeep jobs whose location contains any of these placesnone
remoteOnlyKeep only remote jobsoff
departmentsKeep jobs whose department or team contains any of these textsnone
postedWithinDaysKeep jobs posted within the last N days (0 = any date)0
keepJobsWithoutPostedDateWith postedWithinDays set, keep jobs whose board gives no posted dateon
monitorModeReturn only new and removed jobs since the previous runoff
emitBaselineOnFirstRunIn monitor mode, return all current jobs on the first runon
stateStoreNameNamed key-value store that remembers seen jobscareers-jobs-api-state
persistStateKeep firstSeenAt accurate across normal runson
scanCareersPageLook for job-board links on the company website if guessing failson
maxConcurrencyMax parallel HTTP requests5
requestTimeoutSecsPer-request timeout; failed requests are retried 3 times with backoff15

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 (developpeur matches "Développeur"). engineer matches "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: entwickler matches "Softwareentwickler (m/w/d)" and "Entwicklerin", and berater matches "Steuerberater". It doesn't match at the start of a word, so java doesn't match "JavaScript". There's no translation: developer doesn't match "Entwickler".
  • locations: whole words in the board's location text, ignoring case and accents (Zurich matches "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, so United Kingdom does not match a job listed only as "London". Remote also matches jobs the board flags as remote.
  • remoteOnly: keeps jobs with isRemote: true or workplaceType: "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 (eng matches "Engineering"). Department names are whatever each employer uses.
  • postedWithinDays: compares the board's postedAt with 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. salaryPeriod is taken from the board when it gives a period (including Lever's bi-week-salary, which maps to biweekly). If the board gives no period, salaryPeriod is inferred from the amount (under 500: hour, otherwise year) and salaryPeriodInferred is true. Where an employer posts several ranges (for example by pay zone), salaryMin and salaryMax span them and salaryText lists each one.
  • isRemote is true when 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). workplaceType is remote, hybrid or onsite when the board says so.
  • Dates are ISO 8601 in UTC. Not every ATS publishes an update date, so updatedAt can be null.
  • postedAtUnknown is true when the board gives no posted date for the job.
  • postedAt is the board's own posting date (for Greenhouse first_published, never updated_at, which Greenhouse changes across whole boards at once). postedAtBasis says 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), or approximate-from-listing-text for Workday jobs whose listing only says "Posted 3 Days Ago" (converted to a UTC date, accurate to about a day; postedWithinDays allows one extra day for these). Workday jobs listed as "Posted 30+ Days Ago" have no postedAt unless their job page is read.
  • companyTotalJobs is the number of open jobs the company's board lists at run time, before your filters and maxJobsPerCompany. It is on every job row and on company rows for boards that were read (null for not_found, duplicate and error). Compare it with the rows you got to check nothing was left out. For SmartRecruiters it is the board's own totalFound count; 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 a status of not_found, no_open_jobs, no_matching_jobs (filters set, nothing matched), no_change, partial, empty_unconfirmed or fetch_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) or error, plus a note explaining why. A board token you gave that was found on a different job board gets a board_moved row with fromAts, toAts and fromBoardToken (see Moved boards). When job filters are set, companies with matching jobs also get a company row (status ok, changes or partial_budget) with companyTotalJobs, filteredOut and jobsReturned. 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:

ColumnMeaning
inputThe line you entered
company, ats, boardTokenThe board it resolved to (empty if not found)
statusok, 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
totalJobsOpen jobs on the board before filters and limits (empty when the board wasn't read)
returnedJob rows returned for this input
filteredOutJobs (or monitor changes) your filters excluded
notePlain-English explanation
fromAts, toAtsFor 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_found row whose note mentions the Personio page with the feed switched off.
  • Unknown Personio names return a free not_found row.

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), fromBoardToken and a note. Job rows show the new board in ats and detectionMethod: "moved-board". Use the new ats and boardToken in future runs.
  • Not found anywhere: you get the same free not_found row 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_jobs as 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_change row, 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_failed row, 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 partial and 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_unconfirmed row, 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 as removed.

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:

EventWhen it is chargedPrice
Actor startOnce per run (per GB of memory; the default 256 MB run counts as one start)$0.00005
JobEach job row returned (in monitor mode, each new or removed job). Jobs removed by your filters are not charged$0.002
Company resolvedEach 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 changeMonitor 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 monitorMode on, 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 ApifyClient
    client = 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:token values from the boardToken column. 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 maxJobsPerCompany while you test.
  • Turn on includeDescription only 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.

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.