Career Site Job Feed - Live Workday, Greenhouse & 8 More ATS
Pricing
from $2.00 / 1,000 job results
Career Site Job Feed - Live Workday, Greenhouse & 8 More ATS
Read job postings live from the employer's own applicant tracking system at run time — no stored index, so a row cannot outlive the posting. 27,681 company boards across 10 platforms, one schema.
Pricing
from $2.00 / 1,000 job results
Rating
0.0
(0)
Developer
Sulle H
Maintained by CommunityActor stats
0
Bookmarked
2
Total users
1
Monthly active users
9 hours ago
Last modified
Categories
Share
Career Site Job Feed — Workday, Greenhouse & 27,000+ ATS Boards
Search live job postings straight from 27,681 company career sites in a single feed — 1.36 million openings across nine applicant tracking systems.
Most job APIs resell a stale aggregator index. This one reads each employer's own ATS at run time, so a row means the job is on that company's careers page right now, with a direct apply link rather than a redirect through a job board.
| Platform | Boards covered | Live postings |
|---|---|---|
| Workday | 2,658 | 595,820 |
| SmartRecruiters | 4,334 | 363,544 |
| Greenhouse | 5,578 | 179,052 |
| Workable | 1,710 | 77,339 |
| Lever | 1,917 | 63,622 |
| Ashby | 1,596 | 32,007 |
| Breezy HR | 2,800 | 47,301 |
| Personio | 5,172 | 41,685 |
| Rippling | 1,298 | 18,647 |
| Pinpoint | 618 | 18,235 |
| Total | 27,681 | 1,437,252 |
No login, no cookies, no proxies, no API keys. One normalized schema across all ten platforms.
Read at run time, not served from an index
There are two ways to build a job API, and the difference matters more than coverage counts do.
The aggregator way. Crawl thousands of career sites on a schedule, store the results, serve them from a database, enrich them. You get scale — the largest of these covers around 200,000 career sites — and extras like company metadata that only make sense once the data is warehoused. What you also get is a gap between the crawl and your query: a posting filled last Tuesday is still in the index until the next crawl notices, and "active in the last six months" is doing a lot of work in that sentence.
This Actor's way. When you run it, it calls each employer's own applicant tracking system and
returns what that board answers. There is no stored copy. A row exists because the company's
careers page listed it seconds ago, and scrapedAt on every row is the timestamp of that call.
Neither is better in the abstract. If you want the widest possible index and do not mind that some rows are stale, use an aggregator. If a wrong row costs you — a candidate applying to a filled role, a lead list of companies that stopped hiring, an alert that fires on something already gone — read live.
You can check this rather than believe it. Take any row, open its applyUrl, and the posting
is there. Run again a week later against the same company and the roles that closed are simply
absent, because nothing is holding a copy.
Freshness, measured rather than promised
Every job API claims it has no stale listings. Here is what is actually in these sources, sampled across the registry:
| Platform | Posted ≤ 90 days | Older than a year |
|---|---|---|
| Ashby | 83% | 2% |
| Greenhouse | 63% | 15% |
| Workable | 52% | 5% |
| SmartRecruiters (largest boards) | 50% | 50% |
Some employers — staffing agencies especially — leave requisitions open for years. Rather than pretend otherwise, this Actor defaults to postings from the last 90 days. A default run comes back with a median posting age of 6 days.
- Want everything, including long-open roles? Set
postedWithinDaysto a large number such as3650. - Want only this week? Set it to
7. - Postings with no publication date are always kept, because their age is unknown rather than old. That is most Workday rows — see the note below.
What a row looks like
{"provider": "smartrecruiters","companySlug": "sgs","company": "SGS","jobId": "744000143984409","title": "Assistant Consumer Safety Engineer (Electrical & Electronic)","location": "Senai, my","department": "Laboratory & Testing","employmentType": "Full-time","remote": false,"postedAt": "2026-08-18T05:52:19.533Z","applyUrl": "https://jobs.smartrecruiters.com/sgs/744000143984409","salary": null,"descriptionText": "...","scrapedAt": "2026-08-18T10:14:02.881Z"}
| Field | Description |
|---|---|
company / companySlug | Company name and its board identifier. Pass companySlug back in companies to watch one employer. |
provider | Which ATS the posting came from |
jobId | Stable per-posting id — use it to dedupe across runs |
title | Job title |
location | Location string as the employer published it |
department | Department or team, where the ATS exposes it |
employmentType | Full-time, contract, and so on, where exposed |
remote | Remote flag, where the ATS exposes it |
postedAt / updatedAt | ISO timestamps, or null when the source gives no date |
applyUrl | Direct application link on the employer's own site |
salary | Pay range as the employer wrote it. Only Breezy and Ashby publish this on their listing endpoints; null elsewhere. |
descriptionText | Full description as plain text, when includeDescription is on |
scrapedAt | When this row was read, ISO 8601 |
Input
{"keywords": ["engineer", "developer"],"excludeKeywords": ["senior", "staff"],"locations": ["berlin", "remote"],"postedWithinDays": 30,"maxBoards": 500,"maxJobs": 2000,"includeDescription": false}
| Option | What it does |
|---|---|
keywords | Keep titles containing any of these (case-insensitive). Empty = every job. |
excludeKeywords | Drop titles containing any of these. |
locations | Keep postings whose location matches any entry. |
remoteOnly | Keep only postings flagged remote by the ATS or with a remote-looking location. |
postedWithinDays | Freshness window. Defaults to 90. |
companies | Restrict to specific board slugs. Empty = the whole 27,681-board registry. |
providers | Restrict to greenhouse, personio, smartrecruiters, breezy, workday, lever, workable, ashby, rippling, pinpoint. |
maxBoards | How many boards to scan, largest employers first. |
maxJobs | Cap on returned rows, so a run costs what you expect. Defaults to 1,000 — clearing the box falls back to that, so type a large number (e.g. 500000) to sweep everything. |
includeDescription | Turn off for a much faster, lighter run. |
concurrency | Parallel requests. Higher is faster but likelier to hit ATS rate limits. |
What people use it for
A job board or newsletter. Run it on a schedule with your niche keywords and push straight into your own database. Every row carries an apply link to the employer, so your users never bounce through a third party.
Recruiting and sourcing. Ask which companies are hiring a given role right now: keywords: ["site reliability"], postedWithinDays: 14.
Market and competitive research. Headcount signals straight from the source. Restrict to one employer with companies: ["wd5:nvidia:nvidiaexternalcareersite"] and watch what they open.
Lead generation. A company posting five sales roles is a company with a budget. Filter on titles, keep company and applyUrl, and hand the list to your CRM.
Sending results somewhere
The dataset is standard Apify output, so the platform's own integrations do the delivery — Slack, Zapier, Make, a webhook, Google Sheets, or a direct API pull:
https://api.apify.com/v2/datasets/<datasetId>/items?clean=true&format=json
Pair it with an Apify Schedule to get a recurring feed. If you only want postings you have not seen before, use the companion Actor New Job Alerts, which remembers what it already delivered.
A note on Workday
Workday is where most large enterprises post, and it is the biggest single source here — Accenture, Walmart, CVS Health, Lowe's. Its public listing endpoint carries less than the others: Workday rows carry company, title, location, employment type, requisition id and apply link, and no department or description. Those live behind a per-posting request that a per-result price cannot cover.
Workday also reports posting age as text ("Posted Today", "Posted 30+ Days Ago"), so postedAt is exact when the age is exact and null when it is not — a missing date rather than an invented one.
Performance
A 300-board scan returns 3,628 engineer postings in 8 seconds with zero board failures, measured on Apify. Boards are scanned largest-first, so a capped run returns the most jobs per credit.
Raise maxBoards for wider coverage — the full registry holds 1,437,252 live postings across 27,681 boards.
Which fields each platform actually gives you
Ten applicant tracking systems, one schema — but they do not all publish the same things. These are measured from real runs, not from vendor documentation:
| Field | Workday | SmartRecruiters | Greenhouse | Workable | Lever | Ashby | Breezy | Personio | Pinpoint | Rippling |
|---|---|---|---|---|---|---|---|---|---|---|
| title, company, location | yes | yes | yes | yes | yes | yes | yes | yes | yes | yes |
| applyUrl | yes | yes | yes | yes | yes | yes | yes | yes | yes | yes |
| postedAt | partial | yes | yes | yes | yes | yes | yes | no | no | no |
| department | no | yes | yes | yes | yes | yes | yes | yes | yes | yes |
| employmentType | yes | yes | no | yes | yes | yes | yes | yes | yes | no |
| remote flag | no | yes | no | yes | inferred | yes | yes | inferred | yes | inferred |
| descriptionText | no | no | yes | yes | yes | yes | no | no | yes | no |
| salary | no | no | no | no | no | yes | yes | no | 37% | no |
Two consequences worth knowing before you build on this:
includeDescriptiondoes nothing for Workday, SmartRecruiters or Breezy rows. Their listing endpoints carry no description, and fetching one per posting would cost a request per row. Those rows returnnullhowever the option is set.- Workday's
postedAtis partial by design. It reports age as prose — "Posted Today", "Posted 30+ Days Ago" — so an exact age becomes a timestamp and a vague one becomesnull, rather than a made-up date. Date filters keep rows with no date rather than dropping them.
Live read versus an aggregator index
Most job APIs answer from a database they refreshed at some earlier point. This one calls each employer's ATS while your run is happening. The trade-off is honest in both directions:
| This Actor | Aggregator index | |
|---|---|---|
| Freshness | as of this second | as of their last crawl |
| Ghost jobs | gone the moment the employer closes it | linger until the next crawl |
| Apply link | the employer's own URL | often a redirect |
| Speed | seconds to minutes, bounded by the ATS | instant |
| Coverage | employers whose board we know | whatever they indexed |
If you need a million rows in one second, use an index. If you need rows that are true right now and apply links that go straight to the employer, read live.
Recipes
A daily job board feed. Schedule a run with postedWithinDays: 1 and your keywords, then push
the dataset into your database through Apify integrations. Deduplicate on
provider:companySlug:jobId, which is stable across runs.
Track one company's hiring over time. Put its slug in companies, raise maxJobsPerCompany,
and run weekly. Diffing jobId between runs shows what opened and what closed.
Find employers on a given platform. Set providers: ["workday"] and maxJobs high. The
companySlug column becomes a list of companies using that ATS — useful for technographic
segmentation as well as recruiting.
Watch a whole sector. Combine keywords with locations, raise maxBoards, and lower
maxJobsPerCompany so no single large employer dominates the result.
Troubleshooting
"I got fewer rows than maxJobs." Filters are applied after fetching, so a narrow keyword or
a short postedWithinDays can exhaust the boards before the cap. Raise maxBoards, widen the
window, or loosen keywords.
"One company dominates my results." Lower maxJobsPerCompany. It defaults to 25 for exactly
this reason.
"A board I expect is missing." It may not be in the registry, or it may have closed. The registry is re-validated monthly; boards that stop answering are dropped.
"Some rows have no description." See the field table above — five of the ten platforms do not publish descriptions on their listing endpoints.
If you only need one ATS
This Actor is built for breadth — it does not ask you which applicant tracking system a company uses, because the registry already knows. When you only care about one platform, a narrower Actor is cheaper to reason about and its input schema has fewer knobs:
| Actor | Coverage |
|---|---|
| Workday Jobs Scraper | 2,658 Workday boards — paste a careers URL |
| SmartRecruiters Jobs Scraper | 4,334 SmartRecruiters boards |
| Breezy HR Jobs Scraper | 2,800 Breezy boards, with pay ranges |
| Workable Jobs Scraper | 1,710 Workable boards, with full descriptions |
| Greenhouse Jobs Scraper | Any Greenhouse board by slug or URL, with full descriptions |
All of them emit the same row shape as this Actor, so datasets from any combination can be unioned without a mapping layer.
Use it as an API
Most people who rely on this Actor never open the Apify console after the first run — they call it from their own code and read the rows straight back. One request in, job rows out, no polling and no dataset id to chase:
curl -X POST "https://api.apify.com/v2/acts/starbright_overlap~ats-job-feed/run-sync-get-dataset-items?token=YOUR_TOKEN" \-H "Content-Type: application/json" \-d '{"keywords": ["engineer"], "locations": ["berlin"], "maxJobs": 200}'
import requestsrows = requests.post("https://api.apify.com/v2/acts/starbright_overlap~ats-job-feed/run-sync-get-dataset-items",params={"token": "YOUR_TOKEN"},json={"keywords": ["engineer"], "locations": ["berlin"], "maxJobs": 200},timeout=300,).json()for r in rows:print(r["company"], "—", r["title"], "—", r["applyUrl"])
const rows = await (await fetch('https://api.apify.com/v2/acts/starbright_overlap~ats-job-feed/run-sync-get-dataset-items?token=YOUR_TOKEN',{ method: 'POST', headers: { 'Content-Type': 'application/json' },body: JSON.stringify({"keywords": ["engineer"], "locations": ["berlin"], "maxJobs": 200}) },)).json();
Your token is at Settings → Integrations in the Apify console. Keep maxJobs set to what you
actually need — it is the cap that decides what the call costs.
Running it on a schedule instead
If you want the rows to arrive without asking, add a Schedule from the Actor page (Actions →
Schedule) and point a webhook at your endpoint. A daily schedule plus the postedWithinDays filter
is the usual setup for a job board or an alerting pipeline.
Pricing
Pay per result. You are charged per job row delivered; boards that fail or return nothing cost you nothing. Platform usage is included rather than billed on top, so the per-result price is the whole price.
How the registry stays current
Boards die and companies migrate between ATS vendors constantly. The bundled registry is re-validated every month — dead boards are dropped and posting counts refreshed — so runs do not waste time on employers that have moved on.
Notes
- The registry ships inside the Actor, so there is no setup and no external lookup at run time.
- Dead or rate-limited boards are skipped without aborting the run; the details land in a
FAILED_BOARDSrecord in the key-value store. - Set
includeDescription: falsewhen you only need titles and links — it is several times faster. - Job ids are stable per posting, so
provider:companySlug:jobIdmakes a reliable dedupe key.
Frequently asked
Is there a free Greenhouse / Workday / Lever jobs API? The underlying ATS endpoints are public and unauthenticated — anyone can call them. What is hard is knowing which companies exist on which platform, which is what the 27,681-board registry here is. If you only need one employer you already know, call the ATS directly; this Actor is for searching across all of them at once.
How is this different from a job aggregator API? Aggregators index job boards and resell that index, so postings arrive second-hand and often after the employer already filled the role. This reads each employer's ATS during the run. The apply link points at the company, not at a middleman.
Can I get every job from one specific company?
Yes. Put its board slug in companies and raise maxJobs. Slugs come back on every row as companySlug — for example databricks on Greenhouse, or wd5:nvidia:nvidiaexternalcareersite on Workday.
Which ATS platforms are covered?
Workday, Greenhouse, SmartRecruiters, Workable, Lever, Ashby, Breezy, Personio, Rippling and Pinpoint HR. Restrict with providers if you only want some.
Do I get salary data?
Sometimes, and the salary field says exactly when. Breezy and Ashby publish a pay range on their listing endpoints — "$70,000 – $85,000 / year", "$211.4K – $290.6K • Offers Equity" — and those come through as written. The other five platforms expose no salary field at all, so salary is null there rather than invented. Anything they mention inside the job text still turns up in descriptionText when includeDescription is on.
How fresh are the postings?
A default run has a median posting age of 6 days, because postedWithinDays defaults to 90. The measured age distribution per platform is in the freshness table above.
Can I run it on a schedule? Yes — add an Apify Schedule. If you want only postings you have not already seen, use the New Job Alerts Actor instead; it keeps a per-feed memory and returns just the new ones.
What happens if a company's board is down?
That board is skipped and the run continues. Failures are collected in a FAILED_BOARDS record so you can see exactly which ones and why.
Why do some rows have no department or description?
Different ATS platforms expose different fields. Workday's list endpoint, in particular, has no department, employment type or description. Rather than filling gaps with guesses, missing values come back as null.
Listed on AllMCPs — this Actor is also reachable as a remote MCP server at https://mcp.apify.com/?tools=starbright_overlap/ats-job-feed.
