JobAdder Jobs Scraper — Agency and Employer Job Boards avatar

JobAdder Jobs Scraper — Agency and Employer Job Boards

Pricing

from $1.45 / 1,000 posting saveds

Go to Apify Store
JobAdder Jobs Scraper — Agency and Employer Job Boards

JobAdder Jobs Scraper — Agency and Employer Job Boards

Every open role on any recruitment agency's or employer's JobAdder careers page (clientapps.jobadder.com) as structured data: title, category, sub-category, location, work type, posted date and summary, 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

Adderley Data

Maintained by Community

Actor stats

0

Bookmarked

2

Total users

1

Monthly active users

7 hours ago

Last modified

Share

What does JobAdder Jobs Scraper do?

JobAdder Jobs Scraper reads every open role on any public JobAdder careers page and returns it as structured data you can load straight into a spreadsheet, a database or a model. JobAdder is recruitment software used mostly by recruitment agencies in Australia, New Zealand and the United Kingdom, and by some employers; it hosts a careers page for each of them at clientapps.jobadder.com. If a job's link leads there, this Actor reads it.

Give it a careers page's link (or the link of any job on it), and for each posting you get the title, the date it was posted, its category and sub-category, its location, its work type, the start of its summary, the business that placed it and a link to the posting. If you ask for it, you also get the ad's full text, its whole summary and any pay range the text states.

What makes it different:

  • One request per careers page. JobAdder lists every open posting on one page, so a careers page of 60 jobs is one request, whether you want 5 of them or all 60. A posting's own page is requested only when you ask for full descriptions — one more request per posting saved.
  • Agency boards read as agency boards. On JobAdder the business that placed an ad is usually a recruitment agency advertising a client's role. Each row names that business as the advertiser and does not guess the employer.
  • 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 and not charged, and a monitor of 50 careers pages is 50 requests a run.
  • 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 JobAdder careers page, a Workday board and a job board sit in the same table.
  • No personal data. Neither page the Actor reads names a recruiter in a field, and the Actor has nowhere to put one. The application page is never read. Contact details inside ad 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 JobAdder 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 JobAdder's pages fill the schema:

  • advertiser.name is the business whose careers page it is, as the page's title names it; advertiser.sourceAdvertiserId is its account number and page name (12345/acme-recruitment).
  • company is always null. On an agency's careers page an ad is for a client whose name the list does not give, and the page does not say whether a business is advertising its own role or a client's, so the Actor names no hiring company rather than guess one. Where the ad names the employer, it does so in its text.
  • classifications holds the category and sub-category the business filed the ad under in JobAdder. subcategory is null where the ad has none.
  • location.raw is the location exactly as the business wrote it: a city (Sydney), a county (Somerset), a state code (NSW, VIC Other), London, United Kingdom, or the name of a building. location.region is filled for a state code and for "City, Region" shapes; location.city only where the shape is plain ("City, Country", "City, Region, Country", "City, QLD"). A single name such as Sydney fills neither, because it may be a city, a county or an area.
  • location.country is filled only where the location names a country (London, United Kingdom, Australia - Hybrid - …). A state code is not taken for a country: WA and NT are written elsewhere too. Filter on location.raw or location.region for places in a state.
  • workArrangement is hybrid or remote where the location says so, and unknown otherwise: JobAdder's page has no field for it.
  • employmentTypes comes from the words of the business's own work-type label: Permanent / Full Time is full_time, Contract or Temp is contract and temporary, Fixed Term Full Time is full_time and contract, Casual is casual. Permanent Job states no hours, so it gives none, unless the title does (Graduate Intern).
  • teaser is the summary as the careers page lists it, which JobAdder cuts at about 120 characters and ends with .... With full descriptions on, it is the whole summary from the posting's own page.
  • postedAt is the date JobAdder shows, as midnight UTC on that day. updatedAt and expiresAt are always null: JobAdder's page shows neither.
  • salary is filled only with full descriptions on, from a range the ad's text states next to a word such as "salary" or "pay" (Salary: £38,000 - £42,000 per annum). JobAdder's careers page shows no pay field, and nothing is estimated.
  • market is always global: a careers page is a business's, not a country's.

How the list is read: JobAdder prints each posting's category, sub-category, location and work type as a list with no labels. The Actor reads them by position, in the order JobAdder prints them — checked on 170 postings on 11 careers pages in September 2026, four values on 160 of them and three (no sub-category) on 10. A posting whose list has some other number of values keeps its title, date, summary and link, and a work type where the last value reads as one. A careers page where no posting's values can be read that way fails the run with a message, rather than returning rows without them.

