Greenhouse, Lever, Ashby & Workable Jobs API
Pricing
from $1.50 / 1,000 jobs
Greenhouse, Lever, Ashby & Workable Jobs API
Pull public job postings from Greenhouse, Lever, Ashby, and Workable boards. No logins, proxies, or paid APIs.
Pricing
from $1.50 / 1,000 jobs
Rating
0.0
(0)
Developer
Sunburst
Maintained by CommunityActor stats
0
Bookmarked
2
Total users
1
Monthly active users
13 hours ago
Last modified
Categories
Share
Pull every public job posting from companies' Greenhouse, Lever, Ashby and Workable job boards into one normalized JSON feed, with one dataset item per job.
The Actor calls only the unauthenticated job-board JSON APIs those products already publish for careers sites. It does not log in, does not use proxies, and does not call paid job APIs.
Why use it
- One input list of boards, one output schema
- Board URLs are enough — the Actor detects the ATS from the host
- Bad slugs become error rows; the rest of the run continues
- Optional keyword, location, department, remote, and recency filters
- Pay-per-event: a small start fee plus a fee per job item
How to get a board slug
Open the company's public careers page and copy the URL.
| ATS | Careers URL | Slug |
|---|---|---|
| Greenhouse | https://boards.greenhouse.io/gitlab or https://job-boards.greenhouse.io/gitlab | gitlab |
| Lever | https://jobs.lever.co/spotify (EU boards use jobs.eu.lever.co) | spotify |
| Ashby | https://jobs.ashbyhq.com/ramp | ramp |
| Workable | https://apply.workable.com/soundcloud | soundcloud |
You can paste that URL, or pass {"ats": "greenhouse", "slug": "gitlab"}.
Input
| Field | Type | Description |
|---|---|---|
companies | array | Required. Board URLs and/or {ats, slug} objects. |
keywords | string[] | Keep jobs that contain at least one keyword (case-insensitive substring). Searches the title only unless keywordsMatch says otherwise. |
keywordsMatch | title | title_and_description | Where keywords are searched. Default title. See Keyword matching. |
locations | string[] | Keep jobs whose location text contains at least one value. |
remoteOnly | boolean | Keep only remote roles. |
departments | string[] | Match department or team text. |
postedSince | integer | Keep jobs posted or updated in the last N days. Jobs with no date are kept. |
maxJobsPerCompany | integer | Cap matching jobs per board after filters. Default 5. |
includeDescription | boolean | Write cleaned Markdown (or plain text) on descriptionMarkdown. Default false. |
descriptionFormat | markdown | text | Used only when includeDescription is true. |
maxConcurrency | integer | Boards fetched at once. Default 3. |
The default input is three well-known public boards with maxJobsPerCompany: 5 and descriptions off, so a first run stays small.
Example
{"companies": ["https://job-boards.greenhouse.io/gitlab","https://jobs.lever.co/spotify",{ "ats": "ashby", "slug": "ramp" },{ "ats": "workable", "slug": "soundcloud" }],"keywords": ["engineer", "designer"],"locations": ["Remote", "New York"],"remoteOnly": false,"departments": ["Engineering"],"postedSince": 30,"maxJobsPerCompany": 25,"includeDescription": false}
Keyword matching
keywordsMatch controls where keywords are searched:
title(default): the job title only.engineerreturns "Backend Engineer" and "Engineering Manager", but not an "Account Executive" whose description mentions "solutions engineers".title_and_description: the title plus the full job description. This is howkeywordsworked before 1.0.8. It finds more jobs but also more off-target ones, and it is slower because descriptions have to be downloaded.
Inputs from before 1.0.8 still validate and run. To get the old matching back, add "keywordsMatch": "title_and_description".
Output
Each successful item looks like this:
{"status": "ok","id": "greenhouse:gitlab:1234567","company": "GitLab","ats": "greenhouse","slug": "gitlab","title": "Senior Backend Engineer","department": "Engineering","team": null,"location": "Remote","remote": true,"employmentType": "Full-time","compensation": {"min": 150000,"max": 200000,"currency": "USD","interval": "year"},"postedAt": "2026-10-01T20:00:00+00:00","updatedAt": "2026-10-14T15:55:28+00:00","applyUrl": "https://boards.greenhouse.io/gitlab/jobs/1234567","jobUrl": "https://boards.greenhouse.io/gitlab/jobs/1234567","descriptionMarkdown": null,"sourceId": "1234567"}
compensation is null when the board does not publish a range. descriptionMarkdown is filled only when includeDescription is enabled.
If a company cannot be read, the Actor writes an error row and continues:
{"status": "error","reason": "Board not found (HTTP 404) at https://boards-api.greenhouse.io/v1/boards/not-a-real-board/jobs?content=true&pay_transparency=true.","company": "not-a-real-board","ats": "greenhouse","slug": "not-a-real-board","sourceId": null}
The dataset Overview view shows status, title, company, ATS, location, remote, dates, and links.
Pricing
Pay-per-event. See the pricing table in the Actor listing for the live figures. The published events are:
| Event | Price | Charged |
|---|---|---|
run-start | $0.002 | Once per run |
job | $0.0015 | Once per job item |
Error rows are not billed as job events. If you set a max total charge on the run, the Actor stops when the next event would exceed it and keeps the items already written.
Public APIs used
| ATS | Endpoint |
|---|---|
| Greenhouse | GET https://boards-api.greenhouse.io/v1/boards/{token}/jobs?content=true |
| Lever | GET https://api.lever.co/v0/postings/{company}?mode=json (EU: api.eu.lever.co) |
| Ashby | GET https://api.ashbyhq.com/posting-api/job-board/{name}?includeCompensation=true |
| Workable | GET https://apply.workable.com/api/v1/widget/accounts/{subdomain} (?details=true when descriptions are needed) |
These endpoints are public, unauthenticated, and intended for careers-site widgets. Identify your traffic and do not poll a board more often than you need.
Reliability
- Retries with exponential backoff on HTTP 429, 5xx, timeouts, and network errors
- Invalid or unknown slugs become error rows; the run does not fail
- Modest default concurrency (3 boards at a time)
- Charge-limit stop is clean: no partial charges beyond the cap the platform allows
Limitations
- Only the four ATS hosts above. Workday, SmartRecruiters, Lever-hosted custom domains, and Greenhouse embed-only sites without a public board token are out of scope.
- A board must be public. Private Greenhouse boards, Lever sites that are not published, and Workable accounts that disabled the widget return 404 and an error row.
- Greenhouse has no official remote flag. Remote is inferred from location and office text.
- Compensation is included only when the ATS publishes a structured range (most common on Ashby; occasional on Lever and Greenhouse). Workable's widget feed does not include pay.
- Lever
createdAtis used aspostedAt. Greenhouse usesfirst_published/updated_at. Workable often has a date without a time. - Descriptions are HTML from the ATS, converted to Markdown or plain text. Conversion is best-effort; unusual markup can look messy.
- Large boards are downloaded in one or a few HTTP responses.
maxJobsPerCompanylimits how many items you write, not how much JSON the ATS returns. LeaveincludeDescriptionoff if you only need titles. - This Actor does not deduplicate across runs.
- Results only go as far as each employer published on that board. The Actor does not search the open web.
FAQ
Do I need API keys?
No. These job-board endpoints are public.
Can I pass a Greenhouse job URL, not the board URL?
Yes. https://boards.greenhouse.io/gitlab/jobs/123 is treated as the gitlab board. The same applies to a single Lever, Ashby, or Workable posting URL.
What happens if one company is wrong?
That company becomes an error row. Other companies still run.
Why is compensation null?
Most employers do not publish a structured range on the public board API. The Actor does not guess salary from description text.
Why is a role not marked remote?
Only an ATS remote flag, a workplaceType of remote, or location text such as "Remote" / "Work from home" counts. "Hybrid" is not remote.
Will the default input stay fast?
The prefills are public boards with maxJobsPerCompany: 5 and descriptions off. Run time still depends on how large those boards are on the day you run.
How do I keep costs down?
Set maxJobsPerCompany, add filters, leave descriptions off, and set a max total charge on the run.
More actors from this developer
- PDF to Markdown: DOCX, PPTX, XLSX, OCR & RAG Chunks: PDF, DOCX, PPTX, XLSX and HTML to clean Markdown and RAG-ready chunks.
- Google Trends Scraper & API: Interest, Related, Trending: interest over time, by region, and related queries from Google Trends.
- Google News Scraper: Full-Text Articles to Markdown: Google News articles as Markdown and RAG chunks for any topic or company.
- Website Screenshot & URL to PDF API (Bulk, Full Page): full-page and mobile screenshots (PNG, JPEG, WebP) and PDFs of any URL.
- Wayback Machine API: Snapshots & Archived Pages: every archived capture of a URL or domain, the snapshot closest to a date, and archived pages as Markdown.
Local development
python -m venv .venvsource .venv/bin/activatepip install -r requirements-dev.txtpytest
On the Apify platform the Actor starts with python -m src. Publish pay-per-event events run-start ($0.002) and job ($0.0015) to match this code. There is no deploy step in this repository.