Greenhouse Job Board Scraper - One Request
Pricing
from $1.00 / 1,000 results
Greenhouse Job Board Scraper - One Request
Reads any Greenhouse board from Greenhouse's own JSON feed and returns jobId, title, company, department, location, isRemote, employmentType, postedAt and the apply URL for every open posting. One request covers a whole board, however large. Board token, board URL or embed URL.
Pricing
from $1.00 / 1,000 results
Rating
0.0
(0)
Developer
String
Maintained by CommunityActor stats
0
Bookmarked
2
Total users
1
Monthly active users
11 days ago
Last modified
Categories
Share
What does Greenhouse Job Board Scraper do?
Point it at a Greenhouse board and it returns every open posting on that board as a row: jobId,
title, company, department, location, isRemote, employmentType, postedAt and the apply
URL. You can address a board by its token (databricks), by its URL
(https://job-boards.greenhouse.io/databricks), by a link to a single posting, or by the embedded
board URL that carries ?for=databricks. All four resolve to the same board.
Greenhouse publishes every board as JSON, so there is no markup to parse and no language model in the path. A field is either on the posting or it is not.
- One request returns an entire board. A measured run collected 814 postings from 1.23 requests.
- The
/departmentsvariant of the feed is the one this Actor reads, because it is the only one that carries the department each posting is filed under at a sane response size. - A posting listed under both a parent and a child department is written once, not twice.
- Up to 100 boards per run, fetched five at a time by default.
- No Greenhouse account, cookie or API key. This is the board any logged-out visitor can load.
What data does it extract?
| Field | Type | Description |
|---|---|---|
jobId | string | Greenhouse's own posting id. Stable, so use it to de-duplicate and to diff between runs |
title | string | The posting title |
company | string | The company name Greenhouse publishes on the posting |
tenant | string | The board token, for example databricks |
location | string | Exactly the string the recruiter typed. See the note below |
city | string | Filled only when the location is unambiguously one place |
state | string | US state or Canadian province, resolved from a closed list of names and codes |
country | string | Resolved from a closed list of country names and aliases, not from position in the string |
isRemote | boolean | true when the location names remote or work from home |
department | string | The Greenhouse department the posting sits under. null when it is filed under none |
employmentType | string | From the board's own metadata field where the company publishes one, for example Full-time |
postedAt | string | ISO 8601 timestamp. The posting's first_published, falling back to updated_at |
jobUrl | string | Direct link to the posting. Greenhouse sometimes points this at the company's own careers site instead |
boardUrl | string | https://job-boards.greenhouse.io/<tenant> |
sourceUrl | string | The board feed URL the row came from |
collectedAt | string | ISO 8601 timestamp of the run |
About the location fields
Greenhouse does not model a location. location.name is free text, and across production boards it
includes Remote - US, Cork, Ireland; Dublin, Ireland, Dublin OR London, CHI and DUB,
Canada - Remote (ON, AB, BC, or NS Only) and AMER.
Splitting those confidently produces confident nonsense, so city, state and country are only
filled where the string is one plain comma-separated place. Lists, parentheticals and dashed suffixes
are left alone and come back null. The verbatim string is always in location, which is the field
to read when the other three are empty.
Countries are matched against a fixed vocabulary rather than taken from the last segment, because
plenty of boards write plain city lists in the same shape. Toronto, Seattle is two cities, and a
positional rule would report Seattle as a country.
Why scrape Greenhouse?
Greenhouse sits under a large share of the technology sector's hiring, which makes its boards a primary source rather than a derived one. The postings are on the board the moment the employer publishes them, without the delay and the duplicate rows an aggregator adds.
- Track hiring across a competitor set and alert when a new department starts opening roles.
- Estimate a company's direction from the department mix over time, read from its own board.
- Populate a niche job board or a talent CRM with live postings and a stable
jobIdto key on. - Build recruiting lead lists by department and location, then filter on
isRemote. - Score sales accounts on hiring activity, using
postedAtto weight recent demand. - Reconcile an aggregator's coverage against the employer's own board to find what it missed.
How to use it
- Find the board. On a company's careers page, either the address bar reads
job-boards.greenhouse.io/<token>, or the page embeds the board in an iframe whose URL carries?for=<token>. Either form works as input. - Add the token or URL to Job boards. Up to 100 per run.
- Set Maximum results if you want a hard ceiling on rows. The default is 1000.
- Raise Concurrency toward 10 if you are collecting many boards and want the run to finish sooner.
- Start the run, then export the dataset as JSON, CSV, Excel or XML, or read it over the API.
- For ongoing monitoring, schedule the Actor and compare
jobIdsets between runs.
Input
| Field | Type | Default | Description |
|---|---|---|---|
boards | array of strings | required | Board tokens or Greenhouse board URLs. Between 1 and 100 entries |
maxItems | integer | 1000 | Cap on dataset rows, up to 50,000 |
concurrency | integer | 5 | Boards fetched in parallel, up to 10 |
{"boards": ["https://job-boards.greenhouse.io/databricks","stripe","https://boards.greenhouse.io/embed/job_board?for=gitlab"],"maxItems": 2000,"concurrency": 5}
Duplicates collapse before anything is fetched, so passing the same board as a token and as a URL bills
once. A URL on a host other than greenhouse.io is rejected with a reason instead of being fetched.
Output
One row per posting. Illustrative shape:
{"jobId": "8503792002","title": "Account Executive","company": "GitLab","tenant": "gitlab","location": "San Francisco, CA","city": "San Francisco","state": "CA","country": "United States","isRemote": false,"department": "Sales","employmentType": "Full-time","postedAt": "2026-04-17T05:58:03-04:00","jobUrl": "https://job-boards.greenhouse.io/gitlab/jobs/8503792002","boardUrl": "https://job-boards.greenhouse.io/gitlab","sourceUrl": "https://boards-api.greenhouse.io/v1/boards/gitlab/departments","collectedAt": "2026-08-20T09:14:02.118Z"}
A remote posting on the same board might read "location": "Remote - Italy" with isRemote: true and
all three geo fields null, because that string is a qualifier rather than a place.
Alongside the dataset, the run writes a SUMMARY record holding the item count, the failure count and
every target that failed with its error text.
How much does it cost?
Pay per event, charged once per result row written to the dataset. The rate is on this Actor's pricing tab. A board that fails produces no rows and therefore costs nothing.
The economics here are unusual in your favour: because one fetch returns a whole board, a 3,000-posting board costs one request. What you pay tracks the rows you keep.
Runs started from an Apify free plan stop at 250 requests and 250 results, and the run reports
that it hit the limit. Any paid plan runs the full input and your maxItems. The cap binds on requests
as well as rows, so a 100-board input on a free plan cannot spend fetches on rows that would then be
discarded.
Using it with the Apify API
import { ApifyClient } from "apify-client";const client = new ApifyClient({ token: "<YOUR_APIFY_TOKEN>" });const run = await client.actor("usestring/greenhouse-jobs").call({boards: ["databricks", "stripe"],maxItems: 5000,concurrency: 5});const { items } = await client.dataset(run.defaultDatasetId).listItems();const byDepartment = new Map();for (const job of items) {const key = job.department ?? "(none)";byDepartment.set(key, (byDepartment.get(key) ?? 0) + 1);}console.log([...byDepartment].sort((a, b) => b[1] - a[1]));
To see which boards, if any, failed, read the run's SUMMARY record from its key-value store.
Is it legal to scrape Greenhouse?
Greenhouse boards are published so that candidates and search engines can find open roles. This Actor reads the same public board feed a logged-out visitor's browser reads, with no login, no session and no API key.
The output describes jobs, not people. No applicant data, no application counts, no recruiter contact details. The only personal data that could appear is a name an employer chose to put in a public posting title or department label.
Rules on collecting and reusing data vary by jurisdiction and by the terms you have agreed to, so treat this as background rather than legal advice and check with a lawyer for anything unusual.
Related actors
- Ashby Job Board Scraper is the closest sibling. Same segment of employers, and it returns a structured postal address per posting.
- Workday Job Board Scraper covers the enterprises that outgrew Greenhouse.
- iCIMS Job Board Scraper covers high-volume hiring in retail, healthcare and construction.
- Indeed Job Listings Scraper is the one to use when you want a market rather than a named list of employers.
- LinkedIn Profile Scraper covers the candidate side of the same pipeline.
FAQ
Where do I find the board token? It is the path segment after job-boards.greenhouse.io/. If the
careers page embeds the board instead, view the iframe URL and take the for= parameter. Passing the
full URL works either way.
Does an empty board count as a failure? No. A company between hiring rounds genuinely has zero
postings, so an empty board is a real result. An unknown token is different: Greenhouse answers it with
{"error":"Invalid Board Token"}, which has no departments to read, and that is recorded as a failure.
Why is employmentType often null? Greenhouse has no standard field for it. Boards that publish it
do so as a custom metadata field under a name of their choosing, so this Actor reads a short list of
known names such as Employment Type, Time Type and Commitment. Job Type is deliberately
ignored, because the boards that define it fill it with posting categories like Pipeline and
Evergreen rather than employment terms.
Can I get the job description text? No, and that is a deliberate trade. Greenhouse only returns
descriptions when you ask the feed for content=true, which also inflates one large board's response
from 0.7 MB to 7.5 MB. Follow jobUrl for the description.
How many boards can one run cover? 100, which at one request each is a small run. The practical
limit is maxItems, since a handful of large boards can exceed 5,000 postings between them.
How current are the results? They are fetched live, so they match the board at the moment the run
executes. Schedule the Actor hourly or daily and diff on jobId to catch new and removed postings.
What if one board is broken? It is recorded under failures in the run's SUMMARY with the error,
and the other boards still produce rows. Only a run where every board failed exits with an error.
Feedback
If a board returns something unexpected, or a field you need is missing, open an issue from this Actor's Store page with the board token. Reports that name a specific board get fixed fastest.