Greenhouse Job Board Scraper - One Request avatar

Greenhouse Job Board Scraper - One Request

Pricing

from $1.00 / 1,000 results

Go to Apify Store
Greenhouse Job Board Scraper - One Request

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

String

Maintained by Community

Actor stats

0

Bookmarked

2

Total users

1

Monthly active users

11 days ago

Last modified

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 /departments variant 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?

FieldTypeDescription
jobIdstringGreenhouse's own posting id. Stable, so use it to de-duplicate and to diff between runs
titlestringThe posting title
companystringThe company name Greenhouse publishes on the posting
tenantstringThe board token, for example databricks
locationstringExactly the string the recruiter typed. See the note below
citystringFilled only when the location is unambiguously one place
statestringUS state or Canadian province, resolved from a closed list of names and codes
countrystringResolved from a closed list of country names and aliases, not from position in the string
isRemotebooleantrue when the location names remote or work from home
departmentstringThe Greenhouse department the posting sits under. null when it is filed under none
employmentTypestringFrom the board's own metadata field where the company publishes one, for example Full-time
postedAtstringISO 8601 timestamp. The posting's first_published, falling back to updated_at
jobUrlstringDirect link to the posting. Greenhouse sometimes points this at the company's own careers site instead
boardUrlstringhttps://job-boards.greenhouse.io/<tenant>
sourceUrlstringThe board feed URL the row came from
collectedAtstringISO 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 jobId to key on.
  • Build recruiting lead lists by department and location, then filter on isRemote.
  • Score sales accounts on hiring activity, using postedAt to weight recent demand.
  • Reconcile an aggregator's coverage against the employer's own board to find what it missed.

How to use it

  1. 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.
  2. Add the token or URL to Job boards. Up to 100 per run.
  3. Set Maximum results if you want a hard ceiling on rows. The default is 1000.
  4. Raise Concurrency toward 10 if you are collecting many boards and want the run to finish sooner.
  5. Start the run, then export the dataset as JSON, CSV, Excel or XML, or read it over the API.
  6. For ongoing monitoring, schedule the Actor and compare jobId sets between runs.

Input

FieldTypeDefaultDescription
boardsarray of stringsrequiredBoard tokens or Greenhouse board URLs. Between 1 and 100 entries
maxItemsinteger1000Cap on dataset rows, up to 50,000
concurrencyinteger5Boards 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.

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.

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.