# 🏆 AI Powered Advanced Seek Job Scraper ⚡ Best Value 🏆 (`claygenius/advanced-seek-job-scraper`) Actor

Scrape jobs from Seek, JobStreet and JobsDB across Australia, New Zealand, Singapore, Malaysia, Philippines, Indonesia, Hong Kong and Thailand — full descriptions, salary, contacts, with recruitment agencies filtered out.

- **URL**: https://apify.com/claygenius/advanced-seek-job-scraper.md
- **Developed by:** [Muhammad Shamshad Aslam](https://apify.com/claygenius) (community)
- **Categories:** Jobs, AI, Automation
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 2 bookmarks
- **User rating**: No ratings yet

## Pricing

from $1.00 / 1,000 results

This Actor is paid per event. You are not charged for the Apify platform usage, but only a fixed price for specific events.

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

## What's an Apify Actor?

Actors are web data automations that power AI and operations. They run on the Apify platform to scrape websites, process data, connect APIs, and automate workflows.
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.
Actors are written with capital "A".

## 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.
The best way to integrate Actors is as follows.

- **AI agents and MCP clients** — the [Apify MCP server](https://docs.apify.com/integrations/mcp.md) at `https://mcp.apify.com` (remote, streamable HTTP, OAuth on first use).
- **Agentic workflows and local Actor development** — [Agent Skills](https://apify.com/.well-known/agent-skills/index.json) with the [Apify CLI](https://docs.apify.com/cli/docs.md): `npm install -g apify-cli`, then `apify login`.
- **JavaScript/TypeScript projects** — the official [JS/TS client](https://docs.apify.com/api/client/js/docs.md): `npm install apify-client`.
- **Python projects** — the official [Python client](https://docs.apify.com/api/client/python/docs.md): `pip install apify-client`.
- **Any other language** — the [REST API](https://docs.apify.com/api/v2.md).

For usage examples, see the [API](#api) section below.

For more details, see Apify documentation as [Markdown index](https://docs.apify.com/llms.txt) and [Markdown full-text](https://docs.apify.com/llms-full.txt).

# README

## Seek Job Scraper 🇦🇺🇳🇿🇸🇬 — Seek, JobStreet & JobsDB

Scrape job listings from the whole Seek network — **Seek** (Australia, New Zealand), **JobStreet**
(Singapore, Malaysia, Philippines, Indonesia) and **JobsDB** (Hong Kong, Thailand) — from one
actor. Every job comes with the **full description**, salary, work type, work arrangement,
classification and the advertiser's details.

Then it goes further: **filter out recruitment agencies**, stop one advertiser flooding your
results, and push each job **straight into your workflow** as it's found.

### Features

**Core scraping**

- ✅ **8 country sites** — Australia, New Zealand, Singapore, Malaysia, Philippines, Indonesia, Hong Kong, Thailand
- ✅ **Full job description** on every listing, in clean text and original HTML
- ✅ **Rich structured data** — salary label, work type, work arrangement (Remote / Hybrid / On-site), classification and sub-classification, bullet points, listing date, advertiser ID
- ✅ **Real company profiles** — the employer's own website, industry, size, primary address, specialities, description and logo, each as its own field
- ✅ **Recruiter profiles** — name, photo and profile link from the "About the recruiter" card
- 💰 **Company salary estimates** — Seek's own per-role pay bands for each employer, free
- 🔎 **Post filters** — include/exclude keywords, required contact details, and a minutes-level freshness window
- 👥 **Applicant counts** — how many people have applied, plus what share attached a résumé or cover letter, with min/max filtering
- ⭐ **Ratings, reviews & AI summary** — star rating, six-category breakdown, Seek's AI review summary and individual employee reviews
- 🏢 **More jobs from the same company** — pull each employer's other openings, matched on advertiser ID
- ✅ **Paste a search URL** — reproduce any search from the site, filters and all, country detected from the domain
- ✅ **Real filters** — location, date posted, work type, salary range, sort by date or relevance
- ✅ **Duplicate-free** — deduplicated by job ID. Seek genuinely repeats listings across pages; a 12-job run already caught 2
- ✅ **Fast** — no browser, no login

**Beyond a plain scraper**

- 🤖 **Recruitment-agency filter** — Seek is saturated with recruiters. Flag or remove them and keep only companies hiring for themselves. The keyword screen is **free and needs no API key**; add OpenAI for the harder cases
- 🏢 **Max jobs per company** — stop one advertiser taking over your results
- 📇 **Contact extraction** — application email and phone pulled out of the ad text when the advertiser included them
- ⚡ **Per-job webhook** — each job POSTed to your endpoint the moment it's found, ready for Clay, n8n, Make or Zapier. No dataset fetch afterwards

### Why this one

| | This actor | What to watch for elsewhere |
|---|---|---|
| **Agency filtering** | Built in — free keyword screen plus optional AI | Usually absent. Seek is full of recruiters and you filter them by hand |
| **Company flooding** | `maxJobsPerCompany` caps any one advertiser | One company's 40 listings can eat your whole run |
| **Duplicates** | Deduplicated by job ID | Seek repeats ids across pages; without dedup you pay for the same job twice |
| **Delivery** | Per-job webhook into your workflow | Dataset only; you write the fetch-and-poll glue |
| **Salary** | The advertiser's own stated salary | Some actors charge **extra per 1,000** for *estimated* salaries |
| **Company pay bands** | Seek's per-role estimates included free | Charged as a paid add-on, or absent |
| **Ratings & reviews** | Star rating, category breakdown, AI summary, review text | Not offered |
| **More jobs per employer** | Expand any company, matched on advertiser ID | Not offered |
| **Applicant insight** | Count **plus** résumé and cover-letter rates | Count only |
| **Keyword filtering** | One field selector, used for include *and* exclude | Five toggles, with include and exclude searching different fields |
| **Company profile** | Real data — website, industry, size, address, specialities | Often returns `"N/A"` for every field |
| **All 8 sites** | One actor, one input | Sometimes split across separate actors |

**The company profile actually works.** Seek's `companyProfile` takes a mandatory `zone`
argument — omit it and the entire field silently returns null, which is why competing actors
document it as `"N/A"` in every field. Pass it and you get the real thing — on a Melbourne nursing search, **14 of 14 rows** carried a
full profile:

```
Baptcare            baptcare.org.au   Social & Welfare Services   >10,000 employees
                    Level 1, 1193 Toorak Road Camberwell.
                    NFP, Aged Care, Affordable Housing, FACS and Disability, In Home Care
```

**Measured, not asserted.** On a "recruitment consultant" search the free screen — with no API
key at all — correctly flagged **11 of 14** listings as agencies (Gough Recruitment, Logical
Staffing Solutions, Core Talent, 360 Personnel, PULSE Staffing…) while keeping genuine
employers. Every token is matched as a **separate word**, so real companies like *Cheshire*,
*Staffordshire* and *Jobst* are never caught by "hire", "staff" or "job".

### Input

| Field | Type | Default | Description |
|---|---|---|---|
| `searchUrl` | string | — | **Option 1.** Paste a Seek/JobStreet/JobsDB search URL and everything below is read from it |
| `country` | select | `au` | Which site: `au`, `nz`, `sg`, `my`, `ph`, `id`, `hk`, `th` |
| `keyword` | string | `software engineer` | **Required.** Job title, skill or company |
| `location` | string | — | City, suburb or region. Empty = whole country |
| `sortBy` | select | `relevance` | `relevance` or `date` (newest first) |
| `postedWithinDays` | select | `0` | Any time / 1 / 3 / 7 / 14 / 31 days |
| `workType` | select | all | Full time, Part time, Contract/Temp, Casual |
| `classification` | select | all | Any of Seek's 30 job categories (Accounting → Trades & Services) |
| `salaryMin` / `salaryMax` | integer | `0` | Salary band in local currency |
| `salaryType` | select | `annual` | Annual, Monthly or Hourly |
| `maxItems` | integer | `100` | How many jobs to scrape (see the cap below) |
| `maxJobsPerCompany` | integer | `2` | Cap per advertiser (2–10, max 10). `0` = no limit |
| `fetchJobDetails` | boolean | `true` | Full description + contact extraction. One extra request per job |
| `minApplicants` | integer | `0` | Only keep jobs with at least N applicants. `0` = no minimum |
| `maxApplicants` | integer | `0` | Only keep jobs with at most N applicants. `0` = no maximum |
| `includeKeywords` | array | — | Keep jobs containing at least one of these |
| `excludeKeywords` | array | — | Drop jobs containing any of these |
| `keywordMatchFields` | multi-select | title, company, description, bullets, category | Which parts of the listing the keywords search |
| `requireContactDetails` | select | `off` | `email` / `phone` / `either` / `both` |
| `maxAgeMinutes` | integer | `0` | Keep only jobs listed in the last N minutes |
| `fetchCompanySalaries` | boolean | `false` | Seek's per-role salary bands for each employer. One call per company, cached |
| `fetchCompanyReviews` | boolean | `false` | Star rating, category breakdown, AI summary and employee reviews |
| `maxReviewsPerCompany` | integer | `5` | Individual reviews per company (1–30). Rating and summary always included |
| `fetchMoreCompanyJobs` | boolean | `false` | Also pull each employer's other open roles |
| `moreJobsPerCompany` | integer | `5` | How many extra jobs per company (max 25). Needs the toggle above |
| `expandedJobsOutput` | select | `nested` | `nested` = extras hang off each row; `separateRows` = own rows |
| `recruitmentAgencyHandling` | select | `off` | `off` / `flag` / `exclude` |
| `openaiApiKey` | string | — | Optional, for the AI pass. The free screen runs without it |
| `openaiModel` | select | `gpt-4o-mini` | Fixed — the nano models gave unreliable verdicts and were removed |
| `webhookUrl` | string | — | POST each job to your URL as it's found |
| `proxyConfig` | proxy | off | Usually unnecessary — Seek serves this without blocking |

#### Paste a search URL

Build the search on the site with whatever filters you like, paste the URL, and the actor
reproduces it exactly:

```json
{ "searchUrl": "https://www.seek.com.au/software-engineer-jobs/in-All-Sydney-NSW?classification=6281&worktype=242" }
```

```
Parsed search URL -> site=www.seek.com.au keyword="software engineer"
                     location="All Sydney NSW" classification=6281 workType=242
```

Keyword, location **and work type** are read from the SEO path — Seek puts filters there, not
only in the query string:

```
/software-engineer-jobs/in-All-Sydney-NSW/part-time
   keyword ──────┘        location ──┘      work type ┘
```

The **country is detected from the domain**, in every form Seek uses: `www.seek.com.au`,
`au.seek.com`, `seek.com.au`, `sg.jobstreet.com`, the older `jobstreet.com.sg`, `th.jobsdb.com`
and so on. Open-ended salary ranges (`salaryrange=50000-`) work too. Paste a single job URL by
mistake and you get a clear error rather than an empty run.

#### Job classification

`classification` restricts results to one of Seek's 30 categories — the same list as the
Classification filter on the site, from Accounting through to Trades & Services. The IDs were
taken from Seek's own live data, so they match the site exactly.

#### The 500-result cap

**Seek returns at most 500 results for any single search.** Page 6 comes back empty no matter
what you ask for — this is Seek's limit, not the actor's, and every scraper hits it.

To gather more, run several narrower searches and combine them: by **location** (Sydney,
Melbourne, Brisbane…), by **keyword** (`software engineer`, `backend engineer`, `full stack`),
or by **work type**. Each search gets its own 500. Jobs are deduplicated by ID within a run, so
overlapping searches never produce duplicate rows.

#### Max jobs per company

One advertiser can swamp a search. `maxJobsPerCompany` caps how many rows any single advertiser
contributes, so you see the market instead of one company's careers page.

**`0` means unlimited, not one per company** — whatever that advertiser has, you get, up to
`maxItems`. Defaults to `2`. Skipped jobs do **not** count towards `maxItems`, so you still get
the number you asked for, just spread across more employers.

#### Company salary estimates

Turn on `fetchCompanySalaries` and every employer's own pay bands come with the jobs — the same
data behind the **Salaries** tab on their Seek company page:

```
Victoria Police  (72 roles, updated 2 Jul 2026)
  Administration Officer          $60K - $77K     5 job ads
  Administration Team Leader      $79K - $96K     1 job ad
  Advocate                        $113K - $137K   3 job ads
```

Large employers return a lot — **Northern Health has 116 roles**, Victoria Police 72, Serco 26.
Fetched **once per company and cached**, so an employer with 30 jobs in your results costs a
single call.

| Field | Meaning |
|---|---|
| `companySalaryRanges` | Array of `{ role, salaryRange, basedOnJobAds, salaryType, source }` |
| `companySalaryRoleCount` | How many roles came back |
| `companySalaryUpdatedAt` | When Seek last refreshed the estimates |

`source` is `"company"` when the band comes from that employer's own past ads, or `"industry"`
when Seek fell back to an industry average — so you can tell the difference rather than
treating both as equal.

> These are **Seek's estimates**, not published salaries. The advertiser's own stated salary,
> when there is one, stays in the separate `salary` field and is never overwritten. Coverage is
> partial: 3 of 8 employers in a Melbourne nursing test had bands.

#### Post filters

Seek's search cannot express these, so they run after collection. Filtered-out jobs do **not**
count towards `maxItems`, and the log reports exactly what each filter removed:

```
Post filters: 19 dropped by keywords, 0 by contact requirement, 0 by age.
Matching on: title, company.
```

**One field selector, used for both directions.** `keywordMatchFields` decides which parts of a
listing the keywords search, and the *same* fields apply to include and exclude. That symmetry
matters: where include and exclude search different fields, `"remote"` can keep a job on its
description while failing to drop one whose *salary* text reads "remote work allowance" — a
surprise you then have to explain. Here it behaves the same way in both directions.

The default set is title, company, description, bullet points and category. Add `location`,
`salary` or `workType` deliberately if you want them.

| Filter | Behaviour |
|---|---|
| `includeKeywords` | Keep a job if it contains **at least one** |
| `excludeKeywords` | Drop a job if it contains **any** |
| `requireContactDetails` | Keep only jobs with an email, a phone, either, or both |
| `maxAgeMinutes` | Keep only jobs listed in the last N minutes |

> **Two of these cost extra requests.** Requiring contact details, or matching keywords against
> `description`, both force the per-job detail call even when `fetchJobDetails` is off — that is
> where the description and contacts live. Post filters run **before** company enrichment, so a
> row about to be dropped never triggers a salary or review lookup.

> `maxAgeMinutes` is for scheduled runs polling for brand-new listings. For coarser windows use
> `postedWithinDays`, which filters on Seek's side and costs nothing.

#### Applicant counts

Seek records how many people have applied to each job, and the actor returns it on every row:

| Field | Meaning |
|---|---|
| `applicantCount` | Number of applicants so far |
| `applicantsWithResumePercent` | Share who attached a résumé |
| `applicantsWithCoverLetterPercent` | Share who attached a cover letter |

```
Registered Nurse - Ivanhoe   16 applicants · 100% résumé · 81% cover letter
```

`minApplicants` and `maxApplicants` filter on it — low counts find roles before the rush, high
counts show where demand is proven.

> **Know the cost.** Seek has **no applicant filter on its search API**, so every job has to be
> fetched before its count can be checked, and rows outside your range are discarded after that
> work is done. A narrow range can burn most of a run — the log tells you exactly how many were
> dropped. Discarded jobs do **not** count towards `maxItems`.

> Jobs whose applicant count is unavailable are **kept**, never guessed at. Seek hides these
> values behind a sign-in on its own site; the API returns them regardless, so no login is needed.

#### Company ratings, reviews & AI summary

Turn on `fetchCompanyReviews` and every employer arrives with what their staff actually say:

```
Qualitas Health   1.8★ from 4 reviews
  work/life 3.0 · career 2.0 · management 1.8 · environment 2.0 · perks 1.5 · diversity 1.5
  AI summary: "Employees appreciate the great reception and management staff…"
```

| Field | Meaning |
|---|---|
| `companyRating` / `companyRatingCount` | Overall star rating and how many reviews it is based on |
| `companyRecommendedPercent` | Share of reviewers who would recommend the employer |
| `companyRatingBreakdown` | Six categories: work/life balance, career opportunity, management, environment, perks, diversity |
| `companyReviewsAISummary` | Seek's own AI summary of the positives and challenges |
| `companyReviews` | Individual reviews — title, job title, rating, pros, cons, employment status, date |
| `companyReviewsTotal` | Total reviews on file, so you can see what your sample covers |

Fetched **once per company and cached**. `maxReviewsPerCompany` controls how many individual
reviews come along (1–30); the rating, breakdown and AI summary are always included.

#### More jobs from the same company

A keyword search only shows the one role that matched. Turn on `fetchMoreCompanyJobs` and the
actor goes back to each employer and pulls more of their open positions:

```
Expanding 3 companies, up to 4 more job(s) each.
  Zurich Financial Services: +4 more job(s)
```

Companies are matched on **Seek's advertiser ID**, never the name, so expanding one employer
can never drag in a similarly-named company.

| Mode | Behaviour |
|---|---|
| `nested` (default) | One row per search result; extras hang off each row in `otherJobsAtCompany`. 5 requested → 5 rows |
| `separateRows` | Every extra job becomes its own row, tagged `foundVia: "company-expansion"`. 5 requested → 9 rows |

Off by default. Results stay deduplicated by job ID across the search and the expansion alike.

#### Recruitment agencies

Seek is heavily used by recruiters, so a plain search is mostly agencies. Three modes:

| Mode | Behaviour |
|---|---|
| `off` (default) | No classification, no cost |
| `flag` | Classify and label every job, keep them all |
| `exclude` | Drop agency listings from the dataset and the webhook |

A **free screen** on advertiser name and domain runs first and needs no API key. Adding an
`openaiApiKey` enables an AI pass over the ad text for advertisers the keywords miss — one call
per advertiser, cached against their Seek advertiser ID, so a company with 30 listings costs a
single call.

Classified rows carry `isRecruitmentAgency`, `recruitmentAgencyReason` and
`recruitmentAgencyMatchedBy` (`name`, `domain` or `ai`).

> **Nothing is dropped on a guess.** Advertisers the AI cannot judge — and any whose call fails
> — are kept and labelled `null`. In `exclude` mode a row is only removed on a positive verdict.

#### Contact details

With `fetchJobDetails` on, any application email or phone number stated in the ad text is
extracted into `email` and `phone`.

> **Be realistic about coverage.** Most jobs contain neither — contacts appear mainly on
> recruiter-posted ads. In testing, 2 of 12 listings carried an email and none a phone. These
> fields are a bonus when present, not something to plan a pipeline around.

### Output

One row per job, 30 fields (34 with details and agency classification):

```json
{
  "jobId": "93631135",
  "title": "Senior Software Engineer",
  "url": "https://www.seek.com.au/job/93631135",
  "applyUrl": "https://www.seek.com.au/job/93631135/apply",
  "companyName": "Halcyon Knights",
  "advertiserId": "22319711",
  "advertiserName": "Halcyon Knights Pty Ltd",
  "companyUrl": "https://au.seek.com/companies/halcyon-knights-22319711",
  "location": "Melbourne VIC",
  "locations": ["Melbourne VIC"],
  "countryCode": "AU",
  "salary": "$150,000 – $180,000 per year",
  "workType": "Full time",
  "workArrangement": "Hybrid",
  "classification": "Information & Communication Technology",
  "subClassification": "Developers/Programmers",
  "bulletPoints": ["Build AI-Driven Solutions", "Modern Engineering Practices"],
  "teaser": "Join a team building…",
  "listingDate": "2026-07-29T06:32:59Z",
  "listingDateDisplay": "4d ago",
  "isFeatured": false,
  "isPromoted": false,
  "roleId": "senior-software-engineer",
  "searchKeyword": "software engineer",
  "site": "www.seek.com.au",
  "country": "au",
  "scrapedAt": "2026-08-01T09:00:00.000Z",

  "description": "Full plain-text job description…",
  "descriptionHtml": "<p>…</p>",
  "abstract": "Short summary",
  "email": "careers@example.com",
  "phone": "0406 811 336",

  "recruiterName": "Chelsea Hoey",
  "recruiterPhoto": "https://media.seekrecruiterprofile.com/seek/auto/chelsea-hoey.jpeg",
  "recruiterProfileUrl": "https://www.seek.com.au/recruiters/people/chelsea-hoey",

  "companySeekUrl": "https://au.seek.com/companies/baptcare-434578",
  "companyProfileName": "Baptcare",
  "companyWebsite": "http://baptcare.org.au/",
  "companyIndustry": "Social & Welfare Services",
  "companySize": "More than 10,000 employees",
  "companyAddress": "Level 1, 1193 Toorak Road Camberwell.",
  "companySpecialities": "NFP, Aged Care, Affordable Housing, FACS and Disability, In Home Care",
  "companyDescription": "The employer's own profile text…",
  "companyLogo": "https://…/logo.png",

  "searchClassification": "6281",
  "foundVia": "search",
  "expandedFromCompany": null,
  "otherJobsAtCompany": [
    { "jobId": "93701234", "title": "Claims Advisor", "location": "North Sydney NSW", "url": "https://www.seek.com.au/job/93701234" }
  ],
  "otherJobsAtCompanyCount": 1,

  "companyRating": 1.8,
  "companyRatingCount": 4,
  "companyRecommendedPercent": 42,
  "companyRatingBreakdown": { "workLifeBalance": 3, "careerOpportunity": 2, "executiveManagement": 1.8, "workEnvironment": 2, "perksAndBenefits": 1.5, "diversity": 1.5 },
  "companyReviewsAISummary": "Employees appreciate the great reception and management staff…",
  "companyReviewsTotal": 4,
  "companyReviews": [
    { "title": "Great team, tough workload", "jobTitle": "Registered Nurse", "rating": 2, "pros": "…", "cons": "…", "recommended": false, "employmentStatus": "2 to 3 years in the role, former employee", "date": "18 May 2026" }
  ],

  "companySalaryUpdatedAt": "2 Jul 2026",
  "companySalaryRoleCount": 72,
  "companySalaryRanges": [
    { "role": "Administration Officer", "salaryRange": "$60K - $77K", "basedOnJobAds": 5, "salaryType": "ANNUAL_PACKAGE", "source": "company" }
  ],
  "isRecruitmentAgency": true,
  "recruitmentAgencyReason": "name contains \"Recruitment\"",
  "recruitmentAgencyMatchedBy": "name"
}
```

| Field | Notes |
|---|---|
| `advertiserId` | Seek's stable advertiser ID — the key used for capping and agency caching |
| `salary` | The advertiser's own stated salary. Many listings have none, and nothing is invented |
| `workArrangement` | Remote / Hybrid / On-site, when the advertiser set it |
| `email` / `phone` | Only when stated in the ad text — see the coverage note above |
| `recruiterName` / `recruiterPhoto` / `recruiterProfileUrl` | The "About the recruiter" card, when the recruiter has an opted-in Seek profile |
| `companySeekUrl` | The employer's page on Seek, e.g. `au.seek.com/companies/baptcare-434578` |
| `companyWebsite` | The employer's **own** website — not their Seek page |
| `companyIndustry` / `companySize` / `companyAddress` | From the employer's Seek profile: "Social & Welfare Services", "More than 10,000 employees", "Level 1, 1193 Toorak Road Camberwell." |
| `companySpecialities` | The employer's stated specialities, as its own field rather than mixed into the description |
| `companyDescription` / `companyLogo` | Profile overview text and logo |
| `isRecruitmentAgency` | `true` / `false`, or `null` when it could not be judged |

### Use cases

- **Recruiting & sourcing** — monitor roles across APAC with agencies filtered out, so you only see employers hiring directly
- **Lead generation** — advertiser names, company pages and any stated contacts, pushed into Clay or your CRM by webhook
- **Market research** — salary bands, remote/hybrid split, and hiring volume by classification across 8 countries
- **Job boards** — feed fresh listings with full descriptions into your product

### Notes

- Salary is only present when the advertiser published one. This actor reports what is stated and never estimates.
- Each search is capped at 500 results by Seek. Split by location or keyword to gather more.
- Export as CSV, Excel or JSON from the dataset tab, or via the Apify API.

# Actor input Schema

## `searchUrl` (type: `string`):

Paste a search URL from Seek, JobStreet or JobsDB and the actor reproduces that exact search — keyword, location, classification, work type, salary and date filters are all read from it, and the country is detected from the domain. When set, the fields below are ignored. Must be a SEARCH page, not a single job.

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

Which Seek-family site to search.

## `keyword` (type: `string`):

Job title, skill or company (e.g. 'registered nurse', 'software engineer', 'forklift').

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

City, suburb or region (e.g. 'Sydney', 'Auckland', 'Kuala Lumpur'). Leave empty to search the whole country.

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

Relevance is Seek's default ranking. Date puts the newest listings first.

## `postedWithinDays` (type: `string`):

Only include jobs listed within this window.

## `workType` (type: `string`):

Filter by employment type.

## `classification` (type: `string`):

Restrict results to one of Seek's job categories, the same list as the Classification filter on the site.

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

Minimum salary in the site's local currency. 0 = no minimum. Only applied together with a Salary Type.

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

Maximum salary in the site's local currency. 0 = no maximum.

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

Which salary basis the minimum and maximum above refer to.

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

Maximum number of jobs to scrape. Seek caps every single search at 500 results, so ask for more than that and you will get 500 — narrow the search or run it again with a different keyword or location.

## `maxJobsPerCompany` (type: `integer`):

Keep at most this many jobs from the same advertiser, so one company cannot flood your results. Defaults to 2, which gives far more distinct employers for the same number of rows. Typical values are 2-10; the maximum is 10. Use 0 for no limit at all. Skipped jobs do NOT count towards Max Items.

## `fetchJobDetails` (type: `boolean`):

Visit each job for the complete description, and extract any application email and phone number stated in the text. One extra request per job, so it is slower — but this is where the contact details live.

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

Only keep jobs with at least this many applicants. 0 = no minimum. NOTE: Seek has no applicant filter on its search, so every job must be fetched before it can be checked — a narrow range can discard most of a run. Jobs whose applicant count is unknown are kept, never guessed at.

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

Only keep jobs with at most this many applicants. 0 = no maximum. Great for finding low-competition roles before they fill up.

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

Keep only jobs containing at least ONE of these words. Case-insensitive. Which parts of the listing are searched is set by 'Match In Fields' below — the SAME fields are used for include and exclude, so a word can never match on one and be missed by the other.

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

Drop any job containing ANY of these words. Searched in the same fields as Include Keywords, so behaviour is symmetric and predictable.

## `keywordMatchFields` (type: `array`):

Which parts of the listing the include/exclude keywords search. Leave empty for the sensible default: title, company, description, bullet points and category. Add location, salary or work type only if you want them — searching salary text is how 'remote' ends up matching a 'remote work allowance'.

## `requireContactDetails` (type: `string`):

Keep only jobs where the advertiser put a contact in the ad text. Note that most jobs have neither — this is common on recruiter-posted ads and rare elsewhere, so expect a small result set.

## `maxAgeMinutes` (type: `integer`):

Keep only jobs listed within the last N minutes. Built for scheduled runs that poll for brand-new listings — e.g. 60 for the last hour. 0 = off. For coarser windows use Date Posted above, which filters on Seek's side and is far cheaper.

## `fetchCompanySalaries` (type: `boolean`):

Also pull Seek's own per-role salary bands for each employer — the same data behind the Salaries tab on their company page, e.g. 'Marketing Manager $100K-$120K, based on 1 job ad'. Fetched ONCE per company and cached, so an employer with 30 jobs costs a single call. Large employers can return 100+ roles. These are Seek's estimates, some from the company's own past ads and some from industry averages.

## `fetchCompanyReviews` (type: `boolean`):

Also pull each employer's star rating, category breakdown (work/life balance, career opportunity, management, diversity...), Seek's AI review summary, and the most recent employee reviews. Fetched ONCE per company and cached, so an employer with 30 jobs costs a single call.

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

How many individual employee reviews to include per company (1-30). The star rating, breakdown and AI summary are always included regardless.

## `fetchMoreCompanyJobs` (type: `boolean`):

No by default. Turn on to also collect other open roles from every employer that appears in your results. Companies are matched by Seek's advertiser ID, so a search never mixes in a similarly-named company.

## `moreJobsPerCompany` (type: `integer`):

How many other jobs to pull from each company. Accepts up to 25. Ignored unless the switch above is turned on.

## `expandedJobsOutput` (type: `string`):

Nested (recommended): your dataset keeps exactly one row per search result, and each row gains an 'otherJobsAtCompany' list — no unrelated titles mixed into your results. Separate rows: every extra job becomes its own full row, which suits feeding a job board, but your dataset will contain titles unrelated to your search and will hold more rows than Max Items.

## `recruitmentAgencyHandling` (type: `string`):

Seek is heavily used by recruitment agencies. This reads the advertiser's name, domain and description to decide whether they are a staffing firm hiring on behalf of others, and either labels or removes them. The keyword screen is free; add an OpenAI key for the AI pass. IMPORTANT: the AI can only judge companies whose description clearly states what the business does — companies with no description are kept and labelled null, never removed. Use 'Flag only' first to review the verdicts.

## `openaiApiKey` (type: `string`):

Optional. Your own OpenAI API key (sk-...), used only for the recruitment-agency filter above. Without it the free keyword screen still runs. Billed to your OpenAI account.

## `openaiModel` (type: `string`):

Fixed to gpt-4o-mini. The nano models were tried and returned unreliable verdicts for this classification, so they have been removed rather than left as traps.

## `webhookUrl` (type: `string`):

Optional. POST each scraped job straight to your own URL as it is found — one HTTP request per job, with that job as the JSON body. Your workflow never has to fetch the dataset afterwards. Leave empty to skip.

## `proxyConfig` (type: `object`):

Optional. Seek serves this data without blocking, so proxies are usually unnecessary — enable them only for very large runs.

## Actor input object example

```json
{
  "searchUrl": "",
  "country": "au",
  "keyword": "software engineer",
  "sortBy": "relevance",
  "postedWithinDays": "0",
  "workType": "",
  "classification": "",
  "salaryMin": 0,
  "salaryMax": 0,
  "salaryType": "annual",
  "maxItems": 50,
  "maxJobsPerCompany": 2,
  "fetchJobDetails": true,
  "minApplicants": 0,
  "maxApplicants": 0,
  "includeKeywords": [],
  "excludeKeywords": [],
  "keywordMatchFields": [],
  "requireContactDetails": "off",
  "maxAgeMinutes": 0,
  "fetchCompanySalaries": false,
  "fetchCompanyReviews": false,
  "maxReviewsPerCompany": 5,
  "fetchMoreCompanyJobs": false,
  "moreJobsPerCompany": 5,
  "expandedJobsOutput": "nested",
  "recruitmentAgencyHandling": "off",
  "openaiModel": "gpt-4o-mini",
  "webhookUrl": "",
  "proxyConfig": {
    "useApifyProxy": false
  }
}
```

# Actor output Schema

## `jobs` (type: `string`):

All scraped jobs: title, company, location, salary, work type and URL.

# 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 = {
    "keyword": "software engineer",
    "maxItems": 50,
    "maxJobsPerCompany": 2,
    "maxReviewsPerCompany": 5,
    "moreJobsPerCompany": 5,
    "proxyConfig": {
        "useApifyProxy": false
    }
};

// Run the Actor and wait for it to finish
const run = await client.actor("claygenius/advanced-seek-job-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 = {
    "keyword": "software engineer",
    "maxItems": 50,
    "maxJobsPerCompany": 2,
    "maxReviewsPerCompany": 5,
    "moreJobsPerCompany": 5,
    "proxyConfig": { "useApifyProxy": False },
}

# Run the Actor and wait for it to finish
run = client.actor("claygenius/advanced-seek-job-scraper").call(run_input=run_input)

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

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

```

## CLI example

```bash
echo '{
  "keyword": "software engineer",
  "maxItems": 50,
  "maxJobsPerCompany": 2,
  "maxReviewsPerCompany": 5,
  "moreJobsPerCompany": 5,
  "proxyConfig": {
    "useApifyProxy": false
  }
}' |
apify call claygenius/advanced-seek-job-scraper --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "command": "npx",
            "args": [
                "mcp-remote",
                "https://mcp.apify.com/?tools=claygenius/advanced-seek-job-scraper",
                "--header",
                "Authorization: Bearer <YOUR_API_TOKEN>"
            ]
        }
    }
}

```

## OpenAPI specification

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