# SEEK Jobs Scraper (Australia & New Zealand) (`nice_dev/seek-jobs-scraper`) Actor

Scrape SEEK job ads in Australia and New Zealand: full description, salary (hidden ones inferred), applicants, screening questions, contacts and company profile. Every job of a search, no 550 limit. JSON, CSV, Excel.

- **URL**: https://apify.com/nice_dev/seek-jobs-scraper.md
- **Developed by:** [Nice Dev](https://apify.com/nice_dev) (community)
- **Categories:** Jobs, Lead generation, MCP servers
- **Stats:** 1 total users, 0 monthly users, 0.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $0.15 / 1,000 job with details

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 is SEEK Jobs Scraper?

**SEEK Jobs Scraper** extracts **job ads from [SEEK](https://www.seek.com.au)** in **Australia** and **New Zealand**: **title, company, salary (hidden ones inferred), full description, number of applicants, screening questions, contact e-mails and phones, closing date and company profile**, for any keyword, location or SEEK search URL.

Type a **keyword** (`nurse`) and a **location** (`All Sydney NSW`), or paste any SEEK search URL, click **Start**, and download the jobs in JSON, CSV or Excel. No login, nothing to set up, and it is **fast: about 100 job ads with full details in a minute**. Every job of a search, even beyond the 550 that SEEK shows.

### 📋 What data can you extract from SEEK?

One item per job ad, 91 fields:

| Category | What you get |
| --- | --- |
| 🏷️ **Job** | title, SEEK's standard role name, link, apply link, job id — `Management Accountant` |
| 💰 **Salary** | salary as shown, min / max, per hour / year, currency, super included — and the range of a **hidden salary**, inferred from SEEK's own salary filter — `$65 - $75 per hour + super` |
| 👥 **Applicants** | number of applicants, application volume, share with a resume / a cover letter — `3 applicants, Low application volume` |
| 📝 **Ad** | full description (text and HTML), summary, selling points, screening questions, video, cover image |
| ☎️ **Contacts** | e-mail addresses and phone numbers given in the ad |
| 🧭 **Signals** | the level of the job (entry, junior, senior, lead, manager, executive), whether the ad offers or refuses visa sponsorship, and a 0-100 score of its good signs — worked out from the ad by rules, included in the price |
| 🤝 **Recruiter** | for agency ads, the recruiter's SEEK profile: name, rating, number of reviews, phone, agency, website, location, specialisations, placements — `4.96 / 5 from 32 reviews` |
| 🏢 **Company** | company, advertiser, verified badge, registration date, company profile (industry, size, website, description, head office), rating, number of reviews, salary rating, perks, awards, open jobs, and optionally the latest employee reviews |
| 🗂️ **Category** | SEEK classification and sub-classification, work type (full time, part time, contract, casual), on-site / hybrid / remote |
| 📍 **Location** | location, suburb, area, state, postcode, country |
| 🕒 **Dates** | listing date, closing date, expired or not |

Every field, with an example, is listed in the **Output** section below.

Fields marked **detail** in the Output tab (description, applicants, screening questions, contacts, recruiter, closing date, company profile) are filled when **Extract details** is ticked (off by default: it is charged per job). Leave it off for a results-only scrape that is several times faster.

### ✅ Why use SEEK Jobs Scraper?

- 🔓 **Every job of a search**: SEEK shows at most 550 jobs per search; this Actor splits a bigger search on its own (by category, work type, state…) and returns all of them — 1,500 « nurse » jobs from all Australia in 1 to 2 minutes, results only.
- 💰 **Hidden salaries inferred**: about half of SEEK's ads hide the salary; the Actor finds its range with SEEK's own salary filter (`salaryIsInferred: true`), charged only when a range is found (tick **Infer hidden salaries**; off by default).
- 👥 **Applicants and screening questions**: how many people applied, and what the employer asks when you apply.
- 🚀 **Fast**: about 100 job ads with full details in a minute, 1,200 in under 10 minutes, 1,500 results-only jobs in 1 to 2 minutes.
- 🇦🇺 🇳🇿 **Australia and New Zealand** in one Actor, with SEEK's own filters: classification, sub-classification, work type, work arrangement, salary, listing date, advertiser, company, radius.
- 🔔 **Monitoring built in**: tick **Only new jobs**, schedule the Actor, and each run returns (and charges) only the jobs it has never delivered before.
- 🔌 API, scheduling, integrations (Make, Zapier, n8n, Google Sheets…) and JSON / CSV / Excel export via the Apify platform.

### 🚀 How to scrape SEEK

1. Create a free Apify account.
2. Open **SEEK Jobs Scraper**, type **Search keywords** (e.g. `nurse`) and a **Location** (e.g. `All Sydney NSW`), and pick the **Country**.
3. Or paste your own SEEK URLs into **Start URLs**: any search results page (the filters set on SEEK are kept) or single job ads.
4. Set **Max jobs** (100 by default, 0 = every job of the search) — and **Max jobs per search** when you run several searches — then click **Start**.
5. Download the dataset in JSON, CSV, Excel or via API.

### 💰 How much does it cost to scrape SEEK?

This Actor uses **pay per event** pricing: **$0.18 per 1,000 job ads with full details**, or **$0.17 per 1,000** from the results only (Extract details unticked, the default; a job pasted by its URL or id is always read in full) — plus **$0.01 per run start**. Higher Apify plans pay less, down to $0.15 and $0.14 per 1,000. Company open-job counts are included, and so are the signals (`seniority`, `visaSponsorship`, `jobScore`). The text of **employee reviews** (**Max reviews per company**, off by default) is charged per review delivered, **$0.79 per 1,000 reviews** (down to $0.76 on higher plans), once per company per run (event `company-review`, "Company review"): the first job of a company that carries its reviews pays them, the next jobs of that company carry the same reviews at no cost. **Add market statistics per search** (unticked by default) is charged per search delivered, with at least 10 jobs, **$0.015 per search** ($14.99 per 1,000 searches, down to $14.96 on higher plans; event `market-stats`, "Market statistics"). A **hidden salary inferred** is charged per job whose range is found, **$2.79 per 1,000** on top of the job price, down to $2.76 on higher plans (event `inferred-salary`, "Hidden salary inferred"; a salary shown, or one SEEK's bands cannot place, is never charged; **Infer hidden salaries** is unticked by default: tick it to have them found). The post-filters are applied by the Actor on every job it reads: each job a filter checks is charged, kept or not (event `filter-check` when the results card answers it — dates, title / excluded / included words, salary; `detail-filter-check` when the job ad must be read — e-mail, phone, verified advertiser, applicants, included words with **Extract details**).

Example: 10,000 job ads with full details ≈ $1.81; a daily monitor of 300 new jobs with full details ≈ $0.06. Platform usage (compute, proxy) is included in the price.

### ⚙️ Input

```json
{
    "query": "nurse",
    "location": "All Sydney NSW",
    "maxItems": 200
}
```

Several searches, a cap per search, SEEK filters, only recent jobs, only the ones not delivered before:

```json
{
    "searchQueries": ["registered nurse", "midwife"],
    "locations": ["All Sydney NSW", "All Melbourne VIC"],
    "maxItemsPerQuery": 100,
    "workType": ["full-time"],
    "workArrangement": ["hybrid", "remote"],
    "postedAfter": "7 days",
    "onlyNew": true,
    "stateKey": "nurses-au"
}
```

Or with your own URLs and job ids:

```json
{
    "startUrls": [
        { "url": "https://www.seek.com.au/nurse-jobs/in-All-Sydney-NSW?worktype=242" },
        { "url": "https://www.seek.co.nz/jobs?keywords=developer&where=Auckland" }
    ],
    "jobIds": ["94713920"],
    "maxItems": 500
}
```

| Field | Notes |
| --- | --- |
| `query`, `searchQueries` | Job title, skill or company, exactly as typed on SEEK (`nurse -aged`, `nurse OR midwife`, `"registered nurse"` work); `searchQueries` adds more keywords (one search each). Ignored when `startUrls` is set. |
| `location`, `locations` | Suburb, postcode, city, region or state as SEEK names it (`All Sydney NSW`, `2000`, `New South Wales NSW`, `Auckland`); `locations` adds more places. Every keyword is searched in every location (max 500 searches per run). Empty = the whole country. |
| `country` | `AU` (seek.com.au, default) or `NZ` (seek.co.nz). |
| `radius` | km around a precise location (suburb, postcode); `0` = SEEK's default (50 km). |
| `startUrls` | SEEK search results pages (filters kept, pagination automatic) or single job ads, from seek.com.au or seek.co.nz. |
| `jobIds` | SEEK job ids (`94713920`) to extract directly — AU and NZ ids mixed: each is read on its own site, whatever `country`. |
| `maxItems` | Stop after this many jobs for the whole run (`0` = unlimited). |
| `maxItemsPerQuery` | Cap for EACH search (keyword × location, or search URL). `0` = no per-search cap. |
| `classification` | SEEK categories (`Healthcare & Medical`, `Information & Communication Technology`…). |
| `subClassification` | SEEK sub-categories by name (`Web Development & Production`), by `Classification > Name` when several share it (`Healthcare & Medical > Management`), or by id (`6302`). |
| `workType` | `full-time`, `part-time`, `contract`, `casual`. |
| `workArrangement` | `on-site`, `hybrid`, `remote`. |
| `salaryType` | Unit of the salary filter: `annual`, `monthly`, `hourly`. |
| `salaryMin`, `salaryMax` | SEEK's salary filter; it also matches the ads that hide their salary. |
| `dateRange` | SEEK's date filter, in calendar days of Sydney time (on seek.co.nz too): `1` = today since midnight, `3`, `7`, `14`, `31` = since midnight that many days ago, or `any`. For the last 24 hours, use **postedAfter**. |
| `sortBy` | `date` (newest first, default) or `relevance`. |
| `advertiserIds` | Only the jobs of these SEEK advertisers (the `advertiserId` of a result). |
| `companyName` | Only the jobs of this company (`Ramsay Health Care`). |
| `extractDetails` | Open each job ad for the full description, applicants, questions, contacts and company (off by default; charged per job read, see the pricing). |
| `inferHiddenSalary` | Infer the range of a hidden salary from SEEK's salary filter (off by default; charged per salary found, see the pricing). |
| `maxReviewsPerCompany` | Latest employee reviews added to each job (field companyReviews), read once per company; `0` = none. Charged per review delivered, once per company per run (see the pricing). |
| `includeMarketStats` | Adds the MARKET_STATS record to the run's key-value store: one sheet of figures per search (off by default; charged per search delivered, see the pricing and **Market stats per search** below). |
| `postedAfter`, `postedBefore` | Listing date range: `2026-09-01`, or a period before now (`7 days`, `2 weeks`, `1 month`, `24 hours`). |
| `titleKeywords`, `includeKeywords`, `excludeKeywords` | Keep the jobs whose title contains one of the words; whose title, company, teaser, selling points (or, with **Extract details**, description) contain one; drop the ones that contain one (case and accents ignored). |
| `requireEmail`, `requirePhone`, `onlyWithSalary`, `verifiedOnly` | Keep only the jobs with a contact e-mail, a phone number, a salary (shown or inferred), a verified advertiser. |
| `minApplicants`, `maxApplicants` | Keep the jobs with at least / at most this many applicants (at most 10 = little competition). |
| `onlyNew`, `stateKey`, `resetState` | Monitoring: only the jobs never delivered under this memory key; `resetState` forgets the memory. |
| Advanced | `proxyConfiguration` (Apify proxy by default, included in the price; the residential proxy is not available), `maxConcurrency`, `maxRequestsPerMinute`, `minRequestIntervalMs` (0 by default: no gap between two requests; both count the results pages and job ads, not the salary bands nor the company counts and reviews), `maxRequestRetries`, `debugLog`. |

### 📦 Output

One real item from a test run (long texts shortened; the recruiter's name and contacts replaced):

```json
{
    "id": "94824689",
    "url": "https://www.seek.com.au/job/94824689",
    "applyUrl": "https://www.seek.com.au/job/94824689/apply",
    "title": "Accounts & Payroll Officer - Civil Construction",
    "normalisedRoleTitle": "Payroll and Accounts Officer",
    "roleId": "payroll-and-accounts-officer",
    "teaser": "Join a Growing Melbourne Civil Construction business in a hands-on Accounts role covering payroll, b…",
    "bulletPoints": [
        "Growing & Established Melbourne Civil Construction business",
        "Own this Weekly-Payroll, Accounts, Reconciliations & Monthly Reporting tasks"
    ],
    "abstract": "Join a Growing Melbourne Civil Construction business in a hands-on Accounts role covering payroll, b…",
    "description": "The Company\nOur client's a well-established subcontractor servicing some of Australia's largest Tier…",
    "descriptionHtml": "<h2><strong>The Company</strong></h2><p>Our client's a well-established subcontractor servicing some…",
    "companyName": "Cappro Pacific",
    "advertiserId": "46218953",
    "advertiserName": "Cappro Pacific",
    "advertiserIsVerified": true,
    "advertiserRegistrationDate": "2020-12-03T02:06:44.057Z",
    "isPrivateAdvertiser": false,
    "companyProfileId": null,
    "companyProfileUrl": null,
    "companyLogoUrl": "https://bx-branding-gateway.cloud.seek.com.au/e5113d33-7dfa-461d-a4ef-3196e3270871.1/jdpLogo",
    "companyIndustry": null,
    "companySize": null,
    "companyWebsite": null,
    "companyDescription": null,
    "companyRating": null,
    "companyReviewCount": null,
    "companyPerks": [],
    "companyReviews": [],
    "employerId": "465831",
    "companyOpenJobs": 19,
    "companyPrimaryLocation": null,
    "companySpecialities": null,
    "companySalaryRating": null,
    "companyAwards": [],
    "classificationId": "1200",
    "classification": "Accounting",
    "subClassificationId": "6140",
    "subClassification": "Accounts Officers/Clerks",
    "workType": "Full time",
    "workArrangements": [
        "On-site"
    ],
    "isRemote": false,
    "isHybrid": false,
    "location": "Melbourne VIC",
    "suburb": null,
    "area": "Melbourne VIC",
    "state": "Victoria",
    "postalCode": null,
    "countryCode": "AU",
    "salaryLabel": "$100,000–$120,000 per year plus Super",
    "salaryMin": 100000,
    "salaryMax": 120000,
    "salaryPeriod": "year",
    "salaryCurrency": "AUD",
    "salaryIncludesSuper": true,
    "salaryIsInferred": false,
    "publishedAt": "2026-09-23T04:47:49.000Z",
    "listingDateDisplay": "6m ago",
    "expiresAt": "2026-10-23T12:59:59.999Z",
    "isExpired": false,
    "applicantCount": 1,
    "applicantVolumeLabel": "Low application volume",
    "applicantsWithResumePercentage": 100,
    "applicantsWithCoverLetterPercentage": 100,
    "screeningQuestions": [],
    "contactEmails": [
        "jane.doe@acme-recruit.com.au"
    ],
    "contactPhones": [
        "0412 345 678"
    ],
    "seniority": null,
    "visaSponsorship": null,
    "jobScore": 100,
    "hasRoleRequirements": false,
    "applicationIsExternal": false,
    "isFeatured": false,
    "isSponsored": false,
    "tags": [
        "Be an early applicant"
    ],
    "badges": [
        "On-site"
    ],
    "videoUrl": null,
    "coverImageUrl": "https://image-service-cdn.seek.com.au/fafb8e81ada107b6cfcb07d366e4626315f95472/00e3f857d81b293723f9026c526521e4285c2beb",
    "recruiterProfileUrl": "https://au.seek.com/recruiters/people/jane-doe",
    "recruiterName": "Jane Doe",
    "recruiterRating": 4.86,
    "recruiterReviewCount": 87,
    "recruiterPhone": "0412 345 678",
    "recruiterAgency": "Cappro Pacific",
    "recruiterWebsite": "https://www.cappropacific.com.au/",
    "recruiterLocation": "Sydney, NSW, 2213, Australia",
    "recruiterSpecialisations": [
        "Engineering",
        "Sales",
        "Mining, Resources & Energy"
    ],
    "recruiterPlacements": 85,
    "searchQuery": "accountant",
    "searchLocation": null,
    "searchUrl": "https://www.seek.com.au/jobs?keywords=accountant&sortmode=ListedDate&page=1",
    "scrapedAt": "2026-09-23T04:54:11.718Z"
}
```

You can download the dataset in various formats such as JSON, HTML, CSV or Excel.

#### All 91 fields

| Fields | What you get |
| --- | --- |
| `id`, `url`, `applyUrl`, `title`, `normalisedRoleTitle`, `roleId` | **Job**: `94820549`, job and apply links, `Management Accountant` |
| `teaser`, `bulletPoints`, `abstract`, `description`, `descriptionHtml`, `videoUrl`, `coverImageUrl` | **Ad**: summary, selling points, full text and HTML, video and cover image |
| `salaryLabel`, `salaryMin`, `salaryMax`, `salaryPeriod`, `salaryCurrency`, `salaryIncludesSuper`, `salaryIsInferred` | **Salary**: `$65 - $75 per hour + super` → `65`, `75`, `hour`, `AUD`, `true`; `salaryIsInferred: true` when the range comes from SEEK's filter |
| `applicantCount`, `applicantVolumeLabel`, `applicantsWithResumePercentage`, `applicantsWithCoverLetterPercentage`, `screeningQuestions`, `hasRoleRequirements`, `applicationIsExternal` | **Applicants**: `3`, `Low application volume`, `100`, `33`, the employer's questions |
| `contactEmails`, `contactPhones` | **Contacts** given in the ad |
| `seniority`, `visaSponsorship`, `jobScore` | **Signals** worked out from the ad by rules (see the FAQ): `senior`, `false` (the ad refuses sponsorship) or `true` (it offers it), `80` |
| `recruiterProfileUrl`, `recruiterName`, `recruiterRating`, `recruiterReviewCount`, `recruiterPhone`, `recruiterAgency`, `recruiterWebsite`, `recruiterLocation`, `recruiterSpecialisations`, `recruiterPlacements` | **Recruiter** of an agency ad: profile link, `4.96` / 5 from `32` reviews, agency, `Accounting & Finance`, placements |
| `companyName`, `advertiserId`, `advertiserName`, `advertiserIsVerified`, `advertiserRegistrationDate`, `isPrivateAdvertiser`, `employerId`, `companyOpenJobs` | **Advertiser**: `Perigon Group`, verified, registered in `2010`, `41` open jobs |
| `companyProfileId`, `companyProfileUrl`, `companyLogoUrl`, `companyIndustry`, `companySize`, `companyWebsite`, `companyDescription`, `companyPrimaryLocation`, `companySpecialities` | **Company profile** on SEEK |
| `companyRating`, `companyReviewCount`, `companySalaryRating`, `companyPerks`, `companyAwards`, `companyReviews` | **Company reputation**: `3.4` / 5 from `183` reviews, perks, awards, the latest reviews (title, rating, pros, cons, job title, date) |
| `classificationId`, `classification`, `subClassificationId`, `subClassification`, `workType`, `workArrangements`, `isRemote`, `isHybrid` | **Category**: `Accounting`, `Management Accounting & Budgeting`, `Contract/Temp`, `On-site` |
| `location`, `suburb`, `area`, `state`, `postalCode`, `countryCode` | **Location**: `Brisbane QLD`, `Brisbane`, `Brisbane QLD` (the SEEK region, with its state code), `Queensland`, `4000`, `AU` |
| `publishedAt`, `listingDateDisplay`, `expiresAt`, `isExpired` | **Dates**: listing date (what the date filters read), closing date |
| `isFeatured`, `isSponsored`, `tags`, `badges` | SEEK's featured / sponsored flags, tags and badges |
| `searchQuery`, `searchLocation`, `searchUrl`, `scrapedAt` | The search that found the job, ISO timestamp |

### 💡 Tips

#### How to get more results

Set `maxItems` to `0`: a search above SEEK's 550 jobs is split on its own, and every job is returned once. A broad keyword (or none, with a category) is fine.

#### How to reduce costs

The price is per job, so the levers are `maxItems`, `maxItemsPerQuery`, the SEEK filters, the post-filters (a filtered-out job is not saved, but each job a filter drops is charged a check — a job it keeps pays its own price only: prefer the SEEK filters, applied by SEEK itself) and `onlyNew` for recurring runs (you never pay twice for the same job).

#### Market stats per search (optional)

Tick **Add market statistics per search** (`includeMarketStats`) and, once the run has delivered its jobs, one record named `MARKET_STATS` is added to the run's key-value store (Output tab) — never to the dataset. It holds one sheet per search, worked out from the jobs of this run only:

- the jobs delivered, and the jobs SEEK announced for the search;
- the share of jobs with a salary shown, and with a salary inferred;
- salary median and quartiles for each pay period (year, month, day, hour), in the most frequent currency, on the middle of each shown range;
- the share of remote and hybrid jobs, the work types, the 10 employers who advertise most, the top classifications and sub-classifications;
- the levels (`seniority`), the share of ads that offer visa sponsorship, the median number of applicants (jobs read with their ad), the share of jobs listed in the last 7 days.

A search needs at least 10 jobs delivered; a job pasted by its URL or id belongs to no search. With **Only new jobs**, only the jobs delivered in this run count. Charged per search delivered, see the pricing.

#### Several searches in one run

Fill `searchQueries` and / or `locations`: the Actor runs one search per keyword × location (3 keywords × 4 cities = 12 searches, up to 500 per run). The single `query` and `location` fields still work and are added to the lists. A job found by several searches is saved — and charged — once. Set `maxItemsPerQuery` to give every search its own cap: without it the first searches can use up the whole `maxItems` budget. You can also paste several search URLs into `startUrls`: each one is a search of its own, with the same cap.

#### Monitoring: only the new jobs

Tick **Only new jobs** (`onlyNew`) and schedule the Actor. The first run returns everything; each later run skips the jobs already delivered: they are not saved, not charged, and their ad is not even opened. The memory lives in a named key-value store of your account (`seek-jobs-scraper-seen`, up to 150,000 jobs per key) and is only updated with jobs that really reached the dataset, so a failed run never hides anything. Give each schedule its own `stateKey` (two schedules sharing a key would hide each other's jobs), and tick `resetState` once to start over. Searches are then sorted by newest, and a search stops once it meets 100 jobs in a row you already have, or fewer when **Max jobs** or **Max jobs per search** is smaller (the sponsored jobs SEEK pins at the top are not counted). A search the Actor splits (above SEEK's limit) also skips the jobs listed before the previous run that its `maxItems` left out: a capped run does not hand you the older rest of the search next time. A job your filters drop is checked (and charged the filter check) once: the next runs with the same filters skip it for free; change a filter, or tick `resetState`, to check them again.

#### Filter by publication date

`postedAfter` and `postedBefore` take a date (`2026-09-01`, the whole day is included: Sydney time for a job of seek.com.au, Auckland time for seek.co.nz) or a period before now (`7 days`, `2 weeks`, `1 month`; via the API also `24 hours` or a full ISO date-time). The filter reads `publishedAt`; a job without a listing date is dropped as soon as a date bound is set. Filtered-out jobs are not saved and do not count in `maxItems` (each job the filter checks is charged, see the pricing); the run summary tells how many were filtered. With `postedAfter`, searches are sorted by newest and stop at the first page that is entirely too old.

### 🔌 Integrations and API

Call the Actor via the Apify API, the JavaScript or Python clients, or connect it with integrations and webhooks (Make, Zapier, n8n, Google Sheets, Slack, Airtable…). The dataset can be fetched as JSON or CSV from any tool.

### 🤖 Use with AI agents (MCP)

AI agents (Claude, ChatGPT, Cursor…) can find and run this Actor through the [Apify MCP server](https://mcp.apify.com), billed to their Apify account like any run. It returns one item per SEEK job ad (Australia or New Zealand). Actor id: `nice_dev/seek-jobs-scraper`; MCP server with this Actor only: `https://mcp.apify.com/?tools=fetch-actor-details,nice_dev/seek-jobs-scraper`.

Smallest input, for a cheap first call:

```json
{
    "query": "nurse",
    "location": "All Sydney NSW",
    "maxItems": 10
}
```

Key output fields: `url`, `title`, `companyName`, `location`, `salaryLabel`, `workType`, `publishedAt`, `applicantCount`.

Cost: $0.18 per 1,000 job ads with full details ($0.17 from the results only) plus $0.01 per run start, down to $0.15 and $0.14 per 1,000 on higher Apify plans; the inferred hidden salaries ($2.79 per 1,000, down to $2.76) cost extra, see the pricing section above. Cap each call with `maxItems` and, through the API, with the run option `maxTotalChargeUsd`.

### ❓ FAQ

#### Is it legal to scrape SEEK?

The Actor only reads what SEEK shows publicly to any anonymous visitor. It logs in to nothing and solves no captcha. Results can contain personal data (a recruiter's name, e-mail or phone number written in an ad, anonymous employee reviews), which is protected by GDPR and the Australian Privacy Act: do not store it without a legitimate reason. You are responsible for using the data in compliance with SEEK's Terms of Use and applicable law. This Actor is not affiliated with SEEK.

#### Does it need a login or a proxy?

No login. The proxy is included in the price: leave the default setting (the residential proxy is not available). A request the site turns away is retried at once on a new proxy session (without a proxy, after a pause of 5 seconds, doubled at each retry up to 150 seconds). If SEEK still turns away 10 job ads in a row after all their retries, the run stops and its status says so, keeping the jobs saved until then: run it again later.

#### How is a hidden salary inferred?

When an advertiser hides the salary, SEEK still filters on the range they typed. The Actor asks SEEK the same search once per salary band ($30k, $40k, … $350k per year) and keeps the bands the job comes back in: `salaryMin` / `salaryMax` are those bands' bounds, per year, with `salaryIsInferred: true`. The range is as precise as SEEK's bands (often $20k-$50k wide). Only a range found is charged (event `inferred-salary`), once its job is saved.

#### How are seniority, visa sponsorship and the job score worked out?

By fixed rules, not by a model, from what the ad says. `seniority` is read in the job title (else in SEEK's standard role name), on whole words: `graduate`, `intern`, `trainee`… are `entry`; `junior`; `senior`; `lead`, `principal`, `team leader`; `manager`, `head of`, `supervisor`; `director`, `chief`, `CEO`, `general manager`… are `executive`. A title with several takes the highest (« Senior Account Manager » is `manager`); one with none gives `null`. `visaSponsorship` is `false` when the ad refuses sponsorship or asks for working rights or citizenship (« no sponsorship », « must have full working rights », « Australian citizens only »), `true` when it offers it (« visa sponsorship available », « will sponsor », « 482 visa »), `null` when it says nothing; it is read in the description, or in the summary and selling points of a job whose ad was not opened. `jobScore` is the share of good signs among those known, from 0 to 100: a salary shown, a job listed at most 3 days before it was read, a contact e-mail or phone given (known once the ad is opened), fewer than 20 applicants, a verified advertiser.

#### Is the data safe to open in Excel or to show on a web page?

Titles and descriptions are the advertisers' own words, copied as they are. A text can begin with `-`, `+`, `=` or `@`: Excel and Google Sheets may read such a cell of a CSV file as a formula or as a number. The Actor leaves the text as it is, so that the JSON and the API give the real value: when you open a CSV, import these columns as text. `descriptionHtml` is the ad's HTML as SEEK serves it, not sanitized: on a web page, escape every field like any text written by a stranger. Every link field (`url`, `companyWebsite`, `videoUrl`, `recruiterWebsite`…) is an `http(s)` URL or `null`: never `javascript:` or `data:`, never a quote, never a user name before the host (`https://www.seek.com.au@other.example` is dropped).

#### Known limitations

- SEEK filters that are not in the input can still be used by pasting a filtered search URL into `startUrls`.
- `applicantCount` is only shown by SEEK for ads you apply to on SEEK, once a few people have applied; it is `null` otherwise.
- `onlyNew` remembers job ids, not their content: a job whose salary changed is not returned again.
- Two runs sharing the same `stateKey` at the same time may both return the same new job.
- A split search returns what SEEK gives for each part: SEEK adds loose keyword matches to a broad search that it leaves out of the narrower ones (measured: 1.7 % of « nurse » healthcare jobs, e.g. a GP job), so a few of those are missing.
- Sorted by date with a cap, a search gives its newest jobs up to about 490. Above that the Actor splits it (by category, work type…) and each part gives its own newest ones: the jobs returned are not the newest of the whole search, and do not come out in date order.
- A search the Actor splits (more than about 490 jobs wanted from a search of more than 500), added later to a `stateKey` already used, only returns the jobs listed since that key's last run: give it its own `stateKey`.

**A run the platform stops without warning** (out of memory, run timeout)

- Resurrect it: it goes on from where it stood at most a minute before the stop. What it had read since is read again, and the jobs already saved are skipped: none is delivered or charged twice, and `maxItems` still counts them.
- The jobs found in the last 2 seconds before the stop may be missing from the dataset: they are not charged.
- With `onlyNew`, the memory is saved after each write (at most once every 2 seconds): resurrect the stopped run and the jobs it had saved meanwhile join the memory; leave it stopped for good, and the next run may return the jobs of its last 2 seconds once more.

#### Something doesn't work?

The last line of the log counts the jobs saved, filtered out and no longer on SEEK (removed while the run was reading them), and the requests that failed after every retry. Those requests and the removed jobs are listed, with the reason, in the `FAILED_REQUESTS` record of the run's key-value store. A run that saved nothing and had failed requests fails, and its last message gives the cause. A run that saved some jobs fails too when at least as many requests failed for good as were read (SEEK refusing the job ads after the first ones, for instance): a green run with a short dataset would hide the outage. One failed request among many is only a warning.

If SEEK changes its pages, you are told instead of paying for blank rows. A results page that counts jobs but gives none the Actor can read is an error (listed in `FAILED_REQUESTS`), never a quiet "No listings found". If the first 20 jobs read all lack their title, listing date, classification or — with `extractDetails` — description, the run saves nothing more, stops and fails, and its last message names the missing field: at most those first jobs are charged.

### 🛟 Support

Open an issue in the **Issues** tab with a link to your run: the run log and the `FAILED_REQUESTS` record of the key-value store show exactly which searches or jobs failed and why.

# Actor input Schema

## `startUrls` (type: `array`):

SEEK search-result URLs from seek.com.au or seek.co.nz (every filter set on the site is kept, pagination is automatic) or single job URLs (`https://www.seek.com.au/job/94713920`). When this list is not empty, the search fields below are ignored; filters, caps and monitoring still apply. Max 1 000 URLs.

## `jobIds` (type: `array`):

SEEK job ids (`94713920`) to extract directly, like pasting their job URLs. AU and NZ ids can be mixed: each is read on its own site.

## `country` (type: `string`):

Which SEEK site to search.

## `query` (type: `string`):

Job title, skill or company, exactly as typed on SEEK. SEEK operators work: `nurse -aged`, `nurse OR midwife`, `"registered nurse"`. Empty = every job of the location and filters.

## `searchQueries` (type: `array`):

Several keywords in one run: one search per keyword (times each location below). Added to **Search keywords**; jobs found by several searches are saved once.

## `location` (type: `string`):

Suburb, postcode, city, region or state as SEEK names it: `Sydney NSW 2000`, `2000`, `All Sydney NSW`, `New South Wales NSW`, `Auckland`. Empty = the whole country.

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

Several locations in one run: one search per location (times each keyword, max 500). Added to **Location**.

## `radius` (type: `integer`):

Distance around a precise location (suburb, postcode). 0 = SEEK default (50 km). Ignored for `All ...` regions and states.

## `maxItems` (type: `integer`):

Maximum number of jobs to save for the whole run (after deduplication and filters). 0 = no limit: every job of the search, even beyond the 550 that SEEK shows per search (the search is split automatically).

## `maxItemsPerQuery` (type: `integer`):

Cap for each search (keyword x location, or each start URL). 0 = no cap per search.

## `classification` (type: `array`):

Job categories. Empty = all.

## `subClassification` (type: `array`):

Sub-categories by name as SEEK shows them (`Web Development & Production`, `Nursing - Aged Care`) or by id (`6302`).

## `workType` (type: `array`):

Empty = all.

## `workArrangement` (type: `array`):

Empty = all.

## `salaryType` (type: `string`):

Unit of **Salary min** / **Salary max**.

## `salaryMin` (type: `integer`):

SEEK salary filter: it also matches ads whose salary is hidden.

## `salaryMax` (type: `integer`):

SEEK salary filter (upper bound).

## `dateRange` (type: `string`):

SEEK's own date filter, in calendar days of Sydney time (on seek.co.nz too): Today = since midnight in Sydney, so a run at 00:30 there gets 30 minutes of jobs; Last 3 days = since midnight 3 days ago. For the last 24 hours or an exact date use **Posted after** below.

## `sortBy` (type: `string`):

Order of the jobs. With a cap (**Max jobs**), `Date` gives the newest jobs, up to about 490 from one search: above that, the search is split into parts and each part gives its own newest ones.

## `advertiserIds` (type: `array`):

Only the jobs of these SEEK advertisers (the `advertiserid` of a SEEK URL, or `advertiserId` of a result).

## `companyName` (type: `string`):

Only the jobs of this company, as SEEK matches it (`Ramsay Health Care`).

## `extractDetails` (type: `boolean`):

Open each job ad: full description, applicant count, screening questions, closing date, contact e-mails / phones, company profile. Charged per job whose ad is read (event `result-with-details`, see the pricing). Unticked (default) = only the fields of the results list (faster, cheaper).

## `inferHiddenSalary` (type: `boolean`):

When an advertiser hides the salary, find the range with SEEK's own salary filter (a few extra searches per search, not per job). The result says `salaryIsInferred: true`. Charged only per job whose hidden salary is found (event `inferred-salary`, see the pricing); unticked (default) = never charged.

## `maxReviewsPerCompany` (type: `integer`):

Employee reviews added to each job of a company (`companyReviews`), read once per company. 0 = none. Needs **Extract details**. Charged per review delivered, once per company per run (event `company-review`, see the pricing); 0 (default) = never charged.

## `includeMarketStats` (type: `boolean`):

Adds one record, `MARKET_STATS`, to the run's key-value store (Output tab): one sheet per search, worked out from the jobs of this run only — jobs delivered and on SEEK, share with a salary shown or inferred, salary median and quartiles by pay period, remote and hybrid shares, work types, top employers and classifications, levels (`seniority`), visa sponsorship share, median applicants, share listed in the last 7 days. A search needs at least 10 jobs delivered; jobs pasted by URL or id belong to no search. Charged per search delivered (event `market-stats`, see the pricing); unticked (default) = nothing.

## `postedAfter` (type: `string`):

Only jobs listed on or after this date (`publishedAt`): `2026-09-01`, or a period before now such as `7 days`, `24 hours`, `2 weeks`. A date is a day of the job's site: Sydney time on seek.com.au, Auckland time on seek.co.nz.

## `postedBefore` (type: `string`):

Only jobs listed on or before this date (`publishedAt`), same formats as **Posted after**.

## `includeKeywords` (type: `array`):

Keep only the jobs whose title, company, teaser, bullet points or description contain at least one of these words (case and accents ignored). The description is read only when **Extract details** is ticked.

## `titleKeywords` (type: `array`):

Keep only the jobs whose title contains at least one of these words (case and accents ignored).

## `excludeKeywords` (type: `array`):

Drop the jobs whose title, company, teaser, bullet points or description contain one of these words (case and accents ignored).

## `requireEmail` (type: `boolean`):

Keep only the jobs whose ad gives an e-mail address. Needs **Extract details**.

## `requirePhone` (type: `boolean`):

Keep only the jobs whose ad gives a phone number. Needs **Extract details**.

## `onlyWithSalary` (type: `boolean`):

Keep only the jobs with a salary (shown, or inferred when **Infer hidden salaries** is on).

## `minApplicants` (type: `integer`):

Keep only the jobs with at least this many applicants on SEEK. Jobs without an applicant count are dropped. Needs **Extract details**.

## `maxApplicants` (type: `integer`):

Keep only the jobs with at most this many applicants: find the jobs with little competition. Jobs without an applicant count are kept. Needs **Extract details**.

## `verifiedOnly` (type: `boolean`):

Keep only the jobs of advertisers with the SEEK verified badge. Needs **Extract details**.

## `onlyNew` (type: `boolean`):

Skip the jobs that a previous run (same **Memory key**) already delivered: they are not saved and not charged, and their page is not even opened. First run = everything is new.

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

Name of the memory used by **Only new jobs**. Give each schedule / task its own key (e.g. `sydney-nurses`) so that they do not share their memory. Letters, digits, `-` and `_`.

## `resetState` (type: `boolean`):

Forget the jobs delivered by previous runs with this **Memory key** before starting.

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

Apify Proxy or your own proxies. Keep the default: it is included in the price. The residential Apify proxy is not available in this Actor.

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

Maximum number of requests processed in parallel.

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

Global request rate. Lower it if the site answers HTTP 429 / 403 in the log.

## `minRequestIntervalMs` (type: `integer`):

Smallest gap between two requests, in milliseconds. Unlike the per-minute rate, this spreads the requests evenly instead of letting them go out in a burst. Default 0 (no gap: SEEK's APIs answered 17-22 requests a second without a refusal). Results pages and job ads only: the salary bands and the company job counts and reviews are not spaced.

## `maxRequestRetries` (type: `integer`):

Retries per request before it is marked as failed. Behind a proxy, a request the site turns away is also retried on a new proxy session up to 10 times without using up these retries.

## `debugLog` (type: `boolean`):

Include debug messages in the run log.

## Actor input object example

```json
{
  "startUrls": [],
  "jobIds": [],
  "country": "AU",
  "query": "nurse",
  "searchQueries": [],
  "location": "All Sydney NSW",
  "locations": [],
  "radius": 0,
  "maxItems": 100,
  "maxItemsPerQuery": 0,
  "classification": [],
  "subClassification": [],
  "workType": [],
  "workArrangement": [],
  "salaryType": "annual",
  "dateRange": "any",
  "sortBy": "date",
  "advertiserIds": [],
  "extractDetails": false,
  "inferHiddenSalary": false,
  "maxReviewsPerCompany": 0,
  "includeMarketStats": false,
  "includeKeywords": [],
  "titleKeywords": [],
  "excludeKeywords": [],
  "requireEmail": false,
  "requirePhone": false,
  "onlyWithSalary": false,
  "verifiedOnly": false,
  "onlyNew": false,
  "stateKey": "default",
  "resetState": false,
  "proxyConfiguration": {
    "useApifyProxy": true
  },
  "maxConcurrency": 8,
  "maxRequestsPerMinute": 600,
  "minRequestIntervalMs": 0,
  "maxRequestRetries": 5,
  "debugLog": false
}
```

# Actor output Schema

## `results` (type: `string`):

No description

## `marketStats` (type: `string`):

No description

# 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 = {
    "query": "nurse",
    "location": "All Sydney NSW",
    "proxyConfiguration": {
        "useApifyProxy": true
    }
};

// Run the Actor and wait for it to finish
const run = await client.actor("nice_dev/seek-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 = {
    "query": "nurse",
    "location": "All Sydney NSW",
    "proxyConfiguration": { "useApifyProxy": True },
}

# Run the Actor and wait for it to finish
run = client.actor("nice_dev/seek-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 '{
  "query": "nurse",
  "location": "All Sydney NSW",
  "proxyConfiguration": {
    "useApifyProxy": true
  }
}' |
apify call nice_dev/seek-jobs-scraper --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,nice_dev/seek-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/pWjje0kMxQHGbVlLk/builds/yvhIxlsnCnQdPRmEq/openapi.json