How much does it cost to scrape JobAdder careers pages?

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 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

The price per posting is the same with or without descriptions; descriptions only make the run longer, by one request per posting saved. Use incremental mode for anything you run more than once — after the first run you pay only for what changed.

How to scrape a JobAdder careers page

  1. Find the careers page. On an agency's or employer's website, follow any job's link: if it leads to clientapps.jobadder.com/<number>/<name>/…, the business uses JobAdder's careers page. Copy that link, or the part up to the name.
  2. Open the Actor in Apify Console and go to the Input tab. Paste one link per line into Careers pages. Up to 500 per run.
  3. Optionally filter: Title keywords, Locations, Categories, Work types, Posted within (days). Filters are applied to the careers page's list, so a posting they leave out costs nothing.
  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 posting is returned once, however many of your careers pages or filters list it.

Input

FieldTypeDefaultWhat it does
boardsarray—One entry per business: its JobAdder careers page link (https://clientapps.jobadder.com/12345/acme-recruitment), the link of any job on it, or the account number and name ("12345/acme-recruitment"). Up to 500 pages per run; each is one request for every open posting on it.
keywordsarray—Keep postings whose title contains every word of any keyword, in any order — "engineer site" matches "Senior Site Engineer". Leave empty for all titles.
locationsarray—Keep postings whose location contains this text ("Sydney", "NSW", "Somerset"), whose location names this country ("Australia", "United Kingdom"), or that the location marks "Remote" or "Hybrid". Leave empty for all locations.
categoriesarray—Keep postings whose category or sub-category contains this text, e.g. "Engineering" or "Nursing". Leave empty for all categories.
workTypesarray—Keep postings whose work type, as the business labels it, contains this text: "Contract", "Casual", "Part", "Permanent". Leave empty for all work types.
postedWithinDaysinteger—Keep postings JobAdder dates within the last N days, counted back from the start of the run. Leave empty for any time.
maxResultsinteger100The run stops once this many postings are saved. You are charged per posting saved, so this is also your cost cap.
includeDescriptionbooleanfalseOn: each posting's own page is read once for its full text, its whole summary and any pay range the text states — one extra request per saved posting. Off: the careers page's list only, one request per page.
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 postings that are new, changed or gone. Unchanged postings are skipped, not charged and cost no request for their page. Put the Actor on a schedule with this on.
stateKeystring—Optional name for this monitor, e.g. "agency-contract-roles". Runs with the same key share memory. Left empty, a key is derived from the careers pages and filters themselves.
emitExpiredbooleantrueIncremental mode only. When a complete run no longer finds a posting it saw before, save one row with changeType EXPIRED.
emitUnchangedbooleanfalseIncremental mode only. Saves (and charges for) every posting, 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": [
"https://clientapps.jobadder.com/63623/cybercx"
],
"maxResults": 100
}

The prefilled careers page, account 63623, is one business's public JobAdder careers page, used here only as an example of a public careers page. This Actor is not affiliated with that business. The prefill saves 20 postings in one request.

Output

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

