ATS Jobs API - Workday, Greenhouse, Lever, Ashby job scraper avatar

ATS Jobs API - Workday, Greenhouse, Lever, Ashby job scraper

Pricing

from $1.20 / 1,000 job postings

Go to Apify Store
ATS Jobs API - Workday, Greenhouse, Lever, Ashby job scraper

ATS Jobs API - Workday, Greenhouse, Lever, Ashby job scraper

Extract every public job posting from company career pages. Auto-detects Workday, Greenhouse, Lever, Ashby, SmartRecruiters, Workable and Recruitee, normalizes jobs to one schema (salary, location, remote, Markdown description) and flags new / updated / closed jobs between runs.

Pricing

from $1.20 / 1,000 job postings

Rating

0.0

(0)

Developer

WEIGUO HAN

WEIGUO HAN

Maintained by Community

Actor stats

0

Bookmarked

2

Total users

1

Monthly active users

4 days ago

Last modified

Categories

Share

ATS Jobs API - Workday, Greenhouse, Lever, Ashby, SmartRecruiters, Workable & Recruitee job scraper (no API key)

Extract every public job posting from any company's careers page in one run. Give the Actor a careers URL, a company domain or an ats:slug pair; it auto-detects the applicant tracking system (ATS), calls the vendor's official public JSON endpoint (no HTML scraping, no login, no API key), and returns clean, normalized jobs: title, locations, remote flag, department, employment type, posted/updated dates, salary range when published, and the full description as Markdown and plain text.

Run it on a schedule and it also tells you what changed since last time: every job is labelled new, updated or unchanged, and jobs that disappeared are emitted as closed. That makes it a drop-in hiring-signal feed for sales prospecting, recruiting intelligence, job boards, salary research and AI agents.

Pricing: $1.20 per 1,000 jobs (pay per event, see below) - across all 7 ATS platforms, including Workday.

Why this Actor

This ActorTypical single-ATS scrapers
ATS coverage7: Workday, Greenhouse, Lever, Ashby, SmartRecruiters, Workable, Recruitee1-3, usually without Workday
InputCareers URL, company domain or greenhouse:gitlab style slugs - ATS is auto-detectedYou must know the board token
Change detectionnew / updated / closed out of the box (fingerprints in a key-value store)None
SalaryNormalized salaryMin/Max/Currency/Period from Ashby, Lever, Recruitee and Greenhouse pay rangesRaw or missing
DescriptionMarkdown and plain textHTML
Data sourceVendor-documented public job-board APIs + Workday's own career-site JSONRendered pages / pre-scraped caches
Politeness<= 2 requests/s per host, exponential backoff, Retry-After, robots.txtVaries

Supported ATS platforms and the endpoints used

All sources are public, read-only, unauthenticated endpoints that the vendors provide for displaying jobs on third-party sites.

ATSPublic endpoint usedPaginationVendor documentation
GreenhouseGET https://boards-api.greenhouse.io/v1/boards/{board_token}/jobs?content=true (EU: boards-api.eu.greenhouse.io)single response (page fallback when meta.total is larger)Greenhouse Job Board API - "Job Board data is publicly available, so authentication is not required for any GET endpoints"
LeverGET https://api.lever.co/v0/postings/{site}?mode=json&skip={n}&limit=100 (EU: api.eu.lever.co)skip / limitLever Postings API
AshbyGET https://api.ashbyhq.com/posting-api/job-board/{board}?includeCompensation=truesingle responseAshby Public Job Posting API
SmartRecruitersGET https://api.smartrecruiters.com/v1/companies/{company}/postings?limit=100&offset={n} + GET .../postings/{id} for the descriptionoffset / totalFoundSmartRecruiters Posting API
WorkableGET https://apply.workable.com/api/v1/widget/accounts/{subdomain}?details=truesingle responseWorkable job widget / JSON feed
RecruiteeGET https://{company}.recruitee.com/api/offers/single responseRecruitee Careers Site API - token required from 2027-02-10 (optional input provided)
WorkdayPOST https://{tenant}.wd{N}.myworkdayjobs.com/wday/cxs/{tenant}/{site}/jobs ({"appliedFacets":{},"limit":20,"offset":n,"searchText":""}) + GET .../wday/cxs/{tenant}/{site}{externalPath} for the descriptionoffset / total, 20 per pageWorkday does not document this endpoint; it is the employer career site's own anonymous JSON API used by every Workday careers page (community write-up). The employer host's robots.txt is honoured.

