SFOLN

Catalogue / Company & lead intelligence

ATS Jobs Scraper – Greenhouse, Lever, Ashby

sfoln/ats-jobs-scraper · Jobs, Lead generation, Automation On request

Scrape open jobs from company career sites on Greenhouse, Lever, Ashby, SmartRecruiters, Recruitee, Personio, Teamtailor, Breezy, BambooHR, Workable and Workday: one row per job with salary, location, remote type and seniority. $1 per 1,000 jobs + $0.002 per board.

About

What it does

Scrape open jobs from company career sites in one normalized schema: give job board URLs, careers pages or <ats>:<token> entries and get one row per open job from Greenhouse, Lever, Ashby, SmartRecruiters, Recruitee, Personio, Teamtailor, Breezy HR, BambooHR, Workable and Workday: title, department and team, locations, remote / hybrid / onsite, employment type, a seniority guess, the salary range normalized to min, max, currency and period, posting dates and apply links. Built for recruiters and staffing agencies, job boards and aggregators, sales teams tracking hiring, market researchers and AI agents. It reads the public job-board APIs these ATS publish for career sites, with no login.

Price

Price per delivered record

EventPricePer 1,000Charged for
Job posting
job
$0.001$1 / 1,000One open job delivered as a normalized dataset row (title, department, locations, workplace type, employment type, seniority, salary, dates, URLs).
Job board
job-board
$0.002$2 / 1,000One company job board that delivered at least one job in this run (board detection and reading).

Apify free plan: up to 10 items per run. Paid plans have no per-run cap; set maxTotalChargeUsd to cap spend.

Run

Call it from code, an agent or the Console

This API is private on Apify for now. Email us and we will publish it for you, usually within two working days. The calls below work once it is public. contact@sfoln.com

curl · synchronous run, rows in the response
curl -X POST "https://api.apify.com/v2/actors/sfoln~ats-jobs-scraper/run-sync-get-dataset-items" \
  -H "Authorization: Bearer $APIFY_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"companies":["https://boards.greenhouse.io/stripe","https://jobs.lever.co/palantir"],"maxJobsPerCompany":5}'
mcp.json · hosted Apify MCP server
{
  "mcpServers": {
    "apify": {
      "type": "http",
      "url": "https://mcp.apify.com?tools=sfoln/ats-jobs-scraper"
    }
  }
}
asynchronous run · for large batches
POST https://api.apify.com/v2/actors/sfoln~ats-jobs-scraper/runs?maxTotalChargeUsd=1
GET  https://api.apify.com/v2/actor-runs/{runId}            # until status is SUCCEEDED
GET  https://api.apify.com/v2/datasets/{defaultDatasetId}/items?format=json

Authenticate with an Apify API token. MCP clients can instead sign in with OAuth when prompted. Example prompt for an agent: List the open engineering jobs at Stripe (greenhouse:stripe) and Ramp (ashby:ramp) with their salary ranges and locations, then summarize what each company is hiring for.

Input

Input fields

Add companies as job board URLs, careers pages, websites or <ats>:<token>. Each company produces one dataset row per open job plus one summary record.

