Company Careers Jobs Scraper: Workday, Greenhouse, Lever · Apify
Company Careers Jobs Scraper: Workday, Greenhouse, Lever avatar

Company Careers Jobs Scraper: Workday, Greenhouse, Lever

Pricing

from $1.20 / 1,000 jobs

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

Company Careers Jobs Scraper: Workday, Greenhouse, Lever

Job listings and job openings from company career pages: Workday jobs (myworkdayjobs), Greenhouse, Lever, Ashby, SmartRecruiters. Paste careers page URLs, e.g. engineering jobs at Stripe, Notion, NVIDIA. Free company hiring row with hiring signals and seniority. Only new jobs. Jobs API.

Pricing

from $1.20 / 1,000 jobs

Rating

0.0

(0)

Developer

Solid Fetch

Solid Fetch

Maintained by Community

Actor stats

0

Bookmarked

2

Total users

1

Monthly active users

12 days ago

Last modified

Features

Limited permissions

Share

Company Careers Jobs Scraper: Workday, Greenhouse, Lever, Ashby

Job listings and job openings from company career pages, for example all open engineering jobs at Stripe, Notion or NVIDIA (a Workday careers site, myworkdayjobs). Paste company names or careers page URLs from Workday, Greenhouse, Lever, Ashby or SmartRecruiters and get one row per open job, with title, department, location, remote, seniority and salary when the company shows it. You also get a free company hiring row with hiring signals: the open roles by department, new roles in the last 7 days and a hiring score. Give only a company domain such as stripe.com and the actor finds its careers page and counts how many people the company is hiring and at what seniority. A job row costs $2.00 per 1,000 on the free plan.

{
"companies": ["https://boards.greenhouse.io/airbnb", "lever:zoox", "ashby:ramp"],
"keywords": ["engineer"],
"maxItems": 100
}

One output row per open job (shortened from the full row under Output):

{
"rowType": "job",
"ats": "ashby",
"company": "ramp",
"title": "Security Engineer, Cloud",
"department": "Engineering",
"location": "New York, NY (HQ)",
"remote": true,
"salaryText": "$211.4K - $290.6K",
"postedAt": "2026-04-07T17:12:35.753Z",
"url": "https://jobs.ashbyhq.com/ramp/34413f8d-26bf-4bbc-8ade-eb309a0e2245"
}

Workday, Greenhouse, Lever and Ashby jobs feed: hiring signals and job postings API

Workday jobs, Greenhouse jobs, Lever jobs, Ashby jobs and SmartRecruiters jobs of the companies you choose, in one schema. Give a list of company names or careers-page URLs and get the open jobs at those target companies, plus a free hiring-signal row per company: open roles, roles by department, remote roles, postings in the last 7 days, and what is new or closed since the last run. The actor works out the vendor from each address. Filter by keyword, location, remote, department and posting date, or switch on the monitor to get only the jobs that are new since your last run. Each job is one row with the title, department, team, locations, remote and workplace type, employment type, salary when the vendor shows it, posting date, job and apply URLs and the description.

Read this before you schedule the monitor: with onlyNew on, the first run for a company only records what it has open and returns no jobs (its free summary row is still saved). New jobs come from the second run on. Set emitOnFirstRun to true to get, and pay for, every open job on the first run as well.

