Breezy HR Jobs Scraper — Company Job Boards avatar

Breezy HR Jobs Scraper — Company Job Boards

Pricing

from $1.45 / 1,000 posting saveds

Go to Apify Store
Breezy HR Jobs Scraper — Company Job Boards

Breezy HR Jobs Scraper — Company Job Boards

Every open role on any company's Breezy HR career portal as structured data: title, department, every location with country, work type, pay where stated, published date, full text on request. One request per board, one stable schema. Incremental mode charges only for what changed. No personal data.

Pricing

from $1.45 / 1,000 posting saveds

Rating

0.0

(0)

Developer

Adderley Data

Adderley Data

Maintained by Community

Actor stats

0

Bookmarked

2

Total users

1

Monthly active users

6 hours ago

Last modified

Share

What does Breezy HR Jobs Scraper do?

Breezy HR Jobs Scraper reads every open role on any company's public Breezy HR career portal and returns it as structured data you can load straight into a spreadsheet, a database or a model. Companies that recruit with Breezy HR publish their open roles on a career portal at <company>.breezy.hr; if a careers page links there, this Actor reads it.

Give it a subdomain (acme) or any breezy.hr link for the company, and for each position you get the title, the department, every location it lists with its city, state and country, the employment type and work arrangement as the company set them, the pay the company entered, the date the position was published, and a link to the posting — and, if you ask for it, the full text of the posting.

What makes it different:

  • One request per board. Every fact about every position on a board comes back in a single call to the career portal's own JSON list, the one the portal publishes for its job list. A thousand positions across fifty companies is fifty requests. The posting text lives only on each posting's page, so that page is read only when you ask for descriptions.
  • Pay as numbers. Where a company enters pay in Breezy HR, it comes back as min, max, currency and period, with Breezy HR's own wording ($60,000 – $70,000 / year) kept beside them. Where no pay is entered, the fields are null; nothing is estimated.
  • Incremental mode. Put the Actor on a schedule and each run returns only positions that are new, changed, back again or gone. Breezy HR publishes no last-modified time, so the Actor compares each position with the last run — title, locations, department, employment type, work arrangement, pay and published date. Unchanged positions 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 position. The schema is versioned (job.v1) and every Adderley Data jobs Actor uses it, so a Breezy HR board, a Lever board and a job board sit in the same table.
  • No personal data. The career portal's list has no recruiter fields and this Actor has nowhere to put one. The company logo and office street addresses are never returned. Contact details inside posting text are redacted by default.
  • 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 Breezy HR data can you extract?

FieldWhat it holds
idStable across runs: source:market:sourceJobId. Use it as your primary key.
titleJob title as listed.
company.nameThe hiring company, where the listing names one.
advertiser.nameThe business that placed the listing — often a recruitment agency. Never a person.
location.rawLocation text as listed. Several locations are joined with |.
location.suburbSuburb, when the listing states one.
location.cityCity or area, when the listing states one.
location.regionState or region, e.g. VIC.
location.postcodePostcode, when the source provides it.
location.countryISO 3166-1 alpha-2 country code.
workArrangementon_site, hybrid, remote or unknown.
employmentTypesNormalised: full_time, part_time, contract, casual, temporary, internship, volunteer.
salary.rawThe salary text exactly as shown, or null when the listing shows none.
salary.minLower bound as a number, when the text contains one.
salary.maxUpper bound as a number. Equal to min for a single figure.
salary.currencyISO 4217. Taken from the text, otherwise the market default.
salary.periodhour, day, week, month or year; null when the text does not say.
salary.includesSupertrue / false when the text says so ("plus super", "inc. super"); otherwise null.
classificationsThe source's category and subcategory pairs.
teaserThe short summary shown on the results page.
bulletPointsSelling points shown on the results page.
postedAtWhen the listing was posted, ISO 8601 UTC.
updatedAtThe source's own last-modified time, ISO 8601 UTC. Published by ATS and API sources; null where the site does not show one.
expiresAtExpiry, ISO 8601 UTC, where the source states one.
isPromotedtrue for paid placements. A listing shown both promoted and organic is returned once.
urlLink to the listing.
descriptionNull unless requested. text, optional sanitised html, and contactsRedacted.
changeTypeIncremental runs: NEW, UPDATED, REAPPEARED, EXPIRED (or UNCHANGED if you ask for those). Otherwise null.
firstSeenAtIncremental runs: when this monitor first saw the listing.
contentHashSHA-256 over the fields that define a change. Compare it to detect edits yourself.
scrapedAtWhen this row was produced, ISO 8601 UTC.
sourceSource key, e.g. seek.
marketMarket key, e.g. au, nz.
sourceJobIdThe source's own identifier for the listing.
company.sourceCompanyIdThe source's identifier for the company, when exposed.
company.urlThe company's page on the source site, when exposed.
advertiser.sourceAdvertiserIdThe source's identifier for the advertiser.
schemaVersionAlways job.v1. Breaking changes ship as job.v2 in a new Actor version, never silently.