FieldTypeDefaultMeaning
companies *arrayrequiredCompanies (job boards, careers pages or websites)
One per line: a job board URL (https://boards.greenhouse.io/stripe, https://jobs.lever.co/palantir, https://jobs.ashbyhq.com/ramp, https://acme.recruitee.com, …), a careers page or company website (the job board is detected from its links and embeds), or <ats>:<token> such as greenhouse:stripe, lever:palantir, ashby:ramp, smartrecruiters:BoschGroup, recruitee:bunq, personio:acme, teamtailor:acme, breezy:acme, bamboohr:acme, workable:acme or workday:nvidia.wd5/NVIDIAExternalCareerSite. Duplicates are removed. Accounts on the Apify free plan can process up to 10 companies per run.
maxJobsPerCompanyinteger1000Max jobs per company
Job rows per company (1–10,000), newest first where the board allows it. Each delivered job is one charged event. Range 1–10000.
keywordsarray—Keywords
Keep only jobs whose title, department or team contains one of these words (whole words or word starts, so engineer also matches Engineering; keywords of up to 3 letters such as AI must be whole words). Filtered-out jobs are not charged.
locationsarray—Locations
Keep only jobs with a location containing one of these (city, region or country name). A 2-letter code matches the job's country (US, DE) or a state or province code (CA, NY); remote matches remote jobs.
departmentsarray—Departments
Keep only jobs whose department or team contains one of these (e.g. Engineering, Sales).
remoteOnlybooleanfalseRemote jobs only
Keep only jobs whose workplace type is remote.
postedSinceDaysinteger—Posted within the last N days
Keep only jobs posted within this many days. Jobs without a posting date are left out when this filter is set. Range 1–3650.
includeDescriptionbooleanfalseInclude job descriptions
Add the plain-text job description to each row (up to 20,000 characters). On SmartRecruiters, BambooHR and Workday this needs one extra request per job, so runs are slower.
onlyNewbooleanfalseOnly new jobs since my last run
Deliver only jobs your earlier runs of this Actor did not deliver for the same board (job alerts on a schedule). The memory is kept in the key-value store ats-jobs-scraper-memory of your account for 30 days; delete it to start over.
includeWorkdaybooleanfalseRead Workday career sites
Workday sites (*.myworkdayjobs.com) are read through an undocumented endpoint, at most 200 jobs per site and 1 request per second. Off by default: Workday entries are skipped (not charged).
guessBoardsbooleantrueGuess the board from the domain
When a careers page or website links to no supported job board, try the domain name as a board name on Greenhouse, Lever, Ashby, Recruitee, Personio, Teamtailor, Breezy, SmartRecruiters and BambooHR (summary records say detectedBy: slug_guess). Not tried for IP addresses or sites on hosting platforms (*.vercel.app, *.github.io, *.wordpress.com …).
maxCompaniesinteger1000Max companies
Safety cap on the number of companies processed in this run. Range 1–10000.
concurrencyinteger3Concurrency
How many companies are processed in parallel (1–8). Requests to each ATS are rate-limited for the whole run. Range 1–8.
itemTimeoutSecsinteger600Time limit per company (seconds)
Paging for one company stops cleanly shortly before this limit (60–3600). Range 60–3600.
includeRawbooleanfalseInclude raw upstream response
Attach the unmodified job object of the ATS to each job row (larger output).
Example input
{
  "companies": [
    "https://boards.greenhouse.io/stripe",
    "https://jobs.lever.co/palantir"
  ],
  "maxJobsPerCompany": 5
}

Output

One record per input item

Records land in the run's dataset; a synchronous call returns them directly. Each has a status (ok is charged; everything else is free) and a charged flag. The run's OUTPUT record totals items and charges.

FieldTypeMeaning
inputstring | objectInput
The company entry as given.
recordTypestringRecord type
job (one per delivered job, charged job) | summary (one per company; charged job-board when at least one job was delivered) e.g. "job"
positionintegerPosition
Job rows: position of the job within its company (1-based).
atsstring | nullATS
greenhouse | lever | ashby | smartrecruiters | recruitee | personio | teamtailor | breezy | bamboohr | workable | workday
companystring | nullCompany
Company name from the board, else the board token.
boardTokenstring | nullBoard token
The board's name on the ATS (subdomain, token or company identifier).
jobIdstringJob ID
The job's ID on the ATS.
titlestringJob title
departmentstring | nullDepartment
Top-level department (Greenhouse: the root of the department tree).
teamstring | nullTeam
Team or sub-department when the board has one.
locationsarrayLocations
countrystring | nullCountry
ISO 3166-1 alpha-2 code of the first location when the board gives or implies it.
workplaceTypestring | nullWorkplace type
remote | hybrid | onsite; null when the board does not say.
employmentTypestring | nullEmployment type
full_time | part_time | contract | temporary | internship | volunteer | other
senioritystring | nullSeniority (guess)
intern | entry | mid | senior | lead | manager | director | vp | executive, guessed from the title (else the board's experience level).
salaryobject | nullSalary
{ min, max, currency (ISO 4217), period (year | month | week | day | hour), text } when the board publishes pay; else null.
postedAtstring | nullPosted at
ISO 8601 UTC (Workday: the day, from its relative date).
updatedAtstring | nullUpdated at
ISO 8601 UTC when the board reports it.
urlstring | nullJob URL
applyUrlstring | nullApply URL
descriptionstring | nullDescription
Plain text, up to 20,000 characters; only with includeDescription.
boardUrlstring | nullBoard URL
Summary records only: the company's job board.
detectedBystring | nullDetected by
Summary records only: input (board given) | careers_page (linked from the page) | slug_guess (domain name tried on the ATS APIs).
otherBoardsarrayOther boards
Summary records only: further boards the careers page linked to.
jobsOnBoardinteger | nullJobs on board
Summary records only: open jobs on the board when the ATS reports a total.
resultsDeliveredintegerJobs delivered
Summary records only.
filteredOutintegerFiltered out
Summary records only: jobs dropped by your filters or by onlyNew (not charged).
duplicatesintegerDuplicates
Summary records only: jobs already delivered for another entry of this run (not charged).
stopReasonstringStop reason
Summary records only: end | limit | free_plan | budget | deadline | aborted | max_pages | empty_pages | error
statusstringStatus
ok | not_found | error | skipped_invalid | skipped_budget | skipped_free_plan | skipped_unmetered e.g. "ok"
chargedbooleanCharged
Job rows: charged job. Summary records: charged job-board when at least one job was delivered.
chargedEventstringCharged event
Pay-per-event event charged for this record (present only when charged).
chargedCountintegerEvents charged
Events actually charged when the charge came back short of the requested count (present only then; a warning explains it).
sourcestringSource
Upstream data source (ats).
fetchedAtstringFetched at
ISO timestamp.
errorobjectError
{ code, message, provider?, status? } when status is not ok. Actor codes: invalid_input, board_not_found, workday_not_enabled. Upstream codes: not_found (unknown board), http_error (see status), timeout, network, dns_not_found, too_large, invalid_json, unexpected_type, invalid_response. Codes set by the runtime: budget_exhausted, free_plan_limit, unmetered, aborted, charge_failed, record_too_large, invalid_record, dataset_push_failed.
warningsarrayWarnings
Notes about this record, e.g. a free-plan cap, the budget, or a page that could not be read.
Example record
{
  "input": "ashby:ramp",
  "recordType": "job",
  "position": 1,
  "ats": "ashby",
  "company": "ramp",
  "boardToken": "ramp",
  "jobId": "34413f8d-26bf-4bbc-8ade-eb309a0e2245",
  "title": "Security Engineer, Cloud",
  "department": "Engineering",
  "team": "Backend",
  "locations": [
    "New York, NY (HQ)",
    "Remote (Canada)",
    "Remote (US)",
    "Miami, FL"
  ],
  "country": "US",
  "workplaceType": "hybrid",
  "employmentType": "full_time",
  "seniority": null,
  "salary": {
    "min": 211400,
    "max": 290600,
    "currency": "USD",
    "period": "year",
    "text": "$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",
  "status": "ok",
  "charged": true,
  "chargedEvent": "job",
  "source": "ats",
  "fetchedAt": "2026-10-14T12:00:00.000Z"
}

FAQ

Questions

Which applicant tracking systems are supported?
Greenhouse, Lever (including EU boards), Ashby, SmartRecruiters, Recruitee, Personio, Teamtailor, Breezy HR, BambooHR, Workable (best effort: Workable throttles cloud IPs) and Workday (opt-in with includeWorkday). Give the board URL or `<ats>:<token>`; for a careers page or website the board is found from its links and embeds.
How are salaries normalized?
Every board's pay format (Greenhouse pay ranges in cents, Lever salary ranges, Ashby compensation tiers, Recruitee salary objects, Breezy salary text) becomes salary { min, max, currency, period, text } with ISO currency codes and a period of year, month, week, day or hour. Jobs whose board publishes no pay have salary: null.
How do I get only new jobs on a schedule?
Set onlyNew to true and schedule the Actor. Each run delivers (and charges) only jobs your earlier runs did not deliver for the same board; the memory lives in the key-value store ats-jobs-scraper-memory of your own account for 30 days.
Why is a company marked not_found?
No supported job board was found: the careers page links to an ATS this Actor does not read, or the board name is wrong. Give the board URL or `<ats>:<token>` directly. Not-found and empty boards are never charged.

Machine-readable

This page for agents