Career Site Jobs API — Greenhouse, Lever, Ashby & 10+ ATS avatar

Career Site Jobs API — Greenhouse, Lever, Ashby & 10+ ATS

Pricing

from $1.00 / 1,000 job posting returneds

Go to Apify Store
Career Site Jobs API — Greenhouse, Lever, Ashby & 10+ ATS

Career Site Jobs API — Greenhouse, Lever, Ashby & 10+ ATS

Scrape job postings from any company career page. One Actor covers Greenhouse, Lever, Ashby, Workable, Recruitee, SmartRecruiters, Teamtailor, Personio, Breezy, JazzHR and Workday, returning normalized JSON: title, location, remote flag, salary, apply URL and full description.

Pricing

from $1.00 / 1,000 job posting returneds

Rating

0.0

(0)

Developer

Insight Solutions

Insight Solutions

Maintained by Community

Actor stats

0

Bookmarked

2

Total users

1

Monthly active users

a day ago

Last modified

Share

Scrape job postings from any company career page. Give this Actor a list of career boards — or just company domains — and get back every open role as normalized JSON: title, location, remote flag, salary, posted date, apply URL and full description, 27 fields in one shape across 11 applicant tracking systems.

Data comes straight from each ATS's own public job feed, so runs finish in seconds. No browser, no proxy, no API key, no login. $2 per 1,000 job postings, failed boards are free, and a run that returns no jobs costs nothing at all.

Try it in 30 seconds

{
"boards": ["greenhouse:stripe", "ashby:ramp", "vandebron.nl"],
"includeDescription": true,
"maxJobsTotal": 100
}

Two of those are explicit boards; vandebron.nl is a bare domain the Actor resolves on its own, then remembers for later runs.

Supported ATS platforms

Every serious ATS publishes its customers' open roles on a public, unauthenticated endpoint — that is how postings reach Google and the aggregators. Each vendor uses a different URL, response shape and name for "location". This Actor reads those endpoints directly and flattens them into one schema.

ATSBoard identifierNotes
Greenhousegreenhouse:stripeFull descriptions, departments, offices, structured pay ranges
Leverlever:leverdemoFull descriptions, commitment, workplace type, salary range
Ashbyashby:rampFull descriptions, department/team, compensation tiers, remote flag
Workableworkable:lystDescription, requirements, benefits, multi-location, telecommuting flag
Recruiteerecruitee:vandebronDescription + requirements, remote/hybrid/on-site, salary when published
SmartRecruiterssmartrecruiters:VisaPaginated postings plus the full public job ad
Teamtailorteamtailor:oatlyJSON Feed + JobPosting data: description, locations, salary, employment type
Personio (beta)personio:personioXML feed, titled sections, offices. Personio rate-limits some feeds from cloud IPs (HTTP 429); those return a free diagnostic row
Breezy HRbreezy:new-incentivesStable feed, but it carries no posting body — descriptionText is null
JazzHR (beta)jazzhr:company3Public board page only — title, location, department, apply link. No unauthenticated feed exists, so no description
Workdayworkday:nvidia.wd5.myworkdayjobs.com/NVIDIAExternalCareerSiteFull descriptions, locations, requisition IDs. Verified live; needs the board host and site name, not a short slug

One bound worth knowing. Workday and SmartRecruiters serve each posting body from a separate document, so both adapters fetch descriptions for the first 250 postings per board. Beyond that the posting is still returned with descriptionText: null; split a large board across runs by postedAfter if you need every body. Every other platform ships the description in the board response.

Use cases

  • Build a jobs board or aggregator — pull 500 companies nightly into one consistent schema instead of maintaining eleven parsers.
  • Track who is hiring for a rolelocationFilter plus remoteOnly returns every open remote role on a watchlist, apply URL included.
  • Enrich a CRM or lead list with hiring data — pass bare company domains straight from your account list and let auto-detection find the board.
  • Benchmark salaries and compensationsalaryMin/salaryMax/salaryCurrency come only from structured ATS compensation data, never parsed out of prose.
  • Feed a recruiting or job-matching AI agent — one JSON object in, one flat array out, over the Apify API or MCP.
  • Seed a database before change detection — pull full boards here, then hand the watchlist to New Job Postings Monitor.

How it compares

  • One Actor for 11 ATS platforms, where most career-board scrapers cover three to six. One input format, one output schema, one thing to maintain.
  • Auto-detection from a bare domain — pass stripe.com instead of hunting for a board slug.
  • Charge-on-success, written into the pricing table. A board that 404s produces a free diagnostic row. A run that delivers zero postings finishes FAILED with the reason in its status message — never a green run containing an apology — and costs nothing, start fee included.
  • Recruiter contact details stripped from every posting body, and robots.txt honoured on every HTML page fetched.

We are not the cheapest per row in this category and will not pretend otherwise. We are the one that covers the whole field in a single schema and does not bill you for failures.

Input reference