The actor reads only the public job-board APIs that the five vendors offer for publishing jobs (for Workday, the JSON that a career site's own pages load). There is no login, no browser, no proxy and no scraping of LinkedIn. The one place the actor reads a company's own website is the hiring-signals mode (see "Hiring signals by company domain"), which fetches a careers page with a plain request and respects robots.txt.

What people use it for

  • Hiring signals for sales and recruiting. A company that opens ten engineering roles in a week is growing a team. Run the monitor daily over a list of target accounts and get the new roles, plus a free row per company with its open roles by department.
  • Job boards and alerts. Feed a niche job board or a Slack channel with fresh remote roles from the companies you care about. Each run returns only what is new, so the feed has no duplicates.
  • Market and salary research. Collect open roles with their salary ranges (Ashby, Lever and Greenhouse show them when the company turns pay transparency on) across many companies in one table, without learning four different APIs.
  • Competitor watch. Follow which teams a competitor is hiring for and where.

How to use it

Read three companies on three vendors. The vendor follows from the address, or from the prefix of a board id:

{
"companies": [
"https://boards.greenhouse.io/airbnb",
"https://jobs.lever.co/zoox",
"ashby:ramp",
"https://careers.smartrecruiters.com/Canva",
"https://nvidia.wd5.myworkdayjobs.com/NVIDIAExternalCareerSite"
],
"maxItems": 500
}

Workday jobs of one company, the newest 100, without descriptions (see "Workday career sites"):

{
"companies": ["workday:nvidia/NVIDIAExternalCareerSite/wd5"],
"maxItems": 100,
"includeDescription": "none"
}

Remote engineering jobs posted in the last two weeks, in Europe or the US:

{
"companies": ["greenhouse:stripe", "lever:spotify", "ashby:notion"],
"keywords": ["engineer", "developer"],
"excludeKeywords": ["intern"],
"remoteOnly": true,
"locations": ["Europe", "United States", "Poland", "Germany"],
"postedSince": "2 weeks",
"includeDescription": "none"
}

The monitor, scheduled daily. The first run records what each company has open and returns no jobs, only the free summary rows. That is the baseline, not an error. From the next run on, you get only the jobs that are new since the run before:

{
"companies": ["greenhouse:airbnb", "lever:zoox", "ashby:ramp"],
"departments": ["Engineering", "Product"],
"onlyNew": true
}

The same monitor, but the first run also returns every open job that passes the filters, and charges each one. Later runs return only the new ones, as above:

{
"companies": ["greenhouse:airbnb", "lever:zoox", "ashby:ramp"],
"departments": ["Engineering", "Product"],
"onlyNew": true,
"emitOnFirstRun": true
}

A field name the actor does not know stops the run with a hint such as "keyword" (did you mean "keywords"?), so a typo cannot turn into a run of unfiltered jobs that you pay for.

Input

FieldWhat it does
companiesThe companies to read, up to 2,000. Each entry is a careers-page address (boards.greenhouse.io/stripe, job-boards.greenhouse.io/stripe, jobs.lever.co/spotify, jobs.eu.lever.co/acme, jobs.ashbyhq.com/ramp, careers.smartrecruiters.com/Canva, nvidia.wd5.myworkdayjobs.com/NVIDIAExternalCareerSite, an address with a job id or a language such as /en-US/ in it works too), a board id with its vendor (greenhouse:stripe, lever:spotify, ashby:ramp, smartrecruiters:Canva, workday:nvidia/NVIDIAExternalCareerSite/wd5, short forms gh and sr work), or a bare id such as stripe. A bare id is never read as a Workday site, because Workday needs the tenant, the site name and the data-centre label. A start-URL object with a url is also accepted.
atsWhich vendor to read for bare ids. auto (default) reads Greenhouse, Lever, Ashby and SmartRecruiters (not Workday, see the companies row) and takes the one vendor with open jobs. When more than one vendor has open jobs under the id, nothing is saved or charged for it and it gets a free ambiguous-company error row that names the vendors. Name the vendor if you know it, to save requests and to avoid a same-named board on another vendor.
keywords, excludeKeywordsKeep jobs whose title has any of the keywords, and drop jobs that have any of the excluded ones. Matching ignores case and accents and starts at a word: engineer finds "Engineering Manager", go does not find "Django".
keywordScopetitle (default) or titleAndDescription.
locationsKeep jobs that have any of these in one of their locations or in the country. Locations are free text as the company wrote them ("Remote (US)", "NYC"), so list the variants you need.
departmentsKeep jobs whose department or team contains any of these.
remoteOnlyKeep jobs marked remote, or with a remote location.
seniorityKeep jobs of any of these levels: intern, junior, mid, senior, lead, manager, director, exec. A job with no level is dropped. See "Seniority, work arrangement and skills".
skillsKeep jobs that name any of these skills, for example ["Python", "Kubernetes"]. See "Seniority, work arrangement and skills".
postedSinceA date (2026-10-15) or a period back from today (7 days, 2 weeks, 1 month). Jobs with no posting date are dropped.
onlyNewMonitor mode. See "How the monitor works".
emitOnFirstRunWith onlyNew, also return (and charge) every job of a company that passes the filters the first time the actor sees it. Default off, so the first run returns no jobs and only records the baseline.
includeDescriptiontext (default), html or none.
maxItemsStop after this many jobs in the run, up to 1,000,000. Default 1,000.
maxJobsPerCompanySave at most this many jobs, the newest, from each company.
modejobs (default) returns the job rows of companies. hiringSignals takes companyDomains and returns one row per domain. See "Hiring signals by company domain".
companyDomainsOnly with mode set to hiringSignals, up to 500. Company domains such as stripe.com, or a full address such as https://www.stripe.com/careers. In that mode companies, the job filters (keywords, excludeKeywords, locations, departments, remoteOnly, seniority, skills, postedSince), emitOnFirstRun and maxJobsPerCompany are refused with a message instead of being ignored. onlyNew and maxItems still apply.
stateStoreNameDefault ats-jobs-feed-state. The key-value store on your account that holds the monitor's memory. Use another name to keep a separate watch, for example per client.

Output

The dataset has three kinds of rows, told apart by rowType: job (charged), summary (one per company, free) and error (one per entry that gave no jobs, free). The dataset views "Jobs", "Hiring signals per company" and "Entries that gave no jobs" show each kind as a table. To keep only jobs in an export, filter on rowType being job.

A job row, built from a recorded Ashby posting:

{
"rowType": "job",
"id": "ashby:ramp:34413f8d-26bf-4bbc-8ade-eb309a0e2245",
"ats": "ashby",
"companyId": "ramp",
"company": "ramp",
"jobId": "34413f8d-26bf-4bbc-8ade-eb309a0e2245",
"title": "Security Engineer, Cloud",
"department": "Engineering",
"team": "Backend",
"employmentType": "full-time",
"location": "New York, NY (HQ)",
"locations": ["New York, NY (HQ)", "Remote (Canada)", "Remote (US)", "Miami, FL"],
"country": "USA",
"remote": true,
"workplaceType": "hybrid",
"workArrangement": "hybrid",
"seniority": null,
"skills": ["DevOps", "AWS", "Terraform", "Python", "Flask", "Azure", "GCP"],
"skillsSource": "description",
"salaryMin": 211400,
"salaryMax": 290600,
"salaryCurrency": "USD",
"salaryPeriod": "year",
"salaryText": "$211.4K - $290.6K",
"postedAt": "2026-04-07T17:12:35.753Z",
"updatedAt": null,
"url": "https://jobs.ashbyhq.com/ramp/34413f8d-26bf-4bbc-8ade-eb309a0e2245",
"applyUrl": "https://jobs.ashbyhq.com/ramp/34413f8d-26bf-4bbc-8ade-eb309a0e2245/application",
"language": null,
"description": "About Ramp\nRamp is building the smart infrastructure for finance teams...",
"descriptionError": null,
"source": "Ashby public job-board API",
"fetchedAt": "2026-10-22T09:00:00.000Z"
}
FieldMeaning
id, jobIdid is vendor:company:jobId and is unique across vendors. jobId is the vendor's own id.
ats, companyId, companyThe vendor (greenhouse, lever, ashby, smartrecruiters, workday), the board id you gave, and the company name. Greenhouse and SmartRecruiters send the company name, Lever, Ashby and Workday do not, so for those company is the board id (for Workday the tenant, for example nvidia). A Workday companyId is tenant/site/wdN.
title, department, teamAs the company wrote them. SmartRecruiters has no team, and its department falls back to the job function (Sales, Engineering) when the company sets none. Greenhouse has no team.
employmentTypefull-time, part-time, contract, internship, temporary or other, read from the vendor's label. null when the vendor sends none (Greenhouse sends it only on some boards).
location, locations, countryThe main location, all locations of the job, and the country where the vendor gives one (Lever: two-letter code. Ashby: as written, "USA" or "United States". SmartRecruiters: two-letter code. Greenhouse: not given, null).
remote, workplaceTyperemote is true when the vendor marks the job remote or lists a remote location. workplaceType is remote, hybrid or onsite where the vendor says it, null where it does not. Ashby marks a hybrid job remote when it also lists a remote location, so check workplaceType when the difference matters.
workArrangement, seniority, skills, skillsSourceWorked out from the posting's words with fixed rules, free of charge and with no AI. See "Seniority, work arrangement and skills".
salaryMin, salaryMax, salaryCurrency, salaryPeriod, salaryTextThe salary the vendor shows. Greenhouse: the pay range (no period, so salaryPeriod is null, ranges for several locations are merged when they share a currency). Lever: the salary range and its period. Ashby: the salary component and its period, with the vendor's short summary in salaryText. SmartRecruiters has no salary field. null when the company shows no pay. Bonus and equity are not included.
postedAt, updatedAtWhen the job was first published (Greenhouse first_published, Lever createdAt, Ashby publishedAt, SmartRecruiters releasedDate) and, for Greenhouse only, when it was last updated. Lever and Ashby give no update time.
postedAtApproximatetrue when postedAt was worked out from a relative text (Workday: "Posted 3 Days Ago"). Always false for the other vendors.
url, applyUrlThe job page and the apply page.
jobReqIdThe requisition number where the vendor shows one (Workday, for example JR2015813), otherwise null.
descriptionPlain text, HTML or null, as set by includeDescription. E-mail addresses are replaced with [email removed].
descriptionErrornull on a charged job. When a description was asked for and could not be fetched (SmartRecruiters needs one extra request per job), the job is saved free with description null and the reason here.
languageThe language code where the vendor gives one (Greenhouse, SmartRecruiters).
source, fetchedAtThe vendor API the row comes from, and when it was read.

A summary row, saved free for each company that was read:

{
"rowType": "summary",
"ats": "ashby",
"companyId": "ramp",
"company": "ramp",
"boardUrl": "https://jobs.ashbyhq.com/ramp",
"openRoles": 4,
"postedLast7Days": 0,
"remoteRoles": 4,
"newestPostedAt": "2026-08-12T20:22:32.685Z",
"byDepartment": [
{ "department": "Engineering", "openRoles": 3 },
{ "department": "Risk", "openRoles": 1 }
],
"seniorityMix": { "intern": 0, "junior": 1, "mid": 0, "senior": 0, "lead": 0, "manager": 0, "director": 0, "exec": 0, "unknown": 3 },
"hiringScore": 19,
"hiringScoreParts": { "volume": 11, "freshness": 0, "breadth": 8 },
"matchedThisRun": 1,
"savedThisRun": 1,
"newSinceLastRun": null,
"closedSinceLastRun": null,
"baseline": false,
"truncated": false,
"fetchedAt": "2026-10-22T09:00:00.000Z"
}

openRoles counts every posting on the company's board, whatever your filters. matchedThisRun counts what passed the filters (in monitor mode, among the new postings only) and savedThisRun what was saved and charged. newSinceLastRun and closedSinceLastRun are filled in monitor mode only. baseline is true on the run that recorded the starting point. truncated is true when a SmartRecruiters board has more than 10,000 postings, which is the most the actor reads for one company.

An error row, saved free for each entry that gave no jobs:

{
"rowType": "error",
"input": "ashby:nope",
"ats": "ashby",
"companyId": "nope",
"company": null,
"outcome": "not found",
"error": "Ashby has no job board \"nope\"",
"fetchedAt": "2026-10-22T09:00:00.000Z"
}

outcome is invalid (not a job-board address or id, for example a company's own careers site), not found (the vendor has no such board), ambiguous-company (a bare id with open jobs on more than one vendor: give vendor:id or the careers-page URL) or failed (the vendor did not answer after retries, or its format changed). A company with a board and no open roles gets a normal summary row with openRoles 0.

The run also saves a SUMMARY record in the key-value store: the companies read with their counts, the entries not found, failed or invalid, the bare ids found on more than one vendor, the companies not read because the run stopped, and how many descriptions could not be fetched.

Seniority, work arrangement and skills

Every job row carries four columns that recruiters and sales teams filter on. They come from the title and the description with fixed rules and a fixed list, so the same posting always gives the same answer. There is no AI call and no outside service, and they add nothing to the price.

ColumnValuesHow it is found
seniorityintern, junior, mid, senior, lead, manager, director, exec, or nullThe title decides, from the highest level down: Intern, Chief or VP, Director or Head of, Manager, Lead, Staff or Principal, Senior or Sr, Junior or Associate, Mid-level or "II". A job function that ends in "Manager" (Product Manager, Account Manager) is not a management level, so "Senior Product Manager" is senior. When the title says nothing, a plain statement in the description fills in ("entry-level position", "mid-level", "senior-level"). A plain "Software Engineer" stays null, because guessing would be wrong too often.
workArrangementremote, hybrid, onsite or nullThe vendor's own field first (workplaceType). Then the title and locations ("Remote - US", "Hybrid"). Then clear statements in the description ("this role is hybrid", "fully remote", "this is an on-site position"). A passing mention such as "we build tools for remote teams" does not count. workplaceType stays as it was, null where the vendor says nothing.
skillsA list such as ["Python", "PostgreSQL", "Kubernetes"]The title and description are matched against a curated list of about 300 tech and business skills (languages, frameworks, cloud, data, security, design, product, sales, marketing, finance, HR). A skill matches as a whole word in any case, with common aliases (k8s, golang, postgres). Words that are also ordinary English (Go, R, Excel, React, Swift, Spark) match only in a skill context, so "go-to-market" is not Go. Short acronyms that are also words (ML, RAG, SAP) must be written in capitals. The list is in the order the skills first appear.
skillsSourcedescription or titledescription when the skills were read from the title and the description. title when the posting has no description to read (see below), so the list is short.

SmartRecruiters and Workday lists carry no description. With includeDescription on, the actor fetches it before the row is saved, so those rows have skillsSource description. With includeDescription set to none they are read from the title alone and say so. Greenhouse, Lever and Ashby send the description with the list, so their skills are always read from it, even with includeDescription set to none.

Filter on them. Senior and lead engineers who name Python or Go:

{
"companies": ["greenhouse:airbnb", "lever:zoox", "ashby:ramp"],
"keywords": ["engineer"],
"seniority": ["senior", "lead"],
"skills": ["Python", "Go"]
}

Any manager or director who knows Salesforce or HubSpot, in a monitor that returns only new jobs:

{
"companies": ["greenhouse:stripe", "ashby:notion"],
"seniority": ["manager", "director"],
"skills": ["Salesforce", "HubSpot"],
"onlyNew": true
}

seniority keeps a job when its level is any of the listed ones. skills keeps a job when any listed skill is found. A name that is not in the curated list is searched as a whole word in the title and description, so "skills": ["Tailscale"] works too. A skills filter reads the description, so for SmartRecruiters and Workday it costs one extra request per job that passes the other filters, the same as keywordScope set to titleAndDescription. A seniority filter uses the level found in the title and the description the list already has, and does not fetch descriptions on its own, so on SmartRecruiters and Workday it sees the title. Each combination of filters keeps its own monitor memory.

These are rules, not understanding. Titles such as "Software Engineer" have no level, a skill named only in an image or an attachment is not found, and a posting that mentions "Python" as something it does not want is still tagged Python. Check a sample of your own boards before you build on the columns.

Workday career sites

Workday jobs are the biggest source of corporate postings (NVIDIA, Salesforce, Adobe and thousands more), and each company runs its own career site at https://<tenant>.<wdN>.myworkdayjobs.com/<site>. Give that address, with or without the language part (/en-US/) or a job in it, or write workday:<tenant>/<site>/<wdN>:

{
"companies": [
"https://nvidia.wd5.myworkdayjobs.com/en-US/NVIDIAExternalCareerSite",
"workday:salesforce/External_Career_Site/wd12"
],
"keywords": ["engineer"],
"postedSince": "7 days",
"includeDescription": "none"
}

The actor reads the JSON that the career site's own page loads, POST /wday/cxs/<tenant>/<site>/jobs, 20 postings per request, and for a description GET /wday/cxs/<tenant>/<site>/job/<path>. The robots.txt of the career sites checked does not disallow that path, and the actor sends one request at a time with a pause between them. A career site that answers 401 or 403, or with a bot-check page, is not read and not worked around. It gets a free failed error row and the other companies go on.

What the Workday rows hold, and what they do not:

  • Posting date. Workday's list says only "Posted Today", "Posted 3 Days Ago" or "Posted 30+ Days Ago". postedAt is that day at 00:00 UTC and postedAtApproximate is true. "30+ days" has no date (postedAt is null), so postedSince drops those postings. With includeDescription on, the posting's own page gives the exact day, and then postedAtApproximate is false.
  • Locations. The list gives one location, or only "3 Locations". For the second case the actor takes the first location from the posting's address, for example Israel Yokneam. With includeDescription on, locations holds all of them, and country (two-letter code), employmentType (from Workday's "time type") and workplaceType and remote (from its remote type, where the company sets one) are filled in too. A locations filter is applied to the list entry, so it sees only the first location of such a posting.
  • No department, team or salary. Workday's list has none, so those fields are null.
  • Cost of descriptions. Each saved Workday job takes one extra request for its page, paced, so includeDescription none is much faster. A posting whose page cannot be read is saved free with descriptionError.
  • Size. Workday lists at most 2,000 postings of one site. A site at that size has truncated set to true in its summary row. Without any filter and without the monitor, the actor reads only as many postings as maxItems (or maxJobsPerCompany) allows, 20 per request, newest first, and sets truncated for the rest. With a filter or onlyNew it reads the whole list (up to 100 requests for a big site) so that nothing is missed.
  • Job id. jobId is the requisition part of the posting's address (JR2015813, or JR355160-1 for a repeated posting), jobReqId is the number Workday shows.

Hiring-signals mode also finds Workday: a careers page that links to *.myworkdayjobs.com gives the domain a row with ats workday. A Workday site is never found by the name guess, because the name does not say the tenant, site and data centre.

Hiring signals by company domain

People ask for the "job opening count" of a company, and they have its website, not its Greenhouse or Lever address. Set mode to hiringSignals and give domains:

{
"mode": "hiringSignals",
"companyDomains": ["ramp.com", "stripe.com", "notion.so"]
}

For each domain the actor finds the company's job board and returns one row with the open-role count. It does this in two steps.

  1. The careers page. It asks the company's own site for /careers, then /jobs, then careers.<domain>, then the home page, and stops at the first page that links a Greenhouse, Lever, Ashby, SmartRecruiters or Workday board (boards.greenhouse.io, job-boards.greenhouse.io, jobs.lever.co, jobs.ashbyhq.com, careers.smartrecruiters.com, *.myworkdayjobs.com). It is one plain GET per page, at most four pages per domain, with robots.txt read first. A page that robots.txt disallows is not requested, a site whose robots.txt cannot be read is left alone, and a 403 or any other refusal is taken as the answer. There is no proxy, no browser and no way around a block. The row says matchedBy: "careers-page" and gives the page in careersUrl.
  2. The name, only when the pages name no board. The actor tries the domain's name (stripe for stripe.com) as a board id on each of the four vendors. A board with that id is used only when it belongs to the domain, which means the company name the vendor sends is the domain's name (Greenhouse and SmartRecruiters send it, so Stripe, Inc. counts for stripe.com), or at least two of the board's postings write the domain in their text or addresses. A board that fails this check is dropped, so notion.so does not get the counts of some other company called Notion. The row says matchedBy: "name-guess", and careersUrl is null.

A row has these fields:

FieldMeaning
domainThe domain, lower case, without www..
ats, boardId, company, boardUrlThe vendor, the board id, the company name where the vendor sends it, and the board address. All null when no board was found.
openRolesEvery posting on the board.
byDepartmentOpen roles per department, largest first.
seniorityMixOpen roles per level: intern, junior, mid, senior, lead, manager, director, exec and unknown. The nine numbers add up to openRoles. See "Seniority mix and hiring score".
hiringScore, hiringScorePartsA number from 0 to 100 and the three parts it is the sum of. See "Seniority mix and hiring score". null when no board was found.
remoteCountPostings marked remote or with a remote location.
postedLast7Days, newestPostedAtFresh postings, and the date of the newest one.
newSinceLastRun, closedSinceLastRunWith onlyNew on. Postings the last run did not know, and postings that are gone. null on the first run for a board. These use a memory of their own, so a hiring-signals watch never hides jobs from a jobs-mode monitor of the same board.
careersUrl, matchedByThe page that linked the board, and careers-page or name-guess.
chargedtrue when the row was saved as a job event.
noteWhat the match rests on. For a domain with no board, why none was found (a 404, a robots.txt rule, a vendor that did not answer).
truncatedThe board has more postings than the actor reads, so the count is a lower bound.

A row for ramp.com, built from the recorded careers page and Ashby board:

{
"rowType": "hiringSignal",
"domain": "ramp.com",
"charged": true,
"ats": "ashby",
"boardId": "ramp",
"company": null,
"openRoles": 4,
"byDepartment": [
{ "department": "Engineering", "openRoles": 3 },
{ "department": "Risk", "openRoles": 1 }
],
"seniorityMix": { "intern": 0, "junior": 1, "mid": 0, "senior": 0, "lead": 0, "manager": 0, "director": 0, "exec": 0, "unknown": 3 },
"hiringScore": 19,
"hiringScoreParts": { "volume": 11, "freshness": 0, "breadth": 8 },
"remoteCount": 4,
"postedLast7Days": 0,
"newestPostedAt": "2026-08-12T20:22:32.685Z",
"newSinceLastRun": null,
"closedSinceLastRun": null,
"careersUrl": "https://ramp.com/careers",
"boardUrl": "https://jobs.ashbyhq.com/ramp",
"matchedBy": "careers-page",
"note": "Ashby board \"ramp\" is linked from https://ramp.com/careers",
"truncated": false,
"fetchedAt": "2026-10-28T09:00:00.000Z"
}

What it costs. Each company with a board is charged one job event, the same event as a job row. Think of it as one job-event per company with a board. A domain with no board found, an invalid entry and a domain whose lookup failed are free. Nothing new is added to the pricing. maxItems counts the charged rows, and the run stops before a row your cost limit cannot pay for. A restarted run does not charge a domain twice (RUN_STATE records each charged domain).

A weekly watch over a list of target accounts: run the same domains on a schedule with onlyNew on. From the second run each row carries newSinceLastRun and closedSinceLastRun. The dataset view "Hiring signals by company domain" shows the rows as a table.

Pair it with two other Solid Fetch actors. Run Website Technology Lookup on the same domains to see which ad tags, pixels and marketing tools each company uses, and Google Ads Transparency Scraper to see who is advertising on Google. A company that is hiring five marketers, runs an ad pixel and has live Google ads is a company with a marketing budget. All three take the domain as the key, so the tables join on it.

Hiring signals from the free summary rows

Every company that was read gets one summary row (rowType summary), saved free on every run and on the baseline run too. Open the companies view of the dataset to see only these rows. They are the hiring signal:

SignalFieldHow to read it
Size of the hiring pushopenRolesEvery posting on the company's board, whatever your filters.
Which teams are growingbyDepartmentOpen roles per department, largest first. Ten open Engineering roles at a 200 person company is a team being built.
Hiring right nowpostedLast7Days, newestPostedAtFresh postings. A board whose newest posting is months old is quiet.
Level of the rolesseniorityMixOpen roles per seniority level. Many director and exec roles next to few junior ones is a leadership build-out.
One number for "how hard are they hiring"hiringScore, hiringScoreParts0 to 100, with its three parts, see below.
Remote friendlinessremoteRolesPostings marked remote or with a remote location.
Change since last timenewSinceLastRun, closedSinceLastRunMonitor mode only (onlyNew). Counts every new and every closed posting on the board since the run before, whatever your filters.

Seniority mix and hiring score

seniorityMix and hiringScore are on the free summary row and on the hiring-signal row of a domain (null on a domain with no board). They do not change what is charged. Both are computed from the postings the actor just read, with fixed rules and no AI call, so the same board on the same day gives the same numbers.

Seniority mix. Every posting counts once, under the level seniority gives it on a job row: the title decides (Senior, Staff, Director, Intern and similar words, in English), and the description only fills in when the title says nothing. Postings with no level in either count under unknown. SmartRecruiters and Workday lists carry no description, so for them only the title is read and unknown is larger. lead is a technical lead, principal or architect. manager, director and exec are the leadership levels. Titles in other languages mostly land in unknown.

Hiring score. The sum of three parts, rounded to whole numbers:

PartRangeFormula
volume0 to 4040 x log10(1 + open roles) / log10(301), at most 40. Full marks at 300 open roles or more. 10 roles give 17.
freshness0 to 4040 x (roles posted in the last 7 days / roles with a posting date) / 0.25, at most 40. Full marks when a quarter of the dated roles are from this week. 0 when no posting has a date.
breadth0 to 2020 x departments with at least one role / 5, at most 20. Postings without a department are not counted.

An empty board scores 0. The score measures how large, how fresh and how broad the open roles on one board are, nothing else: it does not know about funding, layoffs, roles on other boards or the company's size, so a big stable board with old postings scores on volume and breadth only. Workday posting dates are worked out from "Posted 3 Days Ago" and are right to the day at best. The thresholds (300 roles, 25%, 5 departments) are choices of this actor, not a standard. The parts are given so you can recompute your own weighting.

A snapshot of open roles for a list of target accounts, without paying for the job rows: run the monitor once with a fresh stateStoreName. It is a baseline run, so it saves no jobs and charges none, and every company gets its summary row with openRoles, byDepartment, postedLast7Days and remoteRoles. You pay only the start of the run.

{
"companies": ["greenhouse:stripe", "lever:spotify", "ashby:ramp", "ashby:notion"],
"onlyNew": true,
"stateStoreName": "hiring-snapshot-2026-10",
"includeDescription": "none"
}

A weekly hiring-signal watch: schedule the same companies weekly with onlyNew on. The summary rows then carry newSinceLastRun and closedSinceLastRun for each company, and the job rows are only the new postings. To keep the paid job rows small while still counting every new posting on the board, add a departments or keywords filter for the roles you sell to, for example ["Engineering"], and set includeDescription to none.

A company that is not on one of the five vendors gets a free error row, not a summary row (see the error row below).

How the monitor works

With onlyNew on, the actor keeps a record of each company's open jobs in a named key-value store on your Apify account (stateStoreName). A run returns only the jobs that were not in the record, then updates the record.

  • The first run for a company records the starting point and returns no jobs. The status message says so. This is on purpose: adding 200 companies must not return and charge for their whole job lists. Set emitOnFirstRun to get them anyway.
  • Jobs that pass the filters are saved. A new job that fails a filter is still remembered, so it does not come back later as new.
  • A job left out because of maxJobsPerCompany, maxItems or your cost limit stays unseen, and the next run returns it.
  • A job whose description could not be fetched is saved free with descriptionError and stays unseen, so the next run tries again and charges it once it has its description.
  • Each set of filters (keywords, excludeKeywords, keywordScope, locations, departments, remoteOnly, seniority, skills) keeps its own record of a board, so two scheduled tasks with different filters on the same company do not hide jobs from each other. Order and case of the values do not matter, and postedSince is not part of it. Changing the filters starts a new record and a new starting point. A watch with no filters uses the same record as earlier versions of the actor.
  • The summary row says how many postings were new and how many closed since the last run.
  • Run it from a schedule (daily is usual). Two runs in a row over the same board return no jobs the second time.
  • Jobs are matched by the vendor's job id. A job that the company deletes and posts again gets a new id and counts as new.
  • If you change stateStoreName, the actor starts a new record and records a new starting point.

Send results to Make, Zapier, n8n, Slack or a CRM

Start the actor or a saved task on a schedule. Trigger the next step on the finished run: in Make the Apify module "Watch Actor Runs", in Zapier the Apify trigger for a finished actor run, in n8n the Apify node. Read the rows with "Get Dataset Items" using the run's dataset ID. For your own endpoint, open the actor's Integrations tab and add an HTTP webhook on "Run succeeded". The payload contains resource.defaultDatasetId, which you can read at https://api.apify.com/v2/datasets/<datasetId>/items?format=json. Add &view=jobs for the job columns only, or &format=csv for a spreadsheet.

Pricing

You pay per job saved (event job), and the price is on the Pricing tab. In hiring-signals mode you pay the same job event once per company domain with a board, and nothing for a domain with none. The summary rows and error rows are free and do not count against maxItems. So is a job saved without the description you asked for (descriptionError set), and a bare id found on more than one vendor is not saved or charged at all. A run that finds no new jobs costs only the start of the run. When a run reaches the maximum cost you set, it stops and keeps what was saved, and it never saves a job the remaining budget cannot pay for. If the platform migrates or restarts a run, the run picks up its own record of what it saved (RUN_STATE in the key-value store) and does not save or charge those jobs again.

The actor calls only the public job-board APIs that the vendors publish for showing a company's own open jobs on its website:

  • Greenhouse: boards-api.greenhouse.io/v1/boards/{board}/jobs
  • Lever: api.lever.co/v0/postings/{company} (and the EU site api.eu.lever.co for a company not found on the global site)
  • Ashby: api.ashbyhq.com/posting-api/job-board/{board}
  • SmartRecruiters: api.smartrecruiters.com/v1/companies/{company}/postings
  • Workday: {tenant}.{wdN}.myworkdayjobs.com/wday/cxs/{tenant}/{site}/jobs (the JSON that the career site's own pages load) and .../job/{path} for a description

These need no login or key. The actor sends one request at a time with a pause between requests, retries politely, and honours Retry-After. In jobs mode it does not open company websites, career pages behind a login, or any page that is not one of these APIs. In hiring-signals mode it also makes up to four plain GET requests per domain for the company's own public careers page and robots.txt, never past a login or a block, and it follows robots.txt. It reads nothing from LinkedIn, Indeed or other job boards.

A company that runs its own careers site on top of one of these vendors usually still has a board on the vendor. Use the vendor address (for example boards.greenhouse.io/stripe for Stripe's jobs page) and not the company's own address, which the actor cannot read.

Personal data

Job postings are company information. The actor does not output the names or e-mail addresses of recruiters or hiring managers, even where a vendor's API returns them (SmartRecruiters returns a posting creator on some postings, and the actor ignores it). Descriptions sometimes contain a recruiter's e-mail address, and every address is replaced with [email removed]. Names written inside a description text are not detected.

When a run fails, an error report with the error message and the shape of the input (which fields are set, how long the lists are), never your input values or your results, is sent to the developer to fix bugs.

Limits

  • A vendor's API shows what the company chose to publish. A job that is not on the public board is not in the feed. Ashby jobs marked unlisted are left out.
  • Posting dates mean "first published" on Greenhouse, Lever, Ashby and SmartRecruiters (Workday: see "Workday career sites"), and Lever and Ashby give no update time, so an edited job does not move.
  • Salary appears only where the company turned it on. Greenhouse gives no pay period. SmartRecruiters gives no salary at all.
  • A bare id looked up with ats set to auto takes the vendor that has open jobs under that name. If two vendors have jobs under it, nothing is saved for it, and if a vendor does not answer, the entry fails rather than guessing. One vendor with jobs can still be a different company of the same name, so name the vendor to be sure.
  • Hiring-signals mode needs the careers page to link a board, or the domain's name to be the board id. A company with its own applicant system, a careers page that builds its links in the browser, or a board id unlike the domain's name gets a free row with no board. Pass its board address in companies in jobs mode instead.
  • A name-guess match is a guess that passed a check. The matchedBy field says which rows are guesses, so keep only careers-page rows if you want none.
  • Greenhouse boards on the EU site (boards.eu.greenhouse.io) are not read yet and get an invalid row.
  • SmartRecruiters answers with an empty list both for a company it does not know and for one with no open jobs, so both give a not found row.
  • SmartRecruiters descriptions need one extra request per saved job, so a run over a big SmartRecruiters board with descriptions is slower. Use includeDescription: "none" for counts and titles.
  • A full run is as fast as the vendors answer. Greenhouse and Lever send a whole board in one response (several MB for the largest boards), and the actor waits a short time between requests.
  • The actor stops with an error row for a company when its board comes back in a format the actor does not read, before saving anything from it, and the other companies continue.

If this actor saved you time, a short review on the actor page helps other people find it.

More from Solid Fetch

Did this help? A review on this page, or an issue saying what is missing, helps other people find it.