How Breezy HR's fields fill the schema:

  • company.name is the company name the career portal shows. company.sourceCompanyId is the board's subdomain and company.url its career portal.
  • location.raw lists every location the position names, the primary one first, joined with |. location.city, location.region (the state's full name) and location.country are the primary location's own fields in Breezy HR, not words split out of a label. location.country is Breezy HR's ISO country code; a position open "Worldwide" has none.
  • classifications holds Breezy HR's department as category. Breezy HR has no second level, so subcategory is null.
  • employmentTypes comes from Breezy HR's position type ("Full-Time", "Part-Time", "Contract", "Temporary", "Internship"). workArrangement comes from the location's remote setting: fully remote, hybrid, or not remote (on_site).
  • salary.raw is the pay exactly as Breezy HR prints it. Breezy HR's list prints the currency's sign, not its code: a bare $ in a position located in the United States, Canada, Australia, New Zealand, Singapore or Hong Kong is read as that country's dollar; a bare $ anywhere else, or "Worldwide", leaves currency as null. When you ask for descriptions and the posting's page states the same pay with its currency code, that code is used. $3,500+ / year is a minimum, so max is null.
  • postedAt is when the position was published on Breezy HR. updatedAt is always null: Breezy HR does not publish a last-modified time.
  • market is always global: a Breezy HR board is the company's, not a country's.

How much does it cost to scrape Breezy HR job boards?

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

Apify planPricePer 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 runCost (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 do not change the price per position. They do take one more request per position saved, so a run with descriptions takes longer than one without. Use incremental mode for anything you run more than once — after the first run you pay only for what changed, and no posting page is read for a position that has not changed.

How to scrape a Breezy HR job board

  1. Find the company's board. On its careers page, the job list or an "Apply" link points at <company>.breezy.hr. The subdomain is what you need; the whole link works too.
  2. Open the Actor in Apify Console and go to the Input tab. Paste one subdomain or link per line into Job boards. Up to 500 boards per run.
  3. Optionally filter: Title keywords, Locations, Departments, Published 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 position that matches more than one board or filter is returned once.

Input

FieldTypeDefaultWhat it does
boardsarray—One entry per company: the subdomain (the first part of https://acme.breezy.hr is "acme"), or any breezy.hr link for the company — its career portal, a posting or the /json feed. Up to 500 boards per run; each is one request.
keywordsarray—Keep positions whose title contains every word of any keyword, in any order — "engineer data" matches "Senior Data Engineer". Leave empty for all titles.
locationsarray—Keep positions with a location containing this text ("Austin", "Toronto"), with a location in this country by Breezy HR's own country code ("Australia", "United States"), or with this work arrangement ("Remote", "Hybrid", "On-site"). Leave empty for all locations.
departmentsarray—Keep positions whose department contains this text, e.g. "Engineering". Leave empty for all departments.
postedWithinDaysinteger—Keep positions published on Breezy HR in the last N days. Leave empty for any time.
maxResultsinteger100The run stops once this many positions are saved. You are charged per position saved, so this is also your cost cap.
includeDescriptionbooleanfalseOn: the full posting text comes back with each row, read from the posting's own page — one extra request per position saved. Off: listing fields only, one request per board.
descriptionFormattext, text_and_html"text"Plain text, or plain text plus sanitised HTML.
redactContactsbooleantrueOn by default: email addresses and phone numbers inside description text are replaced with [redacted]. This Actor never outputs recruiter names or contact fields.
incrementalbooleanfalseRemember what earlier runs saw and save only positions that are new, changed or gone. Unchanged positions are skipped and not charged. Put the Actor on a schedule with this on.
stateKeystring—Optional name for this monitor, e.g. "competitor-engineering". Runs with the same key share memory. Left empty, a key is derived from the boards and filters themselves.
emitExpiredbooleantrueIncremental mode only. When a complete run no longer finds a position it saw before, save one row with changeType EXPIRED.
emitUnchangedbooleanfalseIncremental mode only. Saves (and charges for) every position, labelled UNCHANGED where nothing moved.
proxyConfigurationobject{"useApifyProxy":true}Apify Proxy, automatic group, is the default and is what this Actor is tested with.
maxConcurrencyinteger4Parallel requests. The default is deliberately modest.
maxRequestsPerMinuteinteger90An upper bound on request rate across the whole run.

A typical input:

{
"boards": [
"breezy"
],
"maxResults": 100
}

Output

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

{
"schemaVersion": "job.v1",
"id": "breezy:global:7d0c9e52a1b3",
"source": "breezy",
"market": "global",
"sourceJobId": "7d0c9e52a1b3",
"url": "https://example-freight.breezy.hr/p/7d0c9e52a1b3-data-analyst",
"title": "Data Analyst",
"company": {
"name": "Example Freight Co",
"sourceCompanyId": "example-freight",
"url": "https://example-freight.breezy.hr"
},
"advertiser": {
"name": "Example Freight Co",
"sourceAdvertiserId": "example-freight"
},
"location": {
"raw": "Melbourne, VIC | Sydney, NSW",
"suburb": null,
"city": "Melbourne",
"region": "Victoria",
"postcode": null,
"country": "AU"
},
"workArrangement": "hybrid",
"employmentTypes": [
"full_time"
],
"salary": {
"raw": "$95,000 – $110,000 / year",
"min": 95000,
"max": 110000,
"currency": "AUD",
"period": "year",
"includesSuper": null
},
"classifications": [
{
"category": "Data",
"subcategory": null
}
],
"teaser": null,
"bulletPoints": [],
"postedAt": "2026-09-20T22:14:05.000Z",
"updatedAt": null,
"expiresAt": null,
"isPromoted": false,
"description": null,
"changeType": "NEW",
"firstSeenAt": "2026-09-21T19:30:12.000Z",
"contentHash": "af434bcf002a635efcc0b3af2e4acae1416b1719fbfa4c94348f5ccc3620e2c5",
"scrapedAt": "2026-09-21T19: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 companies

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

changeTypeMeaning
NEWFirst time this monitor has seen the position
UPDATEDSeen before, and the title, locations, department, employment type, work arrangement, pay or published date has changed
REAPPEAREDWas reported as expired and is back
EXPIREDSeen before and no longer on the board. One row, once
UNCHANGEDOnly if you turn on Also save unchanged positions

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 the facts in the career portal's list. That list carries no posting text, so an edit to the text alone is not reported. A position the company publishes again with a new published date is reported as UPDATED.
  • EXPIRED is only ever reported by a complete run. If a run hits your result cap or your spending limit, or a board cannot be read, nothing is declared expired — a position on a board the run never read is not gone.
  • Each board is read in one response, so a run compares whole boards, never pages read seconds apart.
  • Runs share memory when they share a State key. Leave it empty and the key is derived from the boards and filters themselves, so the same input always continues the same monitor. Name it (competitor-engineering) if you want to change filters later without starting again.
  • A position 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 posting text from the position's own page. Where the page carries the structured copy Breezy HR publishes for search engines, the text is read from there; otherwise from the page's description section. Every other field comes from the list, with two exceptions, both about pay. Where the page's structured copy states the same pay as the list with its currency code, that code is used — Breezy HR's list prints only $. And where the company entered no pay and the posting's text states a range, a run with descriptions reads that range from its sentence.

If a position closes between the list and its page, Breezy HR sends the page request to the board's home page. The row is saved without its text and the run log says so.

Postings sometimes contain a recruiter'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 recruiter names or contact details as fields, under any setting: the career portal's list has no such fields, and the schema has nowhere to put one. If your use case is contacting individuals, this is the wrong tool.

What people use it for

  • Competitor and market hiring signals. Which companies are opening which roles, in which departments and locations, and how often — a daily monitor across a list of boards is one scheduled run.
  • Pay-transparency datasets. Entered pay across companies, roles and locations, as numbers with their period, and Breezy HR's own wording kept.
  • Job aggregators and alert products. A clean feed of new positions 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:

import { ApifyClient } from 'apify-client';
const client = new ApifyClient({ token: process.env.APIFY_TOKEN });
const run = await client.actor('adderleydata/breezy-jobs-scraper').call({"boards":["breezy"],"maxResults":100});
const { items } = await client.dataset(run.defaultDatasetId).listItems();
console.log(items.length, items[0]?.location);
import os
from apify_client import ApifyClient
client = ApifyClient(os.environ["APIFY_TOKEN"])
run = client.actor("adderleydata/breezy-jobs-scraper").call(run_input={"boards":["breezy"],"maxResults":100})
for item in client.dataset(run["defaultDatasetId"]).iterate_items():
print(item["title"], item["location"]["raw"], item["postedAt"])

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.

The Actor reads the public pages of a company's Breezy HR career portal — the job list the portal publishes as JSON and, when you ask for descriptions, each posting's public page — with no login, no key and no rate tricks, and returns facts about job postings. It does not log in, does not solve CAPTCHAs, and does not collect personal information.

What you do with the data is your responsibility. Each company's postings are its own copyright — analyse them, do not republish them. 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 a company's subdomain? On its careers page, follow any job's link: the address is <subdomain>.breezy.hr/p/<posting>. Paste either the whole link or just the subdomain. Case does not matter.

The company's careers page is on its own domain. Some companies show their Breezy HR jobs on their own website. If a job's link leads to a breezy.hr address, that address is what this Actor needs. A careers page on the company's own domain is not read.

A board I gave came back as "not found". Breezy HR answers "Career portal not found" when no company uses that subdomain, or when a company has stopped hiring through Breezy HR. The run log names the board, and the other boards in the run are unaffected. A missing board is asked for once, not retried, and a run in which every board is missing finishes with an empty dataset and a message naming each one.

Does it need a Breezy HR login or API key? No. The career portal is public.

Why is currency sometimes null when there is pay? Breezy HR's list prints $ without saying which dollar. The Actor reads it as the dollar of the position's own country where that country uses a dollar, and otherwise leaves it null rather than guess. With descriptions on, the posting's page often states the currency code, and that code is used.

Why is employmentTypes sometimes empty? Breezy HR's position type "Other" states no employment type, so none is given — unless the title says "Intern" or "Contractor", which is taken at its word. Nothing else is guessed.

Why is workArrangement sometimes unknown? Positions created before Breezy HR recorded a remote setting carry none. For those, only a location that says "Remote" or "Hybrid" counts.

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

How current is the data? It is read from Breezy HR while your run is in progress. postedAt is when the position was published; 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.

About

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