FieldTypeDefaultWhat it does
boards (required)array of strings["greenhouse:stripe","ashby:ramp"]One entry per company: ats:slug, a career-page URL, or a bare domain for auto-detection
atsPlatformsmulti-selectallRestricts which platforms auto-detection considers. Ignored for explicit ats:slug entries
includeDescriptionbooleantrueTurn off for a much smaller, faster result
descriptionFormattext | markdown | htmltextHow the posting body is rendered
locationFilterarray of stringsnoneCase-insensitive substring match against the location
remoteOnlybooleanfalseKeep only postings the ATS flags remote, or whose workplace-type or location text says remote. Job titles are never used for this
postedAfterdatenoneKeep postings first published on or after this date. Postings with no published date are kept
maxJobsPerBoardinteger1000Per-board ceiling
maxJobsTotalinteger5000Whole-run ceiling. The run stops cleanly; you are not charged for jobs not returned
maxRunSecsinteger240Whole-run wall-clock budget. When reached the Actor stops fetching, keeps what it has, and says so
requestTimeoutSecsinteger20Per-request timeout. A URL is abandoned once its attempts have used twice that, or 40 seconds, whichever is longer
maxConcurrencyinteger6Boards fetched in parallel. Never more than one request at a time to any single host

Finding a board slug: look at where the Apply button goes — the slug is the identifier in that URL (jobs.lever.co/leverdemo/…lever:leverdemo, acme.breezy.hr/p/…breezy:acme, acme.wd5.myworkdayjobs.com/en-US/SiteNameworkday:acme.wd5.myworkdayjobs.com/SiteName). You can also paste the whole URL, or pass the bare domain.

Output reference

One row per posting, always the same keys in the same order:

{
"jobId": "8077887", // stable ID inside its ATS
"atsPlatform": "greenhouse", "boardSlug": "stripe", "companyName": "Stripe",
"companyDomain": "stripe.com", // the employer's own domain — never the ATS host, never a guess
"title": "Account Executive, Bridge", "department": "Product Sales", "team": null,
"employmentType": "FullTime", // FullTime|PartTime|Contract|Temporary|Internship|Volunteer|Other
"seniority": "senior", // from the title; null when the title says nothing
"locationRaw": "SF, NYC, SEA, CHI", // exactly as the ATS published it
"locations": [{ "city": "SF", "region": null, "country": null, "isRemote": false, "note": null }],
"isRemote": false,
"workplaceType": "hybrid", // onsite|hybrid|remote, when the ATS says so
"salaryMin": 152000, "salaryMax": 228000, "salaryCurrency": "USD",
"salaryPeriod": "YEAR", // YEAR|MONTH|WEEK|DAY|HOUR. Structured ATS data only, never guessed
"descriptionText": "Who we are\n\nStripe is…",
"descriptionHtml": null, // used instead when descriptionFormat is "html"
"applyUrl": "https://job-boards.greenhouse.io/stripe/jobs/8077887",
"jobUrl": "https://stripe.com/jobs/search?gh_jid=8077887",
"postedAt": "2026-07-22T13:15:53-04:00", "updatedAt": "2026-08-06T12:10:17-04:00",
"scrapedAt": "2026-09-08T07:05:51.244Z",
"source": "boards-api.greenhouse.io",
"sourceUrl": "https://boards-api.greenhouse.io/v1/boards/stripe/jobs?content=true"
}

An unreadable board produces a diagnostic row instead — ok: false plus input, atsPlatform, boardSlug, error (reason and the URL we tried), source, sourceUrl, checkedAt — and no charge. Filter with ok !== false for jobs only.

Roles syndicated to two boards are deduplicated. Two genuinely different requisitions on the same board are never collapsed, however alike their titles look.

Pricing

Pay per event. $2 per 1,000 job postings at the Free and Starter tiers, less above that.

EventFreeStarter (Bronze)Scale (Silver)Business (Gold)
actor-start — charged once per run, and only after the run has produced at least one job$0.001$0.001$0.0005$0.0002
job-posting — charged once per job row written to your dataset$0.002$0.002$0.0015$0.001

Worked example. You pull 12 companies and get 1,000 postings back on a Starter plan:

actor-start 1 × $0.001 = $0.001
job-posting 1,000 × $0.002 = $2.000
total $2.001

Three things that are unusual, and deliberate:

  1. Diagnostic rows are free. A dead board slug, a timeout, a domain with no detectable ATS — none of it is billed.
  2. actor-start is only charged once real results exist. It becomes payable once your input has been validated and the first board is about to be fetched, but it is only actually billed after at least one job row is safely in your dataset. A run that returns no postings — for any reason — finishes FAILED and costs nothing at all, not even the start fee.
  3. Caps are honoured before billing. Hit maxJobsTotal, maxRunSecs, or your own run budget, and the Actor stops cleanly. You are never charged for a job that did not make it into your dataset.

Integrations and use from code