{
"schemaVersion": "job.v1",
"id": "jobadder:global:12345/900101",
"source": "jobadder",
"market": "global",
"sourceJobId": "12345/900101",
"url": "https://clientapps.jobadder.com/12345/example-recruitment/900101/data-analyst",
"title": "Data Analyst",
"company": {
"name": null,
"sourceCompanyId": null,
"url": null
},
"advertiser": {
"name": "Example Recruitment Co",
"sourceAdvertiserId": "12345/example-recruitment"
},
"location": {
"raw": "Brisbane, QLD",
"suburb": null,
"city": "Brisbane",
"region": "QLD",
"postcode": null,
"country": null
},
"workArrangement": "unknown",
"employmentTypes": [
"full_time"
],
"salary": {
"raw": null,
"min": null,
"max": null,
"currency": null,
"period": null,
"includesSuper": null
},
"classifications": [
{
"category": "Information & Communication Technology",
"subcategory": "Business/Systems Analysts"
}
],
"teaser": "Own the reporting for a national freight network, two days a week in the office.",
"bulletPoints": [],
"postedAt": "2026-09-25T00:00:00.000Z",
"updatedAt": null,
"expiresAt": null,
"isPromoted": false,
"description": null,
"changeType": "NEW",
"firstSeenAt": "2026-09-28T21:30:12.000Z",
"contentHash": "2569646150828b099cad5ce4c703676cfed2ddc540aec2f1249f2b3341cd2f2f",
"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 agencies' and employers' new roles

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 posting
UPDATEDSeen before, and its title, location, category, work type or listed summary has changed
REAPPEAREDWas reported as expired and is back
EXPIREDSeen before and no longer on the careers page. One row, once
UNCHANGEDOnly 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 careers page's list — each posting's title, location, category, work type and listed summary — before any posting's page is requested. That is what keeps an unchanged posting free. The cost is that an edit only to an ad's text is not reported as UPDATED. When a posting is saved for any other reason, its row carries the page's current text.
  • A new date alone is not a change: agencies refresh their ads, which moves the date and not the role.
  • The order of the list is not a change.
  • 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 posting on a page the run never read is not gone. A posting that falls outside Posted within (days) is no longer listed by your filters, and a complete run reports it as EXPIRED.
  • 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 (agency-contract-roles) 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 ad's text from the posting's own page, ending where JobAdder's "Apply For Job" button begins. The same request gives the whole summary and any pay range the text states.

If a posting is withdrawn between the list and its page, JobAdder shows the careers page instead. The row is saved with what the list said — title, date, category, location, work type and link — and the run log says so.

Agency ads often end with a consultant's name, phone number and email address. With Redact contact details on — the default — phone numbers (Australian, New Zealand and United Kingdom formats, with or without spaces) and email addresses are replaced with [redacted] and description.contactsRedacted is true; so is a personal profile address (linkedin.com/in/…). The consultant's name is part of the ad's wording and is left in the text. A posting's summary (teaser) is a field every row carries, so contact details in it are replaced with [redacted] whatever this setting says. 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 JobAdder, never reads the application page, 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

  • Agency market signals. Which recruitment agencies are advertising which roles, in which categories and places, and how often — a daily monitor across a list of agencies' careers pages is one scheduled run.
  • Contract and temporary role feeds. Filter on the agencies' own work types ("Contract", "Casual") across many careers pages at once.
  • Job aggregators and alert products. A clean feed of new postings from a curated list of agencies and employers, deduplicated and labelled by change.
  • Pay research. Stated pay ranges from ad texts, as numbers with their currency and period, where agencies publish them.
  • 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/jobadder-jobs-scraper').call({"boards":["https://clientapps.jobadder.com/63623/cybercx"],"maxResults":100});
const { items } = await client.dataset(run.defaultDatasetId).listItems();
console.log(items.length, items[0]?.classifications);
import os
from apify_client import ApifyClient
client = ApifyClient(os.environ["APIFY_TOKEN"])
run = client.actor("adderleydata/jobadder-jobs-scraper").call(run_input={"boards":["https://clientapps.jobadder.com/63623/cybercx"],"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.

The Actor reads the public pages of a business's JobAdder careers page — the job list and each posting's page, which JobAdder's robots.txt leaves open to crawlers other than two search engines — 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 open or submit the application page, does not use JobAdder's API, and does not collect personal information.

What you do with the data is your responsibility. Each ad's text is its author's 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 a careers page's link? On the agency's or employer's website, open any job. If the address starts clientapps.jobadder.com/, copy it, or just the part up to the name after the number. The number is the business's JobAdder account; the name after it is its careers page. Case does not matter.

My agency's jobs are on its own website, not clientapps.jobadder.com. Some businesses publish their JobAdder jobs through a widget or JobAdder's API on their own site. This Actor reads only the careers pages JobAdder hosts.

Why is company always empty? Most JobAdder careers pages belong to recruitment agencies, whose ads are for clients the list does not name, and the page does not say which ads are the business's own roles. The business that placed the ad is in advertiser.name.

Can I filter by the date a posting was published? Yes: Posted within (days) keeps postings JobAdder dates within the last N days, and it is applied to the list, so a posting it leaves out costs nothing.

Why is location.country often null? Businesses write locations such as Sydney, NSW or Somerset without a country. The Actor fills country only where the location names one; location.raw and location.region keep the rest.

Why is salary usually empty? JobAdder's careers page shows no pay field. With descriptions on, a range the ad's text states next to a word such as "salary" or "pay" is read; many ads state none.

A careers page I gave came back as "not found". JobAdder answers an account number or name it does not have with a page that says "Nothing here I'm afraid". The run log names the page, and the other 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.

Does it need a JobAdder login or API key? No. The careers pages are public. JobAdder's own API needs a partner's key and is not used.

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

How current is the data? It is read from JobAdder while your run is in progress. postedAt is the date JobAdder shows; 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 JobAdder or its owner, or by any business whose careers page it reads. JobAdder is a trade mark of its owner and is used here only to describe what this Actor reads.