Career Site Jobs Scraper: 9 ATS incl. Workday, Personio
Pricing
from $1.08 / 1,000 jobs
Career Site Jobs Scraper: 9 ATS incl. Workday, Personio
Open jobs from a company's own career site (Greenhouse, Lever, Workday, Personio and 5 more ATS) for a company domain; $1.80 per 1,000 jobs. Posted pay, department, remote flag, description. Monitoring mode returns only new jobs plus free closed-job rows. No recruiter names.
Pricing
from $1.08 / 1,000 jobs
Rating
0.0
(0)
Developer
Arcadium Data
Maintained by CommunityActor stats
0
Bookmarked
2
Total users
1
Monthly active users
a day ago
Last modified
Categories
Share
Give it company domains, careers pages or job board links and it returns every open job as one JSON row: title, department, locations, remote flag, employment type, posted pay, dates, link and description. It reads the company's own board on Greenhouse, Lever, Ashby, Workable, Recruitee, Personio, Teamtailor, SmartRecruiters and Workday, so one company list gives one output shape. $1.80 per 1,000 jobs; you pay only for rows you get.
Why this actor
- Keyed by company domain: hand it
stripe.comand it finds the job board itself. - The European ATS (Personio, Teamtailor, Recruitee) sit next to Greenhouse, Lever and Workday in one schema.
- Monitoring mode (
onlyChanges) returns only new jobs plus a freeclosedrow per vanished job, and keeps the old state if a board suddenly returns nothing. - No recruiter names or contact mailboxes; emails and phones in descriptions are masked.
Built by Arcadium Data: public data, dated and agent-ready, never personal data.
Use with AI agents
Returns open jobs from company career sites (9 job systems) for a company domain; it finds the job board by itself. Agents can call it through the Apify MCP server (https://mcp.apify.com, open source as apify/actors-mcp-server) as arcadium/career-site-jobs: the input schema is read from the actor, the result is a dataset of JSON rows.
Input example (minimal):
{"companies": ["stripe.com"],"maxJobsPerCompany": 20}
"Remote engineering jobs posted this week at three companies, at most 100 rows":
{ "companies": ["stripe.com", "ashby:openai", "lever:zoox"], "keywords": ["engineer"], "remoteOnly": true, "postedWithinDays": 7, "maxItems": 100 }
What comes back: one job row per open position (charged), free coverage rows (stated total vs delivered) and a free error row for a company that could not be read. In monitoring mode (onlyChanges) it also returns free closed rows. Filter on the rowType field. An input that fails is reported, not hidden (includeErrorRows, on by default): the error row carries rowType: "error", a code (invalid_input, not_found, blocked, unavailable, filter_unsupported or no_match), a message and the input it is about. Retry on blocked and unavailable later; do not retry the other codes with the same input.
Cost of the call above: 20 jobs at $1.80 per 1,000 plus the $0.001 start fee is about $0.04 on the free plan; paid plans pay less. Free rows are never charged.
What you get
| Field | What it holds |
|---|---|
id | <system>___<native id>, for example greenhouse___8172508. Stable between runs. |
ats, company, companyToken | The job system, the company name and its board name there. |
title, department, team | As the company publishes them. Empty when not published. |
locations, countries, remote | Location strings as published, ISO country codes where known, and true, false or null for remote. |
employmentType, seniority | Full-time, part-time and so on; seniority only where the system has the field. |
salary | min, max, currency, period and source. structured means the company published a pay field. text means the range was read out of the description. null when nothing is stated. |
postedAt, updatedAt | ISO dates. Not every system publishes both. |
url, applyUrl | The public posting and the apply link. |
description | Plain text or HTML (your choice), with email addresses and phone numbers removed. |
language, requisitionId, scrapedAt | Where known, plus when the row was collected. |
Output example:
{"rowType": "job","id": "ashby___34413f8d-26bf-4bbc-8ade-eb309a0e2245","ats": "ashby","company": "Ramp","companyToken": "ramp","title": "Security Engineer, Cloud","department": "Engineering","team": "Backend","locations": ["New York, NY (HQ)", "Remote (Canada)", "Remote (US)"],"countries": ["US", "CA"],"remote": false,"employmentType": "Full-time","salary": { "min": 211400, "max": 290600, "currency": "USD", "period": "year", "source": "structured" },"postedAt": "2026-04-07T17:12:35.753Z","url": "https://jobs.ashbyhq.com/ramp/34413f8d-26bf-4bbc-8ade-eb309a0e2245","description": "About Ramp\n..."}
Besides jobs, the dataset can hold three kinds of free rows:
coverage: one per company. How many open jobs the board states, how many you received, which system it is and how it was found (explicit,url,htmlorguess). Ashortfallflag marks companies where fewer jobs came back than the board states.closed: in monitoring mode, a job that was open last run and is gone now (id,title,url,closedAt).error: a company that could not be read, with the reason.
Input
{"companies": ["stripe.com", "ashby:openai", "https://jobs.lever.co/zoox"],"includeDescriptions": "text","countries": ["US"],"keywords": ["engineer"]}
| Field | What it does |
|---|---|
companies | Domains, careers URLs, job board URLs, or system:token. A plain company name (notion) also works; see "Finding the job board". |
atsAllowList | Restrict to some systems. Empty means all. |
includeSmartRecruiters | On by default. SmartRecruiters' robots.txt asks crawlers to stay away from its API. Switch this off if you want to honour that. |
includeDescriptions | text (default), html or none. none is the lightest and still returns pay where the board publishes it as a field. |
maxDescriptionChars | Shorten descriptions. 0 keeps them whole. |
maxJobsPerCompany | Cap per company, default 1,000. |
maxItems | Cost cap for the whole run: stops after this many charged jobs across all companies. Empty means no limit. |
keywords, excludeKeywords | Match against title, department and team. |
countries | Names or two-letter codes. Jobs without a stated country are matched on their location text. |
remoteOnly, postedWithinDays | Filters. Jobs without a publish date are kept by the date filter. |
onlyChanges | Monitoring mode, see below. |
emitBaseline | With monitoring: deliver all jobs on the first run (default) or only record them. |
stateKey | Names the saved state. Use a new one when you change filters. |
includeErrorRows, includeCoverageRows | Free rows, on by default. |
Finding the job board
For a domain or a careers page, the actor looks for a link to a supported job system. If the page has none, it tries the company name against each system. A board only counts when it has at least one open job. Check detection on the coverage row: guess means the match came from the name alone, and a different company with the same name is possible. Pass system:token when you need certainty.
Monitoring: only changes
With onlyChanges on, the actor remembers each company's open jobs in a named key-value store in your account. Every later run delivers only the new jobs, adds a free closed row per vanished job and skips description downloads for jobs it already knows. Schedule it daily and pipe the result to Slack, a sheet or a CRM. You pay a small fee per company checked, so a quiet day is almost free.
If a board suddenly returns no jobs at all, the actor keeps the old state and reports an error instead of marking everything closed.
The saved state covers every job on the board, not only the ones your filters keep. If you stop a run early (or it hits your maximum cost), only the jobs already delivered are remembered, and the rest, including closed rows, come with the next run. When emitBaseline is off, the first run reads the job list only and makes no per-job requests.
Switching from another career-site actor
Inputs written for the most used career-site actors work unchanged; each renamed field is noted once in the log.
| Their field | Ours |
|---|---|
startUrls, organizations, domainFilter | companies |
greenhouseBoards, leverCompanies, ashbyOrgs, smartrecruitersCompanies | companies as greenhouse:<token>, lever:<token>, ... |
titleKeyword, titleSearch, keywords (text) | keywords |
titleExclusionSearch | excludeKeywords |
postedAfter, datePostedAfter, timeRange (24h, 7d, 6m) | postedWithinDays |
includeDescription: false, descriptionType | includeDescriptions |
maxItemsTotal, limit | maxItems |
onlyNewOrUpdated, stateKey | onlyChanges, stateKey |
A per-company maxItems from another actor is read as our run-wide cap; set maxJobsPerCompany for a per-company limit.
Use it from an AI assistant
Use the Apify actor "career-site-jobs" to list open roles.Input: {"companies":["stripe.com","ashby:openai"],"keywords":["data"],"includeDescriptions":"none","maxJobsPerCompany":200}Each dataset row with rowType "job" is one position. Key fields: company, title, department, locations, remote, salary, postedAt, url.Run it, then group the roles by department and list the ones that publish a salary range.
Use it from code
Python:
from apify_client import ApifyClientclient = ApifyClient("<YOUR_APIFY_TOKEN>")run = client.actor("arcadium/career-site-jobs").call(run_input={"companies": ["stripe.com", "ashby:openai"],"includeDescriptions": "none",})for job in client.dataset(run["defaultDatasetId"]).iterate_items():if job["rowType"] == "job":print(job["company"], job["title"], job["locations"])
JavaScript:
import { ApifyClient } from 'apify-client';const client = new ApifyClient({ token: '<YOUR_APIFY_TOKEN>' });const run = await client.actor('arcadium/career-site-jobs').call({companies: ['stripe.com', 'ashby:openai'],includeDescriptions: 'none',});const { items } = await client.dataset(run.defaultDatasetId).listItems();console.log(items.filter((i) => i.rowType === 'job').length, 'jobs');
Pricing
You pay per job delivered, at the price of your Apify plan. In monitoring mode (onlyChanges) you also pay a small fee per company checked, and jobs you already know are not billed again. Failed companies, coverage rows and closed rows are free.
| Apify plan | Per 1,000 jobs | Per 1,000 companies checked |
|---|---|---|
| Free | $1.80 | $1.80 |
| Starter (Bronze) | $1.80 | $1.80 |
| Scale (Silver) | $1.58 | $1.58 |
| Business (Gold) | $1.08 | $1.13 |
| Platinum | $1.08 | $1.13 |
| Diamond | $1.08 | $1.13 |
Plus a one-off $0.001 per GB of memory when a run starts.
Set a maximum cost per run in the Apify run options and the actor stops before it goes over.
Related actors and data
- Hiring Changes Tracker: New and Closed Jobs per Company
- ATS Lookup: Which ATS a Company Uses, Job Board, Open Jobs
- Company Hiring Profile: ATS, Open Jobs, Hiring Signals
- Indeed Jobs Scraper: Company Domains, Salary, Descriptions
- Arcadium Data Hiring Signals API: open jobs, ATS and hiring signals for a company domain from our daily jobs database.
- Company Pulse: dated change events per company domain (hiring up or down, new country, ATS switch, first posted pay).
- Jobs data feed: the whole open-jobs table as daily files, keyed by employer domain.
- Posted-pay tracker: share of job ads that state pay, by country and employer size.
- Arcadium Data MCP Server: AI agents can ask the same questions as tools (
company_profile,company_pulse,jobs_search,company_hiring).
Use cases
- Sourcing: pull every open role of a target list of companies, filtered by country or keyword.
- Sales intelligence: a company that opens ten sales roles is growing. Monitor target accounts and act on the change.
- Job boards and newsletters: a daily feed of new roles across hundreds of employers.
- Pay research: collect the salary ranges companies publish, by role and city.
- Market analysis: count open roles per department over time.
Limitations
- Public career pages only. Nothing behind a login.
- Supported: Greenhouse, Lever, Ashby, Workable, Recruitee, Personio, Teamtailor, SmartRecruiters, Workday. A company whose careers site is a custom page or runs on another system returns a "no board found" error row.
- Workday boards are found from their address (
https://company.wd5.myworkdayjobs.com/Site), fromworkday:company/wd5/Site, or from a careers page that links to the board. A company name alone cannot find a Workday board. Workday lists show only relative dates, so the actor reads one extra page per new job to get the exact posting date and the description; that makes Workday runs slower than the other systems (a few jobs per second). Boards over 2,000 jobs are read in slices (by job category or similar) and merged; jobs are counted once. - Pay is returned only when the company states it. Ranges read out of free text carry
source: "text"and can be wrong; check them before relying on them. remoteisnullwhen the system does not say. Some systems mark a job remote because one of several locations is remote.- Posting dates are what the company publishes. Some systems give no update date, some give a creation date instead of a publish date.
- Large Greenhouse and Ashby boards are delivered by the system in one piece, so
maxJobsPerCompanylimits the rows you get, not the data read. - Very large boards (tens of thousands of jobs, for example big Workday sites) need a longer run timeout than the 1 hour default; raise it in the run options for the first full run. Monitoring runs after that are much faster.
- In monitoring mode, descriptions are only fetched for new jobs. Closed rows carry id, title and link, not the full old record.
Privacy
Recruiter names, contact mailboxes and similar fields are never collected. Email addresses and phone numbers inside descriptions are replaced by [email] and [phone]. You get the job, not the people behind it.
FAQ
How fresh is the data? It is read when you run the actor, so it matches what the company shows at that moment.
Can I get only new jobs since my last run?
Yes, set onlyChanges and keep the same stateKey. The first run delivers everything (or just records the baseline with emitBaseline off). To start over, use a new stateKey.
A company returned an error. Why?
Open its careers page. If the jobs live on a system that is not in the list above, or in a custom page, there is no board to read. Try system:token if you know the board name. Errors are not charged.
Why did I get fewer jobs than the board states?
Look at the coverage row: stopReason tells you whether your cap, a filter or the budget ended the list. shortfall: true marks a real gap.
Something looks wrong in the output. Who do I tell? Open an issue on the actor page with the run link. We read them all.