# Employment Hero Jobs Scraper — Employer Careers Pages (`adderleydata/employmenthero-jobs-scraper`) Actor

Every open role on any employer's Employment Hero careers page as structured data: title, team, place with postcode and country, remote, hybrid or on-site, work type, posted date and pay where shown, full text on request. Incremental mode charges only for what changed. No personal data.

- **URL**: https://apify.com/adderleydata/employmenthero-jobs-scraper.md
- **Developed by:** [Adderley Data](https://apify.com/adderleydata) (community)
- **Categories:** Jobs, Lead generation, Automation
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $1.45 / 1,000 posting saveds

This Actor is paid per event. You are not charged for the Apify platform usage, but only a fixed price for specific events.
Since this Actor supports Apify Store discounts, the price gets lower the higher subscription plan you have.

Learn more: https://docs.apify.com/actors/running/actors-in-store.md#pay-per-event

## What's an Apify Actor?

An Actor is a serverless cloud program that runs on the Apify platform. It has two run modes.
In Batch mode, an Actor accepts a well-defined JSON input, performs an action which can take anything from a few seconds to a few hours,
and optionally produces a well-defined JSON output, datasets with results, or files in key-value store.
In Standby mode, an Actor provides a web server which can be used as a website, API, or an MCP server.

Apify vocabulary and the platform model are defined once, in the agent quickstart at https://apify.com/agents.md.

## How to integrate an Actor?

If asked about integration, you help developers integrate Actors into their projects.
You adapt to their stack and deliver integrations that are safe, well-documented, and production-ready.

Do not guess an integration path. Every one of them is in the agent quickstart at https://apify.com/agents.md: the Apify MCP server, Agent Skills with the Apify CLI, the JavaScript and Python clients, the REST API, and the account-free path for an agent with no human to sign in. It also carries the rule on stating cost before the first paid run.

For examples already wired to this Actor's own input schema, see the [API](#api) section below.

Each client library has reference documentation the quickstart does not restate: [JavaScript/TypeScript](https://docs.apify.com/api/client/js/docs.md) (`npm install apify-client`) and [Python](https://docs.apify.com/api/client/python/docs.md) (`pip install apify-client`).

# README

### What does Employment Hero Jobs Scraper do?

Employment Hero Jobs Scraper reads every open role on any employer's public Employment Hero careers page and returns it as structured data you can load straight into a spreadsheet, a database or a model. If an employer's jobs are at `employmenthero.com/jobs/organisations/<name>/` — the careers page Employment Hero hosts for the businesses that hire through it — this Actor reads them, given the link or the name in it.

For each job you get the title, the employer, the team, the place as the employer wrote it with its city, region, postcode and country, whether the job is on-site, hybrid or remote, the employment type and term, the moment the job went up, the pay where the employer shows it, and a link to the job — and, if you ask for it, the full text of the ad.

What makes it different:

- **One request per careers page.** The Actor reads the list the careers page itself reads from Employment Hero: up to 100 open jobs in one response, every job's full text with it. No HTML parsing, no page-by-page crawling, nothing to break when a careers page is redesigned. A careers page of more than 100 jobs is read 100 at a time.
- **Pay as the employer shows it.** Where the employer shows pay, you get `min`, `max`, the ISO currency code and the period — hour, day, week, month or year — with the line the job's page prints, such as `$36.23 AUD – $49.43 AUD (Hour)`, in `salary.raw`. Where the employer hides pay, Employment Hero's pay fields are never read, whatever numbers come with them. Nothing is estimated.
- **Places you can filter and join on.** Employment Hero writes a place as city, region and postcode ("Sydney, New South Wales 2000"); the Actor splits it where the shape says which part is which, and takes the country from Employment Hero's own country code.
- **Incremental mode.** Put the Actor on a schedule and each run returns only jobs that are new, changed, back again or gone. Unchanged jobs are skipped and **not charged**.
- **One stable schema.** Every row has every field, every time. Unknown is `null`, never a missing key, so nothing downstream breaks on a sparse job. The schema is versioned (`job.v1`) and every Adderley Data jobs Actor uses it, so an Employment Hero careers page, a Workable board and a job board sit in the same table.
- **No personal data.** No field in Employment Hero's list names a person, and the Actor never reads its logos, images, attachments or anything about the visitor as a candidate. The ad's text comes only when you ask for it, with contact details redacted by default.
- **A pace Employment Hero asks for.** Employment Hero's robots.txt asks for ten seconds between requests, and the Actor keeps to it whatever rate you set.
- **Bounded cost.** You set a maximum number of results; the run stops there. It also stops at the spending limit you set on the run in Apify.

### What Employment Hero data can you extract?

| Field | What it holds |
| --- | --- |
| `id` | Stable across runs: `source:market:sourceJobId`. Use it as your primary key. |
| `title` | Job title as listed. |
| `company.name` | The hiring company, where the listing names one. |
| `advertiser.name` | The business that placed the listing — often a recruitment agency. Never a person. |
| `location.raw` | Location text as listed. Several locations are joined with `\|`. |
| `location.suburb` | Suburb, when the listing states one. |
| `location.city` | City or area, when the listing states one. |
| `location.region` | State or region, e.g. `VIC`. |
| `location.postcode` | Postcode, when the source provides it. |
| `location.country` | ISO 3166-1 alpha-2 country code. |
| `workArrangement` | `on_site`, `hybrid`, `remote` or `unknown`. |
| `employmentTypes` | Normalised: `full_time`, `part_time`, `contract`, `casual`, `temporary`, `internship`, `volunteer`. |
| `salary.raw` | The salary text exactly as shown, or null when the listing shows none. |
| `salary.min` | Lower bound as a number, when the text contains one. |
| `salary.max` | Upper bound as a number. Equal to min for a single figure. |
| `salary.currency` | ISO 4217. Taken from the text, otherwise the market default. |
| `salary.period` | `hour`, `day`, `week`, `month` or `year`; null when the text does not say. |
| `salary.includesSuper` | true / false when the text says so ("plus super", "inc. super"); otherwise null. |
| `classifications` | The source's category and subcategory pairs. |
| `teaser` | The short summary shown on the results page. |
| `bulletPoints` | Selling points shown on the results page. |
| `postedAt` | When the listing was posted, ISO 8601 UTC. |
| `updatedAt` | The source's own last-modified time, ISO 8601 UTC. Published by ATS and API sources; null where the site does not show one. |
| `expiresAt` | Expiry, ISO 8601 UTC, where the source states one. |
| `isPromoted` | true for paid placements. A listing shown both promoted and organic is returned once. |
| `url` | Link to the listing. |
| `description` | Null unless requested. `text`, optional sanitised `html`, and `contactsRedacted`. |
| `changeType` | Incremental runs: `NEW`, `UPDATED`, `REAPPEARED`, `EXPIRED` (or `UNCHANGED` if you ask for those). Otherwise null. |
| `firstSeenAt` | Incremental runs: when this monitor first saw the listing. |
| `contentHash` | SHA-256 over the fields that define a change. Compare it to detect edits yourself. |
| `scrapedAt` | When this row was produced, ISO 8601 UTC. |
| `source` | Source key, e.g. `seek`. |
| `market` | Market key, e.g. `au`, `nz`. |
| `sourceJobId` | The source's own identifier for the listing. |
| `company.sourceCompanyId` | The source's identifier for the company, when exposed. |
| `company.url` | The company's page on the source site, when exposed. |
| `advertiser.sourceAdvertiserId` | The source's identifier for the advertiser. |
| `schemaVersion` | Always `job.v1`. Breaking changes ship as `job.v2` in a new Actor version, never silently. |

How Employment Hero's fields fill the schema:

- `company.name` and `advertiser.name` are the employer's name as its careers page shows it: the business whose careers page it is placed the ad and is the employer. `company.sourceCompanyId` and `advertiser.sourceAdvertiserId` are the organisation's name in the careers page's link, and `company.url` is that careers page, `https://employmenthero.com/jobs/organisations/<name>/`.
- `url` is the job's own page, `https://employmenthero.com/jobs/position/<job>/`, as the careers page links it, and `sourceJobId` is Employment Hero's id for the job.
- `location.raw` is the place as the employer wrote it. `city`, `region` and `postcode` are its parts where the shape says which is which: "Sydney, New South Wales 2000" is Sydney, New South Wales, 2000, and "Auckland 1021" is Auckland and 1021. A single name without a postcode ("Manila") gives no city, since it may be a city or a region, and a country's name is never a city. `location.country` is Employment Hero's own two-letter country code, or the country the place names where the code is missing. `suburb` is always `null`.
- `workArrangement` is the employer's own setting: `on_site`, `hybrid`, or `remote` for a job open anywhere or anywhere in one country.
- `employmentTypes` comes from the employment type and term together: Full-time gives `full_time`, Part-time `part_time`, Casual `casual`, Contract `contract`, Temporary `temporary`. Permanent states no type of its own, so a Full-time, Permanent job is `full_time`. Where neither states one, a title that does ("Casual Barista") is taken at its word.
- `classifications` holds the job's team as `category`. Employment Hero has no second level, so `subcategory` is `null`, and a job with no team has an empty list.
- `salary` is filled only where the employer shows pay. `includesSuper` is `null`: Employment Hero does not say. Where pay is hidden but the ad's own text states a range, that range is read from the text, as it is for every source, with the sentence it came from in `salary.raw`.
- `postedAt` is when the job went up on Employment Hero, to the second, in UTC. `updatedAt` and `expiresAt` are `null`: the list publishes neither.
- `market` is always `global`: an Employment Hero careers page is the employer's, and one employer can post in several countries.
- Employment Hero's demonstration jobs are never returned. An "expression of interest" posting is listed like any other job and is returned like one.

### How much does it cost to scrape Employment Hero careers pages?

You pay per job saved to your dataset — **$1.75 per 1,000 jobs** on Apify's Starter plan — plus $0.005 each time a run starts. There is no monthly rental.

| Apify plan | Price | Per listing |
| --- | --- | --- |
| Free | $1.75 per 1,000 listings | $0.00175 |
| Starter (Bronze) | $1.75 per 1,000 listings | $0.00175 |
| Scale (Silver) | $1.60 per 1,000 listings | $0.00160 |
| Business (Gold) | $1.45 per 1,000 listings | $0.00145 |

Plus $0.005 per run start. Compute and proxy are included in these prices.

| What you run | Cost (USD, Starter plan) |
| --- | --- |
| 100 listings, one run | $0.18 |
| 1,000 listings, one run | $1.75 |
| 10,000 listings, one run | $17.50 |
| 50,000 listings, one run | $87.50 |
| A daily incremental monitor finding about 150 new or changed listings a day, for a month | $8.03 |

Descriptions cost nothing extra here: they arrive in the same request as the list. Use incremental mode for anything you run more than once — after the first run you pay only for what changed.

### How to scrape an Employment Hero careers page

1. Find the employer's careers page on Employment Hero. Its address looks like `https://employmenthero.com/jobs/organisations/acme-health/`; the organisation's name is `acme-health`. A regional copy (`employmenthero.com/uk/jobs/organisations/…`) and the employer's page on Employment Hero Jobs (`jobs.employmenthero.com/organisations/…`) work too.
2. Open the Actor in Apify Console and go to the **Input** tab. Paste one link or name per line into **Careers pages**. Up to 500 per run.
3. Optionally filter: **Title keywords**, **Locations**, **Teams**, **Work types**, **Posted within (days)**.
4. Set **Maximum results**. This is also your cost cap.
5. Press **Start**. When the run finishes, open the **Output** tab and export as JSON, CSV, Excel, XML or HTML, or read the dataset through the Apify API.

A job that matches more than one careers page or filter is returned once.

### Input

| Field | Type | Default | What it does |
| --- | --- | --- | --- |
| `boards` | array | — | One entry per employer: its Employment Hero careers page link (https://employmenthero.com/jobs/organisations/acme-health/), its page on Employment Hero Jobs, or the organisation's name in the link ("acme-health"). Up to 500 pages per run; each is one request for up to 100 open postings, their full text included. |
| `keywords` | array | — | Keep postings whose title contains every word of any keyword, in any order — "engineer data" matches "Senior Data Engineer". Leave empty for all titles. |
| `locations` | array | — | Keep postings whose place contains this text ("Sydney", "New South Wales", "2000"), that are in this country ("Australia", "United Kingdom", "NZ"), or that the employer marks "Remote", "Hybrid" or "On-site". Leave empty for all locations. |
| `teams` | array | — | Keep postings whose team, as the employer names it, contains this text, e.g. "Engineering" or "Sales". Leave empty for all teams. |
| `workTypes` | array | — | Keep postings whose employment type or term, as the employer sets it, contains this text: "Full-time", "Part-time", "Casual", "Permanent", "Contract", "Temporary". Leave empty for all work types. |
| `postedWithinDays` | integer | — | Keep postings that went up within the last N days, counted back from the start of the run. Leave empty for any time. |
| `maxResults` | integer | `100` | The run stops once this many postings are saved. You are charged per posting saved, so this is also your cost cap. |
| `includeDescription` | boolean | `false` | On: each row carries the ad's full text. It arrives in the same request as the list, so it costs no extra request and no extra time. Off: the facts only. |
| `descriptionFormat` | `text`, `text_and_html` | `"text"` | Plain text, or plain text plus sanitised HTML. |
| `redactContacts` | boolean | `true` | On by default: email addresses and phone numbers inside description text are replaced with \[redacted]. This Actor never outputs anyone's name or contact fields. |
| `incremental` | boolean | `false` | Remember what earlier runs saw and save only postings that are new, changed or gone. Unchanged postings are skipped and not charged. Put the Actor on a schedule with this on. |
| `stateKey` | string | — | Optional name for this monitor, e.g. "competitor-engineering-roles". Runs with the same key share memory. Left empty, a key is derived from the careers pages and filters themselves. |
| `emitExpired` | boolean | `true` | Incremental mode only. When a complete run no longer finds a posting it saw before, save one row with changeType EXPIRED. |
| `emitUnchanged` | boolean | `false` | Incremental mode only. Saves (and charges for) every posting, labelled UNCHANGED where nothing moved. |
| `proxyConfiguration` | object | `{"useApifyProxy":true}` | Apify Proxy, automatic group, is the default and is what this Actor is tested with. |
| `maxConcurrency` | integer | `1` | Parallel requests. One by default: Employment Hero asks for ten seconds between requests, and the Actor keeps to that whatever is set here. |
| `maxRequestsPerMinute` | integer | `6` | An upper bound on request rate across the whole run. Six by default: Employment Hero's robots.txt asks for ten seconds between requests, which the Actor keeps whatever is set here. If Employment Hero answers 429 (too many requests), the Actor waits as long as it asks, up to two minutes, or a minute when it does not say, and asks again from the same address, at most twice. |

A typical input:

```json
{
  "boards": [
    "https://employmenthero.com/jobs/organisations/employmenthero/"
  ],
  "maxResults": 100
}
```

The prefilled careers page is Employment Hero's own, used here only as an example of a public Employment Hero careers page. This Actor is not affiliated with Employment Hero.

### Output

One row per job. This is a synthetic example in the exact shape the Actor returns:

```json
{
  "schemaVersion": "job.v1",
  "id": "employmenthero:global:5b0c6a8e-2f4d-4c1a-9e7b-3d2f1a0c9b88",
  "source": "employmenthero",
  "market": "global",
  "sourceJobId": "5b0c6a8e-2f4d-4c1a-9e7b-3d2f1a0c9b88",
  "url": "https://employmenthero.com/jobs/position/data-analyst-k7q2xw/",
  "title": "Data Analyst",
  "company": {
    "name": "Example Health Co",
    "sourceCompanyId": "example-health",
    "url": "https://employmenthero.com/jobs/organisations/example-health/"
  },
  "advertiser": {
    "name": "Example Health Co",
    "sourceAdvertiserId": "example-health"
  },
  "location": {
    "raw": "Parramatta, New South Wales 2150",
    "suburb": null,
    "city": "Parramatta",
    "region": "New South Wales",
    "postcode": "2150",
    "country": "AU"
  },
  "workArrangement": "hybrid",
  "employmentTypes": [
    "full_time"
  ],
  "salary": {
    "raw": "$95,000 AUD – $110,000 AUD (Annum)",
    "min": 95000,
    "max": 110000,
    "currency": "AUD",
    "period": "year",
    "includesSuper": null
  },
  "classifications": [
    {
      "category": "Data & Insights",
      "subcategory": null
    }
  ],
  "teaser": null,
  "bulletPoints": [],
  "postedAt": "2026-09-25T03:12:40.000Z",
  "updatedAt": null,
  "expiresAt": null,
  "isPromoted": false,
  "description": null,
  "changeType": "NEW",
  "firstSeenAt": "2026-09-28T21:30:12.000Z",
  "contentHash": "c908a279904cdb5991f29ae00221201225d4fb895f94e44c854b87b4b3326c58",
  "scrapedAt": "2026-09-28T21:30:12.000Z"
}
```

The Output tab has two table views: **Overview** (the fields most people want, flattened) and **Changes** (for incremental runs).

### Incremental mode: monitor new roles across employers

Turn on **Incremental mode** and run the same input on a schedule — daily or weekly. The Actor keeps a small record of what it has seen and every row tells you what happened:

| `changeType` | Meaning |
| --- | --- |
| `NEW` | First time this monitor has seen the job |
| `UPDATED` | Seen before, and the title, employer name, place, country, team, work arrangement, employment type, pay or posted date has changed |
| `REAPPEARED` | Was reported as expired and is back |
| `EXPIRED` | Seen before and no longer on the careers page. One row, once |
| `UNCHANGED` | Only if you turn on **Also save unchanged postings** |

How it behaves, so there are no surprises:

- The first run returns everything as `NEW`. From the second run you pay only for the difference.
- A change is found by comparing what the list says about each job. Employment Hero's list gives no time a job was last edited, so an edit made only to the ad's text is not seen; a new title, place, team, work type or pay is.
- `EXPIRED` is only ever reported by a complete run. If a run hits your result cap or your spending limit, or a careers page cannot be read, nothing is declared expired — a job on a page the run never read is not gone.
- A careers page of up to 100 jobs is read in one response. A larger one is read 100 at a time, newest first, ten seconds apart; a job withdrawn between two of those pages moves the rest up by one, so one job can be missed for a run and is read again by the next.
- Runs share memory when they share a **State key**. Leave it empty and the key is derived from the careers pages and filters themselves, so the same input always continues the same monitor. Name it (`competitor-engineering-roles`) if you want to change filters later without starting again.
- A job not seen for 45 days is forgotten.

### Descriptions and contact details

Full descriptions are off by default. Turn on **Include full descriptions** and each row carries `description.text` (and sanitised `description.html` if you choose that format): the ad's whole text as the employer wrote it. Because Employment Hero returns the text in the same response as the list, this costs no extra requests and no extra time.

Job text sometimes contains a contact's email address or phone number. With **Redact contact details** on — the default — those are replaced with `[redacted]` and `description.contactsRedacted` is `true`; so is a personal profile address (linkedin.com/in/…). Neither `description.text` nor the optional HTML carries the address behind a link: the HTML keeps each link's words and drops its address, and drops images. The Actor never returns anyone's name or contact details as fields, under any setting: it never reads the employer's standing blurb, logos, the position-description attachment or the fields Employment Hero keeps about a signed-in candidate, and the schema has nowhere to put a person. If your use case is contacting individuals, this is the wrong tool.

### What people use it for

- **Hiring signals from the employers that run their HR on Employment Hero.** Which of them are opening which roles, in which teams and countries, and at what pay where they show it. A daily monitor across a list of careers pages is one scheduled run.
- **Pay benchmarking.** Where employers show pay, it comes as numbers with a currency and a period, ready to compare.
- **Job aggregators and alert products.** A clean feed of new jobs from a curated list of employers, deduplicated and labelled by change.
- **Sales and partnership research at company level.** Growth signals from hiring, without collecting anything about the individuals involved.
- **Research and teaching.** A clean, repeatable dataset with a documented schema.

### Using the API

Run it from code with the Apify client, using your own API token:

```javascript
import { ApifyClient } from 'apify-client';

const client = new ApifyClient({ token: process.env.APIFY_TOKEN });
const run = await client.actor('adderleydata/employmenthero-jobs-scraper').call({"boards":["https://employmenthero.com/jobs/organisations/employmenthero/"],"maxResults":100});
const { items } = await client.dataset(run.defaultDatasetId).listItems();
console.log(items.length, items[0]?.salary);
```

```python
import os
from apify_client import ApifyClient

client = ApifyClient(os.environ["APIFY_TOKEN"])
run = client.actor("adderleydata/employmenthero-jobs-scraper").call(run_input={"boards":["https://employmenthero.com/jobs/organisations/employmenthero/"],"maxResults":100})
for item in client.dataset(run["defaultDatasetId"]).iterate_items():
    print(item["title"], item["location"]["raw"], item["salary"]["raw"])
```

Schedules, webhooks and the Make, Zapier, n8n and Google Sheets integrations all work the way they do for any Apify Actor. The Actor runs with limited permissions and is priced per event, so AI agents can call it through Apify's MCP server as well.

### Is it legal to scrape Employment Hero careers pages?

The Actor reads the same list of open jobs that an employer's public Employment Hero careers page reads when anyone opens it, with no login and no key, at the pace Employment Hero's robots.txt asks for, and returns facts about job postings. It does not log in, does not solve CAPTCHAs, does not submit applications and does not collect personal information. Pay an employer hides in Employment Hero's pay fields is never read.

What you do with the data is your responsibility. Employment Hero's platform terms limit how information from its websites may be used, and each employer's job text is its own copyright — analyse it, do not republish it. If your project touches personal information, privacy law applies to you wherever you are. This is general information, not legal advice.

### Questions

**Where do I find an employer's Employment Hero careers page?** Employers link it from their own websites, often as "Careers" or "Jobs". Its address starts `employmenthero.com/jobs/organisations/`. Paste the whole link, or just the name after `organisations/`.

**I have a link to one job.** A job's link (`employmenthero.com/jobs/position/…`) does not say which employer posted it, so the Actor cannot tell which careers page to read from one and asks for the careers page instead. The job's page links to it ("View all jobs").

**The employer's jobs are on its own domain.** A careers section on an employer's own website does not say which Employment Hero organisation is behind it, so the Actor asks for the Employment Hero careers page instead of guessing, and never sends a request to a domain Employment Hero does not run.

**A careers page I gave came back as "not found".** Employment Hero answers "Organisation not found" when it has no organisation of that name. The run log names it, and the other careers pages in the run are unaffected. A missing page is asked for once, not retried, and a run in which every page is missing finishes with an empty dataset and a message naming each one. Names are matched exactly as Employment Hero writes them, in lower case.

**Why is `salary` empty for most jobs?** Many employers choose to hide pay on Employment Hero. The Actor returns pay only where the employer shows it, or where the ad's own text states a range.

**Why is a job's `city` empty when `location.raw` names a place?** The place was a single name without a postcode, such as "Manila" or "Quebec", which may be a city or a region, so the Actor does not guess. `location.raw` and `location.country` still carry it.

**Why is it slower than your other jobs Actors?** Employment Hero's robots.txt asks for ten seconds between requests, and the Actor keeps to it whatever **Maximum concurrency** and **Maximum requests per minute** say. One request covers up to 100 jobs, so fifty employers take a little over eight minutes. If Employment Hero answers 429 (too many requests), the Actor waits as long as it asks, up to two minutes, or a minute when it does not say, and asks again from the same address, at most twice. A careers page still refused after that, or asked to wait longer, fails with a message naming it, the other pages are kept, and the run summary counts the refusals under `http.tooManyRequests`. The Actor never switches to another address to get round a limit.

**Does it need an Employment Hero login or API key?** No. The careers page's list is public.

**Can I get recruiter emails or phone numbers?** No, by design.

**How current is the data?** It is read from Employment Hero while your run is in progress. `postedAt` is when the job went up; `scrapedAt` records when the row was produced.

**The field I need is not there.** Open an issue on the **Issues** tab. Fields are added to the schema without breaking existing ones.

### Support

Use the **Issues** tab on this page. We read it every day. Include the run ID and what you expected to see.

### Other Adderley Data Actors

Every Actor in a vertical returns the same fields, so adding a source needs no new code on your side.

- [Ashby Jobs Scraper — Company Job Boards](https://apify.com/adderleydata/ashby-jobs-scraper) — same `job.v1` fields
- [BambooHR Jobs Scraper — Company Job Boards](https://apify.com/adderleydata/bamboohr-jobs-scraper) — same `job.v1` fields
- [Breezy HR Jobs Scraper — Company Job Boards](https://apify.com/adderleydata/breezy-jobs-scraper) — same `job.v1` fields
- [Career Site Jobs Scraper — Greenhouse, Lever, Workday](https://apify.com/adderleydata/career-site-jobs-scraper) — same `job.v1` fields
- [Dayforce Jobs Scraper — Company Job Boards](https://apify.com/adderleydata/dayforce-jobs-scraper) — same `job.v1` fields
- [Greenhouse Jobs Scraper — Company Job Boards](https://apify.com/adderleydata/greenhouse-jobs-scraper) — same `job.v1` fields
- [JazzHR Jobs Scraper — Company Job Boards](https://apify.com/adderleydata/jazzhr-jobs-scraper) — same `job.v1` fields
- [JobAdder Jobs Scraper — Agency and Employer Job Boards](https://apify.com/adderleydata/jobadder-jobs-scraper) — same `job.v1` fields
- [Lever Jobs Scraper — Company Job Boards](https://apify.com/adderleydata/lever-jobs-scraper) — same `job.v1` fields
- [Personio Jobs Scraper — Company Job Boards](https://apify.com/adderleydata/personio-jobs-scraper) — same `job.v1` fields
- [Pinpoint Jobs Scraper — Company Job Boards](https://apify.com/adderleydata/pinpoint-jobs-scraper) — same `job.v1` fields
- [Recruitee Jobs Scraper — Company Job Boards](https://apify.com/adderleydata/recruitee-jobs-scraper) — same `job.v1` fields
- [RemoteOK Jobs Scraper — Remote Job Feed](https://apify.com/adderleydata/remoteok-jobs-scraper) — same `job.v1` fields
- [Rippling Jobs Scraper — Company Job Boards](https://apify.com/adderleydata/rippling-jobs-scraper) — same `job.v1` fields
- [Workable Jobs Scraper — Company Job Boards](https://apify.com/adderleydata/workable-jobs-scraper) — same `job.v1` fields
- [Workday Jobs Scraper — Company Job Boards](https://apify.com/adderleydata/workday-jobs-scraper) — same `job.v1` fields

### About

Made by Adderley Data, Melbourne — https://adderleydata.com. Not affiliated with, endorsed by or sponsored by Employment Hero. Employment Hero is a trade mark of its owner and is used here only to describe what this Actor reads.

# Changelog

This Actor's version history is a separate document: https://apify.com/adderleydata/employmenthero-jobs-scraper/changelog.md

# Actor input Schema

## `boards` (type: `array`):

One entry per employer: its Employment Hero careers page link (https://employmenthero.com/jobs/organisations/acme-health/), its page on Employment Hero Jobs, or the organisation's name in the link ("acme-health"). Up to 500 pages per run; each is one request for up to 100 open postings, their full text included.

## `keywords` (type: `array`):

Keep postings whose title contains every word of any keyword, in any order — "engineer data" matches "Senior Data Engineer". Leave empty for all titles.

## `locations` (type: `array`):

Keep postings whose place contains this text ("Sydney", "New South Wales", "2000"), that are in this country ("Australia", "United Kingdom", "NZ"), or that the employer marks "Remote", "Hybrid" or "On-site". Leave empty for all locations.

## `teams` (type: `array`):

Keep postings whose team, as the employer names it, contains this text, e.g. "Engineering" or "Sales". Leave empty for all teams.

## `workTypes` (type: `array`):

Keep postings whose employment type or term, as the employer sets it, contains this text: "Full-time", "Part-time", "Casual", "Permanent", "Contract", "Temporary". Leave empty for all work types.

## `postedWithinDays` (type: `integer`):

Keep postings that went up within the last N days, counted back from the start of the run. Leave empty for any time.

## `maxResults` (type: `integer`):

The run stops once this many postings are saved. You are charged per posting saved, so this is also your cost cap.

## `includeDescription` (type: `boolean`):

On: each row carries the ad's full text. It arrives in the same request as the list, so it costs no extra request and no extra time. Off: the facts only.

## `descriptionFormat` (type: `string`):

Plain text, or plain text plus sanitised HTML.

## `redactContacts` (type: `boolean`):

On by default: email addresses and phone numbers inside description text are replaced with \[redacted]. This Actor never outputs anyone's name or contact fields.

## `incremental` (type: `boolean`):

Remember what earlier runs saw and save only postings that are new, changed or gone. Unchanged postings are skipped and not charged. Put the Actor on a schedule with this on.

## `stateKey` (type: `string`):

Optional name for this monitor, e.g. "competitor-engineering-roles". Runs with the same key share memory. Left empty, a key is derived from the careers pages and filters themselves.

## `emitExpired` (type: `boolean`):

Incremental mode only. When a complete run no longer finds a posting it saw before, save one row with changeType EXPIRED.

## `emitUnchanged` (type: `boolean`):

Incremental mode only. Saves (and charges for) every posting, labelled UNCHANGED where nothing moved.

## `proxyConfiguration` (type: `object`):

Apify Proxy, automatic group, is the default and is what this Actor is tested with.

## `maxConcurrency` (type: `integer`):

Parallel requests. One by default: Employment Hero asks for ten seconds between requests, and the Actor keeps to that whatever is set here.

## `maxRequestsPerMinute` (type: `integer`):

An upper bound on request rate across the whole run. Six by default: Employment Hero's robots.txt asks for ten seconds between requests, which the Actor keeps whatever is set here. If Employment Hero answers 429 (too many requests), the Actor waits as long as it asks, up to two minutes, or a minute when it does not say, and asks again from the same address, at most twice.

## Actor input object example

```json
{
  "boards": [
    "https://employmenthero.com/jobs/organisations/employmenthero/"
  ],
  "maxResults": 20,
  "includeDescription": false,
  "descriptionFormat": "text",
  "redactContacts": true,
  "incremental": false,
  "emitExpired": true,
  "emitUnchanged": false,
  "proxyConfiguration": {
    "useApifyProxy": true
  },
  "maxConcurrency": 1,
  "maxRequestsPerMinute": 6
}
```

# Actor output Schema

## `postings` (type: `string`):

One row per posting in the job.v1 schema; every key is always present and unknown is null. In incremental runs each row carries a changeType of NEW, UPDATED, REAPPEARED or EXPIRED.

## `runSummary` (type: `string`):

Requests made, first-attempt success, rows saved and skipped, rows that failed validation, and contact details redacted.

# API

You can run this Actor programmatically using our API. Below are code examples in JavaScript, Python, and CLI, as well as the OpenAPI specification and MCP server setup.

## JavaScript example

```javascript
import { ApifyClient } from 'apify-client';

// Initialize the ApifyClient with your Apify API token
// Replace the '<YOUR_API_TOKEN>' with your token
const client = new ApifyClient({
    token: '<YOUR_API_TOKEN>',
});

// Prepare Actor input
const input = {
    "boards": [
        "https://employmenthero.com/jobs/organisations/employmenthero/"
    ],
    "maxResults": 20,
    "proxyConfiguration": {
        "useApifyProxy": true
    }
};

// Run the Actor and wait for it to finish
const run = await client.actor("adderleydata/employmenthero-jobs-scraper").call(input);

// Fetch and print Actor results from the run's dataset (if any)
console.log('Results from dataset');
console.log(`💾 Check your data here: https://console.apify.com/storage/datasets/${run.defaultDatasetId}`);
const { items } = await client.dataset(run.defaultDatasetId).listItems();
items.forEach((item) => {
    console.dir(item);
});

// 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/js/docs

```

## Python example

```python
from apify_client import ApifyClient

# Initialize the ApifyClient with your Apify API token
# Replace '<YOUR_API_TOKEN>' with your token.
client = ApifyClient("<YOUR_API_TOKEN>")

# Prepare the Actor input
run_input = {
    "boards": ["https://employmenthero.com/jobs/organisations/employmenthero/"],
    "maxResults": 20,
    "proxyConfiguration": { "useApifyProxy": True },
}

# Run the Actor and wait for it to finish
run = client.actor("adderleydata/employmenthero-jobs-scraper").call(run_input=run_input)

# Fetch and print Actor results from the run's dataset (if there are any)
print(f"💾 Check your data here: https://console.apify.com/storage/datasets/{run.default_dataset_id}")
for item in client.dataset(run.default_dataset_id).iterate_items():
    print(item)

# 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/python/docs/quick-start

```

## CLI example

```bash
echo '{
  "boards": [
    "https://employmenthero.com/jobs/organisations/employmenthero/"
  ],
  "maxResults": 20,
  "proxyConfiguration": {
    "useApifyProxy": true
  }
}' |
apify call adderleydata/employmenthero-jobs-scraper --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,adderleydata/employmenthero-jobs-scraper"
        }
    }
}
```

The hosted server signs you in with OAuth on first connect, so no API token belongs in this config. Clients without OAuth support can send an `Authorization: Bearer <APIFY_API_TOKEN>` header instead, using a token from API & Integrations in Apify Console (https://console.apify.com/settings/integrations).

## OpenAPI specification

Download the OpenAPI definition: https://api.apify.com/v2/actors/DltOkyMVRfH9psDtE/builds/BYe9wnzEGtkoA7bnp/openapi.json