How to scrape jobs from a careers page (tutorial)

  1. Open the Actor, paste one or more entries into Companies. Any of these work:
    • a careers URL: https://boards.greenhouse.io/gitlab, https://jobs.lever.co/palantir, https://jobs.ashbyhq.com/ashby, https://jobs.smartrecruiters.com/BoschGroup, https://apply.workable.com/blueground, https://bunq.recruitee.com, https://nvidia.wd5.myworkdayjobs.com/NVIDIAExternalCareerSite
    • an explicit pair: greenhouse:gitlab, lever:palantir, ashby:ashby, smartrecruiters:BoschGroup, workable:blueground, recruitee:bunq
    • a bare domain or name: gitlab.com - the Actor scans the company's /careers and /jobs pages for ATS links and probes each vendor API with the domain label as a slug. Successful detections are cached for 7 days.
  2. Optionally set Max jobs per company, Title keywords, Locations, Remote only or Posted after.
  3. Keep Detect changes on if you will run the Actor repeatedly (daily schedule, webhook, integration).
  4. Click Start. Results land in the dataset (table / JSON / CSV / Excel) and a per-company summary is stored under the SUMMARY key of the run's key-value store.

Input example

{
"companies": [
"https://boards.greenhouse.io/gitlab",
"lever:palantir",
"https://nvidia.wd5.myworkdayjobs.com/NVIDIAExternalCareerSite",
"gitlab.com"
],
"includeDescription": true,
"keywords": ["engineer", "data"],
"remoteOnly": false,
"detectChanges": true,
"emitUnchanged": false,
"maxJobsPerCompany": 0
}

Output example (one dataset item)

{
"id": "greenhouse:gitlab:8860302002",
"jobId": "8860302002",
"title": "Account Executive - France",
"company": "GitLab",
"companySlug": "gitlab",
"ats": "greenhouse",
"url": "https://job-boards.greenhouse.io/gitlab/jobs/8860302002",
"applyUrl": "https://job-boards.greenhouse.io/gitlab/jobs/8860302002",
"location": "Remote, France",
"locations": ["Remote, France", "France"],
"remote": true,
"workplaceType": "remote",
"department": "EMEA - Commercial",
"team": null,
"employmentType": null,
"postedAt": "2026-10-02T15:31:50Z",
"updatedAt": "2026-10-02T15:31:50Z",
"descriptionMarkdown": "GitLab is the intelligent orchestration platform...",
"descriptionText": "GitLab is the intelligent orchestration platform...",
"salaryMin": null,
"salaryMax": null,
"salaryCurrency": null,
"salaryPeriod": null,
"language": "en",
"change": "new",
"firstSeenAt": "2026-10-07T05:30:12Z",
"scrapedAt": "2026-10-07T05:30:12Z",
"fingerprint": "6c4d2b1e9a0f7c3d5e8b",
"raw": { "...": "untouched vendor fields (disable with includeRaw=false)" }
}

An Ashby job with published compensation carries "salaryMin": 110000, "salaryMax": 185000, "salaryCurrency": "EUR", "salaryPeriod": "year".

A closed job (present in the previous run, gone now) is a small free record:

{ "id": "lever:palantir:6ed76ce8-...", "title": "Administrative Business Partner", "company": "palantir", "ats": "lever", "url": "https://jobs.lever.co/palantir/6ed76ce8-...", "change": "closed", "firstSeenAt": "2026-09-01T08:00:00Z", "lastSeenAt": "2026-10-06T08:00:00Z", "closedAt": "2026-10-07T08:00:00Z" }

Change detection (new / updated / closed jobs)

With detectChanges on, the Actor keeps a fingerprint (title, locations, department, employment type, salary, vendor updatedAt, description hash) of every job per board in the named key-value store stateStoreName (default ats-jobs-api-state). On the next run each job is labelled:

  • new - not seen before (firstSeenAt is set now)
  • updated - fingerprint changed
  • unchanged - identical (set emitUnchanged: false to skip and not pay for these)
  • closed - present last time, missing now (free, emitClosed)

The description hash is part of the fingerprint, so toggling includeDescription between runs reports every job as updated once. closed is only computed when the whole board was listed in both runs; if a run is truncated by maxJobsPerCompany, filters, a rate limit or a vendor error, the state is merged instead of replaced and no false "closed" events are produced. Use different stateStoreName values for independent monitors.

Pricing (pay per event)

EventWhat it meansPrice
jobOne job record written to the dataset (new, updated or unchanged)$0.0012 ($1.20 / 1,000 jobs)
company-detectedATS auto-detected from a bare domain or company name (careers-page scan + API probing). Not charged for ATS URLs or ats:slug inputs$0.01 per company
Actor startApify's standard synthetic start event at the platform defaultplatform default

