JazzHR Jobs Scraper — Company Job Boards
Pricing
from $1.45 / 1,000 posting saveds
JazzHR Jobs Scraper — Company Job Boards
Every open role on any company's JazzHR career page (applytojob.com) as structured data: title, department, location, employment type, and the date, pay and postcode where the posting publishes them, full text on request. 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
Maintained by CommunityActor stats
0
Bookmarked
2
Total users
1
Monthly active users
6 hours ago
Last modified
Categories
Share
What does JazzHR Jobs Scraper do?
JazzHR Jobs Scraper reads every open role on any company's public JazzHR career page and returns it as structured data you can load straight into a spreadsheet, a database or a model. Companies that hire through JazzHR publish their roles at <company>.applytojob.com; if a careers page's "Apply" button leads there, this Actor reads it.
Give it a subdomain (acme) or any applytojob.com link for the company, and for each posting you get the title, the department, the location as JazzHR prints it, the employment type, the company's name and a link to the posting — and, where the posting page publishes them, the date it was posted, the pay the company entered and the postcode. If you ask for it, you also get the full text of the posting.
What makes it different:
- Two kinds of request, both counted. JazzHR publishes no feed of one company's postings, so the Actor reads the company's career page — one request lists every posting on the board. Then one request per posting you save reads that posting's own page, because the list carries no employment type, date or pay. Both are ordinary public pages, read at the Actor's default of 90 requests a minute.
- Both career-page layouts. Most JazzHR career pages use the current layout; some companies still show the older one. The Actor reads both, and fails with a clear message on a page that is neither.
- Pay as numbers. Where a company enters pay in JazzHR, the posting page publishes it in its structured data and it comes back as
min,max,currencyandperiod. Where it does not, but the posting's text states a range, that range is read and its sentence kept. Where no range is stated anywhere, the fields arenull; nothing is estimated. - Incremental mode. Put the Actor on a schedule and each run returns only postings that are new, changed, back again or gone. Unchanged postings are skipped, not charged, and cost no request for their page.
- One stable schema. Every row has every field, every time. Unknown is
null, never a missing key, so nothing downstream breaks on a sparse posting. The schema is versioned (job.v1) and every Adderley Data jobs Actor uses it, so a JazzHR board, a Lever board and a job board sit in the same table. - No personal data. Neither page the Actor reads names a recruiter, and the Actor has nowhere to put one. The application form on each posting page is never read. Contact details inside posting text are redacted by default.
- Bounded cost. You set a maximum number of results; the run stops there, and no posting page is requested for a posting that will not be saved. It also stops at the spending limit you set on the run in Apify.
What JazzHR 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 JazzHR's pages fill the schema:
company.nameis the company name the career page shows.company.sourceCompanyIdis the board's subdomain andcompany.urlits career page.location.rawis the location exactly as JazzHR prints it: city, state and country, with the country left out for places in the United States (Wichita, KS,Rome, Italy,Baturaja, Sumatera Selatan, Indonesia,Mexico,Remote).location.cityandlocation.regionare read from those positions only when the label's shape is plain.location.postcodecomes from the posting page's structured address, where it publishes one.location.countryis filled only where JazzHR states a country: the label names one, or a remote posting's page names the country applicants must be in. A label such asWichita, KSnames no country — a two-letter state does not say which country it is in — socountryisnullthere. Filter onlocation.rawif you need the state.workArrangementisremotewhere JazzHR lists the posting as "Remote" or its page marks it for telecommuting, andhybridwhere the printed location says "Hybrid". JazzHR records no on-site setting, so every other posting isunknown.classificationsholds the department the career page lists ascategory. JazzHR has no second level, sosubcategoryisnull.employmentTypescomes from the posting page's "Type". The labels we have read are "Full Time", "Part Time", "Part Time to Full Time" (both types) and "Contracted" (contract).postedAtis the posting date JazzHR publishes in the page's structured data for search engines, in UTC. JazzHR publishes that data on some postings and not others (17 of the 30 we read in September 2026), and where it does not,postedAtisnull: the Actor never makes a date up.salary: the pay the company entered in JazzHR, as the posting page's structured data states it, withsalary.rawin words (150,000 - 175,000 USD per year).updatedAtandexpiresAtare alwaysnull: JazzHR publishes no last-modified time, and the end date in its structured data is its own 90 days after the posting date, not a closing date the company set.marketis alwaysglobal: a JazzHR board is the company's, not a country's.
How much does it cost to scrape JazzHR job boards?
You pay per posting 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 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: the text is on the posting's page, which is requested for every saved posting anyway. Use incremental mode for anything you run more than once — after the first run you pay only for what changed.
How to scrape a JazzHR job board
- Find the company's board. On its careers page, the job list or an "Apply" link points at
<company>.applytojob.com/apply. The subdomain is what you need; the whole link works too, including a link to one posting. - 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.
- Optionally filter: Title keywords, Locations, Departments. Filters are applied to the board's list, so a posting they leave out costs no request.
- Set Maximum results. This is also your cost cap.
- 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 posting that matches more than one board or filter is returned once.
Input
| Field | Type | Default | What it does |
|---|---|---|---|
boards | array | — | One entry per company: the subdomain (the first part of https://acme.applytojob.com/apply is "acme"), or any applytojob.com link for the company — its career page or a posting. Up to 500 boards per run; each is one request for its list of postings, plus one request per posting saved. |
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 location contains this text ("Austin", "TX"), whose location names this country ("Canada", "Australia"), or that JazzHR lists as "Remote". Leave empty for all locations. |
departments | array | — | Keep postings whose department contains this text, e.g. "Engineering". Postings without a department are left out when this is set. Leave empty for all departments. |
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. Each posting saved is also one request for its details. |
includeDescription | boolean | false | On: the posting's full text comes back with each row, from the request already made for the posting's details, so it costs no extra requests. Off: listing fields 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 recruiter names 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, not charged and cost no request for their details. Put the Actor on a schedule with this on. |
stateKey | string | — | 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. |
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 | 4 | Parallel requests. The default is deliberately modest. |
maxRequestsPerMinute | integer | 90 | An upper bound on request rate across the whole run. |
A typical input:
{"boards": ["mra"],"maxResults": 100}
The prefilled board, mra, is one company's public JazzHR career page, used here only as an example of a public JazzHR board. This Actor is not affiliated with that company. The prefill saves 20 postings, so a trial run reads 21 pages.
Output
One row per posting. This is a synthetic example in the exact shape the Actor returns:
{"schemaVersion": "job.v1","id": "jazzhr:global:Fr8tDa7a01","source": "jazzhr","market": "global","sourceJobId": "Fr8tDa7a01","url": "https://example-freight.applytojob.com/apply/Fr8tDa7a01/Data-Analyst","title": "Data Analyst","company": {"name": "Example Freight Co","sourceCompanyId": "example-freight","url": "https://example-freight.applytojob.com/apply"},"advertiser": {"name": "Example Freight Co","sourceAdvertiserId": "example-freight"},"location": {"raw": "Melbourne, Victoria, Australia","suburb": null,"city": "Melbourne","region": "Victoria","postcode": "3000","country": "AU"},"workArrangement": "unknown","employmentTypes": ["full_time"],"salary": {"raw": "95,000 - 110,000 AUD per year","min": 95000,"max": 110000,"currency": "AUD","period": "year","includesSuper": null},"classifications": [{"category": "Data","subcategory": null}],"teaser": null,"bulletPoints": [],"postedAt": "2026-09-20T00:00:00.000Z","updatedAt": null,"expiresAt": null,"isPromoted": false,"description": null,"changeType": "NEW","firstSeenAt": "2026-09-21T19:30:12.000Z","contentHash": "0dac5ea8c92f2cb1750fba3f8c2b0b491332a062a967314a6f35a02ce9a8c8c1","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:
changeType | Meaning |
|---|---|
NEW | First time this monitor has seen the posting |
UPDATED | Seen before, and its title, its location or its department on the career page has changed |
REAPPEARED | Was reported as expired and is back |
EXPIRED | Seen before and no longer on the board. 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. - Change detection runs on the career page's list — each posting's title, location and department — before any posting's page is requested. That is what keeps an unchanged posting free: no charge and no request. The cost is that an edit only to a posting's own page — its text, pay, type or date — is not reported as
UPDATED. When a posting is saved for any other reason, its row carries the page's current facts. - The order of the list is not a change.
EXPIREDis 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 posting on a board the run never read is not gone.- 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 posting 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's text from its own page, ending where JazzHR's application form begins. Because the text is on the page that is requested for every saved posting anyway, it costs no extra requests.
If a posting is put on hold between the list and its page, JazzHR answers that hiring for it is on hold. The row is saved with what the list said — title, location, department and link — 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: it reads no such fields from JazzHR, never reads the application form, 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 places, and how often — a daily monitor across a list of boards is one scheduled run.
- Pay-transparency datasets. Entered pay across companies and roles, as numbers with their currency and period.
- Job aggregators and alert products. A clean feed of new postings 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/jazzhr-jobs-scraper').call({"boards":["mra"],"maxResults":100});const { items } = await client.dataset(run.defaultDatasetId).listItems();console.log(items.length, items[0]?.employmentTypes);
import osfrom apify_client import ApifyClientclient = ApifyClient(os.environ["APIFY_TOKEN"])run = client.actor("adderleydata/jazzhr-jobs-scraper").call(run_input={"boards":["mra"],"maxResults":100})for item in client.dataset(run["defaultDatasetId"]).iterate_items():print(item["title"], item["location"]["raw"], item["employmentTypes"])
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 JazzHR job boards?
The Actor reads the public pages of a company's JazzHR career page — the job list and each posting's page, paths JazzHR's robots.txt allows for crawlers in general — with no login, no key and no rate tricks, at a modest default rate, and returns facts about job postings. It does not log in, does not solve CAPTCHAs, does not submit or read the application form, 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>.applytojob.com/apply/<posting id>/<title>. Paste either the whole link or just the subdomain. Case does not matter.
Why does each saved posting cost a request? JazzHR's list of postings carries titles, places and departments only. The employment type, the date and the pay are on each posting's own page, so the Actor reads that page once for every posting it saves — and never for a posting a filter, the result cap or incremental mode leaves out. At the default of 90 requests a minute, 100 new postings take a little over a minute.
Can I filter by the date a posting was published? Not inside the run: the list has no dates, and many postings publish none. Every row carries postedAt where JazzHR states it; filter on it in your dataset or with a dataset view.
Why is location.country often null? JazzHR prints a place in the United States as city and state only (Austin, TX) and does not print the country. The Actor fills country only where JazzHR states one, so for those postings it stays null; location.raw and location.region keep the state.
A board I gave came back as "not found". JazzHR sends a subdomain no company uses to its own page for job seekers, and shows "This account is no longer active" for a closed account. The run log names the board and says which it was, 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 JazzHR login or API key? No. The career pages are public. JazzHR's own API needs a company's key and is not used.
Why is employmentTypes sometimes empty? Only when the posting's page could not be read, or its "Type" is one the Actor does not recognise and neither its structured data nor its title states one. Nothing is guessed.
Can I get recruiter emails or phone numbers? No, by design.
How current is the data? It is read from JazzHR while your run is in progress. postedAt is the posting date JazzHR publishes; 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 — same
job.v1fields - BambooHR Jobs Scraper — Company Job Boards — same
job.v1fields - Breezy HR Jobs Scraper — Company Job Boards — same
job.v1fields - Career Site Jobs Scraper — Greenhouse, Lever, Workday — same
job.v1fields - Dayforce Jobs Scraper — Company Job Boards — same
job.v1fields - Greenhouse Jobs Scraper — Company Job Boards — same
job.v1fields - JobAdder Jobs Scraper — Agency and Employer Job Boards — same
job.v1fields - Lever Jobs Scraper — Company Job Boards — same
job.v1fields - Personio Jobs Scraper — Company Job Boards — same
job.v1fields - Pinpoint Jobs Scraper — Company Job Boards — same
job.v1fields - Recruitee Jobs Scraper — Company Job Boards — same
job.v1fields - RemoteOK Jobs Scraper — Remote Job Feed — same
job.v1fields - Rippling Jobs Scraper — Company Job Boards — same
job.v1fields - Workable Jobs Scraper — Company Job Boards — same
job.v1fields - Workday Jobs Scraper — Company Job Boards — same
job.v1fields
About
Made by Adderley Data, Melbourne — https://adderleydata.com. Not affiliated with, endorsed by or sponsored by JazzHR or its owner. JazzHR is a trade mark of its owner and is used here only to describe what this Actor reads.