Greenhouse, Lever, Ashby & Workable Jobs API avatar

Greenhouse, Lever, Ashby & Workable Jobs API

Pricing

from $1.50 / 1,000 jobs

Go to Apify Store
Greenhouse, Lever, Ashby & Workable Jobs API

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

Sunburst

Maintained by Community

Actor stats

0

Bookmarked

2

Total users

1

Monthly active users

13 hours ago

Last modified

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.

ATSCareers URLSlug
Greenhousehttps://boards.greenhouse.io/gitlab or https://job-boards.greenhouse.io/gitlabgitlab
Leverhttps://jobs.lever.co/spotify (EU boards use jobs.eu.lever.co)spotify
Ashbyhttps://jobs.ashbyhq.com/rampramp
Workablehttps://apply.workable.com/soundcloudsoundcloud

You can paste that URL, or pass {"ats": "greenhouse", "slug": "gitlab"}.

Input

FieldTypeDescription
companiesarrayRequired. Board URLs and/or {ats, slug} objects.
keywordsstring[]Keep jobs that contain at least one keyword (case-insensitive substring). Searches the title only unless keywordsMatch says otherwise.
keywordsMatchtitle | title_and_descriptionWhere keywords are searched. Default title. See Keyword matching.
locationsstring[]Keep jobs whose location text contains at least one value.
remoteOnlybooleanKeep only remote roles.
departmentsstring[]Match department or team text.
postedSinceintegerKeep jobs posted or updated in the last N days. Jobs with no date are kept.
maxJobsPerCompanyintegerCap matching jobs per board after filters. Default 5.
includeDescriptionbooleanWrite cleaned Markdown (or plain text) on descriptionMarkdown. Default false.
descriptionFormatmarkdown | textUsed only when includeDescription is true.
maxConcurrencyintegerBoards 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. engineer returns "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 how keywords worked 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:

EventPriceCharged
run-start$0.002Once per run
job$0.0015Once 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

ATSEndpoint
GreenhouseGET https://boards-api.greenhouse.io/v1/boards/{token}/jobs?content=true
LeverGET https://api.lever.co/v0/postings/{company}?mode=json (EU: api.eu.lever.co)
AshbyGET https://api.ashbyhq.com/posting-api/job-board/{name}?includeCompensation=true
WorkableGET 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 createdAt is used as postedAt. Greenhouse uses first_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. maxJobsPerCompany limits how many items you write, not how much JSON the ATS returns. Leave includeDescription off 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

Local development

python -m venv .venv
source .venv/bin/activate
pip install -r requirements-dev.txt
pytest

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.