Closed-job records, filtered-out jobs and failed boards are never charged. Proposed volume discounts for higher Apify plans: job $0.0010 (Silver) / $0.0008 (Gold); company-detected $0.008 / $0.006.

Cost examples

  • Monitor 50 companies via their careers URLs, ~2,000 open jobs, daily run with emitUnchanged: false: ~40 new/updated jobs a day -> about $0.05 per day (plus the start event).
  • One-off export of 10 Greenhouse/Lever/Workday boards with 5,000 jobs including descriptions -> $6.00.
  • Lead list of 200 bare domains, auto-detected once (then cached 7 days), 8,000 jobs -> $2.00 detection + $9.60 jobs = $11.60.

Use cases

  • Sales & lead generation - "who is hiring for X right now" signals for outbound (export to CSV, Google Sheets, HubSpot via Zapier/Make/n8n).
  • Recruiting & talent intelligence - track competitor headcount plans, new offices, remote policy shifts.
  • Job boards & aggregators - one normalized feed instead of seven integrations.
  • Salary research - Ashby, Lever, Recruitee and Greenhouse pay-transparency ranges in one schema.
  • AI agents / MCP - "What is NVIDIA hiring for in Munich?" becomes one Actor call; Markdown descriptions are ready for RAG.

Limitations and good-to-know

  • Workday: the endpoint is undocumented by Workday (stable for years, but it can change). Descriptions need one request per job; at the polite 2 req/s limit a 2,000-job tenant takes ~17 minutes - set includeDescription: false for fast monitoring. Workday boards must be given as a careers URL (tenant + site cannot be guessed from a domain).
  • SmartRecruiters descriptions also need one request per job. Its API host's robots.txt disallows all generic crawlers while the vendor's Posting API documentation permits anonymous reads; the default robotsPolicy: vendor-api follows the API terms, strict disables SmartRecruiters.
  • Recruitee announced an X-Careers-Sites-Token requirement from 2027-02-10; paste your token into recruiteeCareersSiteToken once it is enforced.
  • Auto-detection from a domain works when the company's /careers or /jobs page links to its ATS or when its slug equals the domain label (gitlab.com -> gitlab). JavaScript-only careers pages with a non-obvious slug cannot be detected - pass the careers URL instead.
  • remote combines vendor flags with a keyword heuristic on location/title; employmentType keeps the vendor's wording (e.g. Full-time, Full time, FullTime is mapped).
  • Salary is only present when the employer publishes it through the ATS.
  • No personal data is collected: the Actor reads job advertisements only (recruiter e-mails in Recruitee's mailbox_email are kept in raw only if you leave includeRaw on).
  • The Actor stops a board on persistent 429, 401/403 or robots.txt disallow and reports it in the run summary instead of hammering the host.
  • One board never fails the run. Each company has its own time budget (maxSecondsPerCompany, default 180 s) and error isolation: a slow, blocked or broken board is cut off with a warning in SUMMARY, its jobs fetched so far are kept, and the other companies finish normally. The run only fails when every board failed and nothing was written.
  • Change-detection state lives in the named key-value store stateStoreName (default ats-jobs-api-state). If that store cannot be opened the Actor falls back to the run's own store and says so in the log / SUMMARY.stateStore instead of failing.

FAQ

Do I need API keys or accounts? No. Every source is a public, unauthenticated endpoint.

Is scraping these job boards allowed? Greenhouse, Lever, Ashby, SmartRecruiters, Workable and Recruitee publish these endpoints precisely so third parties can display the jobs. Workday career sites expose the same JSON their own page uses; the Actor honours each employer host's robots.txt and sends at most 2 requests/second. You remain responsible for how you use the data.

How do I get only new jobs? Keep detectChanges: true and set emitUnchanged: false; schedule the Actor daily and connect a webhook or integration.

Can I pass a list of company names? Yes ("Acme" -> tries acme.com), but detection is best-effort; careers URLs are always reliable and never charged for detection.

Why are there fewer jobs than the careers page shows? Ashby only exposes listed jobs; Greenhouse boards may hide internal postings; filters and maxJobsPerCompany reduce the count - check SUMMARY in the key-value store for totalReported per company.

Can it run locally? Yes: apify run with the Apify CLI, or python -m src inside the venv. Unit tests: pytest.

Changelog

  • 1.0.1 (2026-10-09) - per-company time budget (maxSecondsPerCompany) and error isolation; named state store falls back to the run store instead of crashing; default example boards are now GitLab (Greenhouse), Palantir (Lever) and Ashby (Ashby) so the 20-job sample finishes in well under a minute; partial failures are summarised in the status message.
  • 1.0.0 - initial release: 7 ATS adapters, auto-detection, change detection, pay-per-event charging.