ATS Jobs Aggregator: Greenhouse, Lever, Ashby & SR · Apify
ATS Jobs Aggregator: Greenhouse, Lever, Ashby, SmartRecruiters avatar

ATS Jobs Aggregator: Greenhouse, Lever, Ashby, SmartRecruiters

Pricing

Pay per event

Go to Apify Store
ATS Jobs Aggregator: Greenhouse, Lever, Ashby, SmartRecruiters

ATS Jobs Aggregator: Greenhouse, Lever, Ashby, SmartRecruiters

Pricing

Pay per event

Rating

0.0

(0)

Developer

David-Solal Chalmeau

David-Solal Chalmeau

Maintained by Community

Actor stats

0

Bookmarked

2

Total users

1

Monthly active users

16 days ago

Last modified

Categories

Features

Limited permissions

Share

ATS Jobs Aggregator: Greenhouse, Lever, Ashby & SmartRecruiters

Get up to maxJobsPerCompany open jobs per company (default 200), in one flat table, from each company's own public job-board API — totalJobs and truncated on every row tell you when a board was bigger than the cap. Give it a list of companies (ashby:ashby, greenhouse:stripe, lever:ro, smartrecruiters:smartrecruiters) and get one row per job with title, department, location, remote flag, employment type, posting dates, apply URL and a plain-text description. Built for job aggregators, market research, competitor hiring tracking and LLM agents that need clean structured rows rather than scraped HTML.

What you get per job

FieldNotes
company, atsThe company you asked for and the ATS it came from
jobIdThe board's own job id (stable across runs)
title, department, locationDepartment and location are only as precise as the ATS provides
remotetrue / false / null. null means the ATS does not publish remote status (Greenhouse) — it is never guessed
employmentTypee.g. Full-time, FullTime. Only some ATS provide it (null otherwise)
postedAt, updatedAtISO 8601, or null when the ATS does not publish the field (Lever/Ashby/SmartRecruiters have no updatedAt; Greenhouse has both)
applyUrl, jobUrljobUrl is always the board job page. applyUrl is a separate direct application link where the ATS publishes one (Lever, Ashby, SmartRecruiters); it is null for Greenhouse, whose public API has no separate apply endpoint — no invented links
totalJobs, truncatedtotalJobs is how many postings the ATS reports for that board; truncated: true means maxJobsPerCompany cut the board short — including when the cap stopped the SmartRecruiters fetch before your filters ran — so this run did not cover all of it
descriptionTextPlain text: HTML stripped, entities decoded (Greenhouse double-escapes part of its content, e.g.  ), max 2000 characters. null for SmartRecruiters (its public list endpoint carries no description)
sourceUrl, fetchedAtThe exact API URL that produced the row, and when it was fetched

Example input

{
"companies": ["ashby:ashby", "greenhouse:stripe", "lever:ro", "smartrecruiters:smartrecruiters"],
"keywords": ["engineer"],
"locations": ["Berlin", "Remote"],
"remoteOnly": false,
"maxJobsPerCompany": 200,
"maxCompanies": 20
}

companies accepts "ats:slug" strings or {"ats": "greenhouse", "slug": "stripe"} objects. Slugs are validated (letters/digits, ., _, -, max 64 chars) and deduplicated case-insensitively — the board APIs themselves are case-insensitive, so greenhouse:stripe and greenhouse:Stripe are one board, fetched and billed once. Keywords match the title or department (case-insensitive), locations match as substrings, and remoteOnly: true keeps only jobs the ATS explicitly marks remote.

Example output

Real rows from a run of the input above (descriptions truncated here for readability):

{
"company": "ashby",
"ats": "ashby",
"jobId": "7458d4e9-da2e-47bd-98cb-adfda43d42b2",
"title": "Engineering Manager - EU",
"department": "Engineering",
"location": "Remote - European Union",
"remote": true,
"employmentType": "FullTime",
"postedAt": "2024-03-04T14:29:08.532Z",
"updatedAt": null,
"applyUrl": "https://jobs.ashbyhq.com/ashby/7458d4e9-da2e-47bd-98cb-adfda43d42b2/application",
"jobUrl": "https://jobs.ashbyhq.com/ashby/7458d4e9-da2e-47bd-98cb-adfda43d42b2",
"descriptionText": "Hi 👋 I'm Colin https://uk.linkedin.com/in/colinhoweuk, Director of Engineering, Europe. How do you feel about software e...",
"sourceUrl": "https://api.ashbyhq.com/posting-api/job-board/ashby?includeCompensation=false",
"fetchedAt": "2026-10-28T05:26:19.043Z"
}
{
"company": "ro",
"ats": "lever",
"jobId": "3910baa9-f961-4380-ad0a-01deec07f7a2",
"title": "Analytics Engineer, Conversational Analytics",
"department": "Data",
"location": "New York, NY",
"remote": false,
"employmentType": "Full-time",
"postedAt": "2026-09-16T17:43:56.068Z",
"updatedAt": null,
"applyUrl": "https://jobs.lever.co/ro/3910baa9-f961-4380-ad0a-01deec07f7a2/apply",
"jobUrl": "https://jobs.lever.co/ro/3910baa9-f961-4380-ad0a-01deec07f7a2",
"descriptionText": "Join Tech @ Ro to build the future of healthcare, from the g...",
"sourceUrl": "https://api.lever.co/v0/postings/ro?mode=json",
"fetchedAt": "2026-10-28T05:26:19.065Z"
}