One JSON object in, one flat array out — the shape agent runtimes want. The Actor runs with limited permissions, uses pay-per-event pricing and never enters Standby, so it works over the Apify MCP server and with x402 agentic payments. The Integrations tab pushes results to Slack, a webhook, Zapier, Make, Google Sheets, Snowflake or BigQuery.

curl -X POST "https://api.apify.com/v2/acts/insight.solutions~ats-jobs-api/run-sync-get-dataset-items?token=$APIFY_TOKEN" \
-H "Content-Type: application/json" \
-d '{"boards":["greenhouse:stripe"],"includeDescription":false,"maxJobsTotal":100}'
# pip install apify-client
from apify_client import ApifyClient
client = ApifyClient("<APIFY_TOKEN>")
run = client.actor("insight.solutions/ats-jobs-api").call(run_input={
"boards": ["greenhouse:stripe", "ashby:ramp"],
"remoteOnly": True,
"descriptionFormat": "markdown",
"maxJobsTotal": 500,
})
for job in client.dataset(run["defaultDatasetId"]).iterate_items():
if job.get("ok") is not False:
print(job["companyName"], job["title"], job["locationRaw"], job["applyUrl"], sep=" | ")

Set includeDescription to false when you only need the role list — the payload shrinks by roughly 90% and costs the same.

FAQ

How fresh is the data? Live. Every run reads the ATS endpoint at that moment; nothing is cached except the domain-to-ATS mapping, which records only which board a company uses, never the postings.

Will I get rate limited? These are syndication endpoints — vendors publish them so postings get crawled. The Actor sends at most one request at a time to any single host and backs off exponentially on 429 and 5xx. In normal use you will not see limits.

What stops a run from hanging? maxRunSecs, 240 seconds by default, checked before each board and each auto-detection probe. When it runs out the Actor stops fetching, keeps and bills for everything already collected, writes a diagnostic row for each board it never reached, and finishes. Auto-detection has a tighter budget of its own: six candidate pages, one attempt each, five seconds apiece.

Why is a company I want missing? It is on an ATS we do not support yet (iCIMS, Taleo, SuccessFactors and BambooHR are the usual suspects); or it uses a supported ATS under a slug that differs from its brand name — check the apply link; or it hosts its own careers page with no ATS behind it, so there is no public feed to read.

Why are salaries often null? Most postings do not publish one. We fill salaryMin/salaryMax only from structured compensation data the ATS actually returns — never from "competitive salary" or numbers scraped out of description text.

How is this different from calling the ATS APIs myself? For one company, do that. This exists for fifty companies on nine platforms: eleven response shapes, four location formats, two XML feeds, one POST API, HTML entities encoded twice on one of them, and recruiter emails you should not be storing.

Does it handle pagination? Yes, where the platform has it — SmartRecruiters and Workday page automatically up to maxJobsPerBoard. The others return the whole board in one response.

What happens if one board is down? The others still run. The failed one produces a diagnostic row, the run finishes SUCCEEDED, and you are not billed for the failure. If every board is down the run finishes FAILED and you are billed nothing at all.

  • Public endpoints only. Every source is an unauthenticated, publicly documented job-syndication feed or a public career board page. The Actor never logs in, never accepts cookies or session tokens, and never uses an API key belonging to anyone else.
  • No personal data by design. Recruiter contact details — email addresses, mailto: links, personal LinkedIn profile URLs — and recruiter or hiring-manager attribution lines are removed from every description before it is written to your dataset. Recruiter names exposed as structured fields by an ATS are never mapped into the output at all. What this does not do is chase a name written into free prose with no attribution marker; a regex that tried would mangle more descriptions than it cleaned, so we state the limit rather than overstate the guarantee.
  • robots.txt is respected on every HTML page we fetch — the career pages read during domain auto-detection and the JazzHR board page. It is fetched once per domain per run, cached, and evaluated per RFC 9309 against our user-agent token and *; a Disallow means the page is skipped, and the board says so in its diagnostic row. The remaining sources are the vendors' own JSON and XML syndication feeds.
  • You are the controller of what you do next. If you export this data, combine it with personal data, or contact people based on it, GDPR/CCPA obligations are yours, not ours.
  • Not affiliated with Greenhouse, Lever, Ashby, Workable, Recruitee, SmartRecruiters, Teamtailor, Personio, Breezy HR, JazzHR, Workday, or with any employer whose postings you retrieve. All product names and trademarks belong to their respective owners and are used only to describe which public feeds this Actor reads.

Our other Actors

ActorWhat it does
New Job Postings MonitorThe same 11 ATS connectors with memory: only the roles that opened, closed or changed since your last run
Website Contact ExtractorBusiness emails, phone numbers and social profiles from a list of website domains
SEC EDGAR APISEC filings, normalized XBRL financials and full-text search, by ticker or CIK
Wayback Machine ToolkitArchived URLs, archived page text, and a diff of one page between two dates