SiteVerdict Job Postings API: Greenhouse, Lever, Ashby +3
Pricing
Pay per event
SiteVerdict Job Postings API: Greenhouse, Lever, Ashby +3
Get every open job from company careers boards on Greenhouse, Lever, Ashby, Workable, SmartRecruiters and Recruitee via their official public APIs. One clean schema: salary, remote, parsed locations. Auto-detect boards from careers URLs. Monitor new, changed and closed jobs.
Pricing
Pay per event
Rating
0.0
(0)
Developer
Aman Thakran
Maintained by CommunityActor stats
0
Bookmarked
2
Total users
1
Monthly active users
9 days ago
Last modified
Categories
Share
Company Job Postings API: Greenhouse, Lever, Ashby, Workable, SmartRecruiters & Recruitee
Every open job from the companies you care about, in one clean schema, straight from the official public job-board APIs their careers pages use. Paste careers URLs or company domains. You get title, team, parsed locations, remote/hybrid/on-site, employment type, salary (when published), dates, apply links and descriptions. Run it on a schedule and get only the new, changed and closed jobs.
By SiteVerdict (BharatFare Travel and Technologies Ltd, UK).
- 6 ATS platforms, 1 schema. No more writing a separate parser for each vendor.
- Auto-detect. Paste any board URL, an identifier like
greenhouse:airbnb, or juststripe.com. The Actor finds the board. - Salary you can trust. Structured pay ranges (Ashby, Lever, Recruitee, Greenhouse pay transparency) are used first. Otherwise ranges are parsed from pay-transparency text and always flagged
salary_source: "parsed_text". - Monitoring built in. Labels every job NEW / CHANGED (with the fields that changed) / CLOSED, compared with your previous run.
- Robust. One failing board never stops the run. Retries with backoff, per-vendor rate limiting, timeouts and a per-board status report.
- Fair pay-per-event pricing. You pay per job delivered plus a tiny fee per board checked. Failed boards are never charged, and your max-charge limit is always respected.
Which ATS platforms are covered?
| ATS | What you can paste | Official public endpoint used | Structured salary | Workplace type | Description |
|---|---|---|---|---|---|
| Greenhouse | boards.greenhouse.io/acme, job-boards.greenhouse.io/acme, embed ?for=acme, greenhouse:acme | Job Board API boards-api.greenhouse.io/v1/boards/{token}/jobs?content=true | Pay-transparency ranges (optional, 1 request per job) and currency custom fields. Otherwise parsed from text | Custom field when published, else detected | HTML + text |
| Lever | jobs.lever.co/acme, jobs.eu.lever.co/acme, lever:acme, lever-eu:acme | Postings API api.lever.co/v0/postings/{company}?mode=json (EU: api.eu.lever.co) | salaryRange when published | Yes | HTML + text (lists and additional sections included) |
| Ashby | jobs.ashbyhq.com/acme, ashby:acme | Public Job Posting API api.ashbyhq.com/posting-api/job-board/{org}?includeCompensation=true | Yes (compensation tiers) | Yes | HTML + text |
| Workable | apply.workable.com/acme, acme.workable.com, workable:acme | Public jobs widget API apply.workable.com/api/v1/widget/accounts/{subdomain}?details=true | Parsed from text | Remote flag | HTML + text |
| SmartRecruiters | careers.smartrecruiters.com/Acme, jobs.smartrecruiters.com/Acme, smartrecruiters:Acme | Posting API api.smartrecruiters.com/v1/companies/{id}/postings (+ /postings/{id} for descriptions) | Parsed from text | Yes (remote/hybrid) | HTML + text |
| Recruitee | acme.recruitee.com, recruitee:acme | Careers Site API {company}.recruitee.com/api/offers/ | Yes (salary min/max/period) | Yes (remote/hybrid/on-site) | HTML + text |
Careers pages on a company's own website (for example https://www.notion.com/careers) are scanned for links, iframes and embed scripts from these vendors.
Quick start
1. Get all jobs from a few boards
{"boards": ["greenhouse:discord","https://jobs.lever.co/spotify","https://jobs.ashbyhq.com/linear","https://apply.workable.com/huggingface/","https://channable.recruitee.com"]}
2. Find the board from a company domain
{ "discoverDomains": ["stripe.com", "figma.com", "ramp.com"] }
The Actor reads the homepage and a few careers pages (it respects robots.txt) and finds the ATS link. If there is none, it can try the domain name as a board slug on each ATS's public API (probeAtsApis). Where the vendor publishes a company name, it must match the domain. Each board's detected_by field in the SUMMARY record shows how it was found.
3. Remote engineering jobs posted this week, with salaries
{"boards": ["greenhouse:stripe", "ashby:openai", "ashby:ramp"],"keywords": ["engineer*", "developer"],"keywordScope": "title","workplaceTypes": ["remote"],"postedWithinDays": 7,"onlyWithSalary": true,"descriptionFormat": "none"}
4. Daily monitor: only new, changed and closed jobs
{"boards": ["greenhouse:airbnb", "lever:spotify", "smartrecruiters:ServiceNow"],"monitorMode": true,"monitorOutput": "changes","monitorStateKey": "competitor-hiring"}
Schedule it daily in Apify. The first run outputs every job as NEW (or set emitBaselineOnFirstRun: false to only save a baseline). Later runs output only NEW, CHANGED (with changed_fields) and CLOSED rows. A quiet day costs just the per-board fee.
Filters
All filters are optional. Different filters combine with AND, and values inside one filter combine with OR.
| Filter | Example | Notes |
|---|---|---|
keywords | ["data engineer", "ml*"] | Whole word or phrase, case-insensitive, * wildcard. Searches title + department + description (or title only with keywordScope: "title") |
excludeKeywords | ["intern", "principal"] | Matched against the title |
departments | ["Engineering", "Sales"] | Matches department or team (substring) |
locations | ["London", "Germany", "CA", "remote"] | City, region, country name or 2-letter country code. remote = fully remote jobs |
workplaceTypes | ["remote", "hybrid"] | Jobs with an unknown workplace type are excluded when this is set |
employmentTypes | ["full_time", "contract"] | Normalised types |
postedWithinDays | 14 | Uses the first-published date (falls back to last-updated) |
onlyWithSalary | true | Structured or parsed salary |
maxJobsPerCompany / maxJobsTotal | 50 / 1000 | Newest first |
Output
One dataset row per job. All six ATSs map to the same fields:
| Field | Description |
|---|---|
company, ats, board_token, job_id, job_key | job_key = ats:board:job_id, stable across runs (use it to dedupe) |
title, department, team | As published (team = Lever team, Ashby team, Workable function, SmartRecruiters function) |
location | The location string as published |
city, region, country, country_code | Parsed primary location (ISO 3166-1 alpha-2 code) |
locations[] | Every location, each parsed into city / region / country |
remote, workplace_type | remote / hybrid / onsite / null. remote is true only for fully remote roles |
employment_type, employment_type_raw | full_time, part_time, contract, temporary, internship, volunteer, other, plus the vendor's label |
salary_min, salary_max, salary_currency, salary_period, salary_source, salary_text | salary_source = structured (vendor field) or parsed_text (extracted from the description; salary_text shows the snippet) |
posted_at, updated_at | ISO 8601 UTC, when the ATS publishes them |
job_url, apply_url | Public job page and application link |
requisition_id, language | When published |
description_html, description_text | Controlled by descriptionFormat |
change_status, changed_fields, first_seen_at, closed_detected_at | Monitoring mode only |
contacts | Only when includeContacts is on (see below) |
Example row (trimmed):
{"company": "Discord","ats": "greenhouse","job_id": "8811881002","job_key": "greenhouse:discord:8811881002","title": "Senior Engineering Manager, Nitro","location": "San Francisco Bay Area","city": "San Francisco Bay Area","country_code": "US","salary_min": 304000,"salary_max": 380000,"salary_currency": "USD","salary_period": "year","salary_source": "parsed_text","salary_text": "The US base salary range for this full-time position is $304,000 - $380,000 + equity + benefits.","posted_at": "2026-10-09T20:09:30.000Z","job_url": "https://job-boards.greenhouse.io/discord/jobs/8811881002"}
The dataset has four views: Overview, Salary, Remote & location and Changes. Each run also saves a SUMMARY record in the key-value store. It lists every board with its status (ok, not_found, error, skipped_*), job counts, timing and the error message if there was one, plus discovery results and inputs that could not be resolved.
Pricing (pay per event)
| Event | When it is charged |
|---|---|
job-posting | Each job row delivered to your dataset (including CLOSED rows in monitoring mode) |
board-checked | Each company board fetched successfully (a small fee, so scheduled monitors with no changes stay cheap) |
You are never charged for boards that fail or don't exist, for discovery pages, or for retries. The run stops cleanly when your Max total charge is reached, and the Actor never starts a board it cannot afford to deliver at least one job from. See the Store pricing tab for current prices.
Performance
Measured on real public boards (11 Oct 2026, 1 GB memory):
| Run | Result |
|---|---|
| 5 sample boards (Greenhouse, Lever, Ashby, Workable, Recruitee) + SmartRecruiters | 167 jobs in ~9 s |
| Stripe (729) + OpenAI on Ashby (815) + Experian on SmartRecruiters (393, with descriptions) | 1,937 jobs in 63 s, peak about 410 MB |
| Domain discovery for 6 sites | ~6 s |
Greenhouse, Lever, Ashby, Workable and Recruitee return a whole board in one request. SmartRecruiters needs one extra request per job to get the description, so large SmartRecruiters boards take longer. For those, use descriptionFormat: "none" or title-only keywords.
Legal & data protection
- Official public endpoints only. Every request goes to an endpoint the ATS vendor documents for publishing a company's jobs on its careers site (Greenhouse Job Board API, Lever Postings API, Ashby Job Posting API, Workable jobs widget, SmartRecruiters Posting API, Recruitee Careers Site API). No login, no API keys, no CAPTCHA solving, no proxies, no bypassing of any access control.
- Honest identity and polite rate. A clear user agent (
SiteVerdict-JobPostings/…), conservative per-vendor rate limits, andRetry-Afteris always honoured. - Company websites: discovery fetches only a handful of pages per domain and follows
robots.txt. - No job-board scraping. This Actor does not touch LinkedIn, Indeed, Glassdoor or any aggregator.
- Job data, not people. The output is the job advert the company chose to publish.
includeContactsis off by default. When you turn it on, you only get contact details the company itself published on the posting (a SmartRecruiters posting creator's name, a Recruitee application mailbox). Turn it on only if you have a lawful basis under GDPR / UK GDPR, and respect each vendor's terms. - You are responsible for how you use the data (for example, respect each company's copyright in its job descriptions when republishing them).
- Licence of the data: job adverts belong to the companies that publish them and are provided under each ATS vendor's own terms (Greenhouse, Lever, Ashby, Workable, SmartRecruiters, Recruitee). No licence to republish is granted by this Actor.
- Where results are stored: the default dataset and key-value store of the run, inside your own Apify account. These default storages are unnamed. How long Apify keeps them is set by Apify and your plan, not by SiteVerdict (Apify data retention); you can delete them sooner in Apify Console.
- Monitoring (off by default): to detect NEW / CHANGED / CLOSED jobs, each job's ID, title, URL, location, department, posting date and short hashes of its fields are kept in a named key-value store in your account (
siteverdict-job-postings-state, or the name you set). Apify keeps named stores until you delete them. - What SiteVerdict keeps: nothing. The Actor runs in your Apify account and sends no data to SiteVerdict.
FAQ
I only know the company name. Put its website in discoverDomains. If you know the ATS, ats:slug (for example lever:spotify) is the fastest.
SmartRecruiters says "not found" but the company uses SmartRecruiters. SmartRecruiters identifiers are case-sensitive and sometimes differ from the brand (for example Ubisoft2). Copy the identifier from the company's careers.smartrecruiters.com/... URL.
Why is salary_min empty? The company didn't publish pay for that role. Ashby, Recruitee and many US Greenhouse/Lever boards do publish it. Turn on greenhousePayTransparency to read Greenhouse's structured pay ranges where they exist.
Is a parsed salary reliable? It is taken from the company's own pay-transparency sentence, and salary_text shows the exact snippet. The parser needs a currency, ignores funding/revenue/perk amounts, and accepts a single amount only next to salary words. It is still a parse, which is why it is flagged.
How does CHANGED work? For each job we store short hashes of title, department, team, locations, workplace, employment type, salary, description, apply URL and requisition ID. changed_fields lists the hashes that differ. Changing your filters starts a fresh baseline automatically, so a filter change is never reported as closures.
Can a failed fetch make all jobs look CLOSED? No. Closures are only reported for boards fetched successfully and completely: not after errors, not when maxJobsPerCompany truncated the list, and not when the charge limit cut the output short.
Workday, iCIMS, Taleo, Personio, Teamtailor? Not yet. Ask for the ATS you need.
Known limits
- Location parsing is rules-based (ICU country names plus aliases and local names, US/CA/AU states and provinces, a list of major cities). Ambiguous strings keep the original text in
location; multi-city strings separated only by commas may be split imperfectly. - Lever and Ashby publish no company name. The board slug is used, prettified (
openaibecomesOpenai). - Greenhouse EU-hosted board URLs (
job-boards.eu.greenhouse.io/...) are queried through the main Job Board API (boards-api.greenhouse.io); if a board is not served there it is reported asnot_foundinSUMMARY. - Workable's widget API rate-limits aggressively, so Workable boards are fetched at most once every 1.5 s.
Support
Open an issue on the Actor's Issues tab.