Row-level totalJobs / truncated are shown on every delivered row but omitted from the two samples above for readability.

A company that cannot be fetched yields one row instead of jobs, and the run continues:

{
"company": "this-board-does-not-exist-xyz123",
"ats": "greenhouse",
"error": "BOARD_NOT_FOUND",
"message": "Greenhouse board not found: this-board-does-not-exist-xyz123"
}

error is BOARD_NOT_FOUND (no such board), UPSTREAM_ERROR (ATS returned an error or unusable payload), INVALID_COMPANY (unknown ATS name, malformed or traversal-looking slug) or SKIPPED_MAX_COMPANIES (valid company beyond your maxCompanies — one free row per board so you can see exactly what was left out).

How to find a company's slug

Open the company's careers page and read the URL: boards.greenhouse.io/<slug>, jobs.lever.co/<slug>, jobs.ashbyhq.com/<slug>, jobs.smartrecruiters.com/<slug>. For companies that host the board on their own domain (e.g. Stripe's stripe.com/jobs), the slug is the Greenhouse/Lever identifier, usually visible in the embedded job-board iframe URL.

Pricing

Pay per event: a small start fee per run plus $0.0018 per delivered job row. Companies whose board is missing or fails, invalid company inputs, and jobs removed by your filters are free — you are only charged for rows you actually receive. If your spending limit is reached the Actor stops cleanly instead of continuing unpaid. Current prices are shown on this page.

Limits and honest notes

  • Sources: the official public job-board APIs of Greenhouse, Lever, Ashby and SmartRecruiters — the endpoints each vendor publishes for embedding a company's board on its own site. No login, no CAPTCHA or anti-bot bypass, no proxies.
  • Companies are user-supplied. The Actor only reads boards you name; it does not discover or crawl companies on its own.
  • No personal data beyond the posting. Rows contain only what the job posting publishes. No recruiter names, emails or phone numbers are extracted, even when a description mentions them.
  • Hard caps: at most 100 companies and 1000 jobs per company per run (defaults 20 and 200). For Greenhouse, Lever and Ashby the whole board arrives in one response, so the cap is applied after your filters and maxJobsPerCompany: 200 means at most 200 matching jobs. For SmartRecruiters the same cap also bounds how many postings are fetched while it paginates (100 per request), so a highly selective filter on a large SmartRecruiters board can return fewer rows than the cap — raise maxJobsPerCompany there if you need deeper coverage.
  • Truncation is visible, never silent. Every job row carries totalJobs (postings the ATS reports for the board) and truncated (true when the cap cut that board short), and the run's status message names how many boards were truncated. A live Stripe Greenhouse board is ~720 jobs, so with the default maxJobsPerCompany: 200 you get 200 rows flagged truncated: true — raise the cap to fetch the rest. Filtered boards are covered too: if the cap stopped the SmartRecruiters fetch, the row(s) that did match are still stamped truncated: true (a 5025-posting board filtered down to 21 rows says so on every row) — truncated: false means your run genuinely covered the whole board.
  • Companies beyond maxCompanies are reported, not dropped silently. Each one yields a single free row with error: "SKIPPED_MAX_COMPANIES", and the count appears in the status message. Reorder companies or raise maxCompanies (hard limit 100) to include them.
  • Field coverage differs by ATS. Greenhouse publishes no remote flag and no employment type, so those are null, and it has no separate direct-apply URL (applyUrl is null, jobUrl is the board page). Lever publishes no description HTML update time; Ashby and Lever publish no updatedAt. SmartRecruiters' public list endpoint has no description, so descriptionText is null there.
  • remoteOnly: true cannot match Greenhouse rows — Greenhouse never publishes remote status, so every Greenhouse job is excluded. That is counted and reported in the run status message (it is not a silent empty result); drop remoteOnly or use keywords/locations for those boards.
  • SmartRecruiters paginates 100 postings per request, up to your maxJobsPerCompany. Its unknown-company response is an empty list rather than an error, so a typo in a SmartRecruiters slug yields no rows and no error row.
  • Descriptions are truncated at 2000 characters to keep rows small; the full posting is always at jobUrl.
  • Politeness: boards are fetched with concurrency 3 and retried with exponential backoff (500 ms, then 1 s) on HTTP 429/5xx. SmartRecruiters pagination (100 postings per request, up to your maxJobsPerCompany) waits 250 ms between pages instead of hitting the API back-to-back. Each board is fetched at most a handful of times per run.
  • Listings change constantly; fetchedAt tells you exactly when a row was read.