# Seek Job Scraper (AU & NZ) - Salary, Company + Full Advert (`lanternlab318/seek-jobs`) Actor

Search Seek Australia and Seek New Zealand by keyword and location for public job adverts: title, company, advertiser, location, work type, salary text, classification, listing date, teaser, bullet points and URL - plus the full advert body on request.

- **URL**: https://apify.com/lanternlab318/seek-jobs.md
- **Developed by:** [Lantern Lab](https://apify.com/lanternlab318) (community)
- **Categories:** Jobs, Lead generation, Business
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

$1.59 / 1,000 jobs

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

## Seek Job Scraper (AU & NZ) — Salary, Company + Full Advert

A Lantern Lab Actor. Give it keywords and a location and get Seek's public job adverts back from **Seek Australia** (`seek.com.au`) or **Seek New Zealand** (`seek.co.nz`) — job id, title, hiring company, advertiser, location, work type and work arrangement, the **salary text** when the advertiser published one, classification, listing date, teaser and bullet points, plus the job URL. Switch one flag on and every row also carries the **full advert body**. No Seek login, no cookies, no account of your own.

**Price: USD 1.59 per 1,000 job rows. No run-start fee.** Empty searches, per-keyword status rows and the run summary are free.

### What you get

- **Both ANZ boards.** `site: "AU"` reads `au.seek.com`, `site: "NZ"` reads `nz.seek.com`. They are separate job boards, so a run covers one of them; run the Actor twice to cover both.
- **The salary, when there is one.** `salaryText` is Seek's own wording — `"$41.58 – $58.40 ph plus super"`, `"$78 per hour and paid training"`, `"Excellent hourly rates!"` — kept verbatim, never parsed into numbers that were not published. An advertiser who published no salary gives `salaryText: null`, not an empty string and not a guess.
- **The full advert on request.** `includeDescription: true` adds `description` (the HTML Seek serves) and `descriptionText` (the same body with the tags removed) — one extra request per job, included in the price.
- **Source id, publish time and collection time kept apart.** `jobId` is Seek's own identifier, `listedAt` is when Seek listed the advert (`publishedAt` is the same instant, under the field name the rest of this Actor family uses), `listedAtDisplay` is the relative phrase Seek prints (`"2d ago"`), and `collectedAt` is when this run read it. With `includeDescription` on, `expiresAt` and `isExpired` come too.
- **Deduplicated across the whole run.** A job that matches several of your keywords is written once and charged once; the later keyword's status row says how many it skipped as already collected, so that is never mistaken for an empty search.
- **Partial is never reported as empty.** A keyword Seek answered with no jobs is an empty result. A keyword cut short by a limit, a refusal or a failed request is `partial: true` with the reason (`upstream_request_cap`, `charge_limit_reached`, `blocked`, `upstream_error`, `not_collected`). The two are different records, and neither is charged.
- **Seek's own result ceiling is reported, not hidden.** Seek stops paging a search at **500 results**, whatever the page size. When that is what ended a list, the keyword's status row carries `resultDepthLimitReached: true` and `jobsAvailable` — Seek's own total — so you can see that there were more and narrow the search.
- **It caps itself.** `maxUpstreamRequests` is a hard ceiling on requests to Seek for the whole run, derived from your input when you leave it empty, so a run is never unbounded.

### What it does not do

- **No login-only data.** No saved searches, no applicant data, no recruiter contact details behind a login. Only what a logged-out visitor sees on a public job advert.
- **No personal data.** The company and advertiser names are the organisations that published the advert. Contact names, private phone numbers and email addresses are not collected.
- **No more than 500 jobs per keyword**, because Seek will not page past that. Split a broad keyword by location or by a narrower title to reach the rest.
- No company profiles, no salary-insight pages, no application submission, no job alerts.
- No scheduling or monitoring built in; use Apify schedules.

### Input

| Field | Default | Meaning |
| --- | --- | --- |
| `keywords` | required | One Seek search keyword per line. Quotes for an exact phrase work. Every keyword is searched separately in the location below. |
| `site` | `AU` | `AU` for `seek.com.au`, `NZ` for `seek.co.nz`. |
| `location` | whole country | A location as Seek names it: `"Sydney NSW"`, `"All Melbourne VIC"`, `"Auckland"`. Empty means `All Australia` / `All New Zealand`. |
| `maxJobs` | `100` | Upper bound on rows per keyword. Seek's own ceiling is 500. |
| `includeDescription` | `false` | Also fetch each job's full advert body. One extra request per job, included in the price. |
| `maxUpstreamRequests` | derived | Hard ceiling on requests to Seek for the whole run. |
| `proxyConfiguration` | Apify Proxy | Datacenter by default, which is what this Actor is measured on. Switch to residential or Australian exit IPs only if searches come back blocked. |

```json
{
  "keywords": ["registered nurse", "python developer"],
  "site": "AU",
  "location": "Sydney NSW",
  "maxJobs": 100,
  "includeDescription": false
}
```

### Output

One row per job, `recordType: "job"`:

```json
{
  "recordType": "job",
  "jobId": "95420643",
  "url": "https://au.seek.com/job/95420643",
  "title": "Mid-Level Software Developer",
  "companyName": "iasset.com",
  "advertiserName": "iasset.com Pty Ltd",
  "advertiserId": "27380635",
  "location": "Sydney NSW",
  "locationCountryCode": "AU",
  "locations": ["Sydney NSW"],
  "workType": "Full time",
  "workTypes": ["Full time"],
  "workArrangement": "Remote",
  "salaryText": null,
  "classification": "Information & Communication Technology",
  "classificationId": "6281",
  "subClassification": "Developers/Programmers",
  "subClassificationId": "6287",
  "listedAt": "2026-10-22T00:19:59.000Z",
  "publishedAt": "2026-10-22T00:19:59.000Z",
  "listedAtDisplay": "2d ago",
  "expiresAt": null,
  "isExpired": null,
  "teaser": "Join iasset.com as a Software Developer! Build innovative cloud-based SaaS solutions using .NET, Azure, React/Angular, and AI-assisted tools.",
  "bulletPoints": [
    "Flexible, remote-first workplace",
    "Birthday Leave!",
    "Shape the future of our platform"
  ],
  "description": null,
  "descriptionText": null,
  "descriptionStatus": "not_requested",
  "keyword": "python developer",
  "site": "AU",
  "searchLocation": "Sydney NSW",
  "rank": 1,
  "partial": false,
  "partialReason": null,
  "collectedAt": "2026-10-24T06:12:40.118Z"
}
```

One **free** `keyword_status` row per keyword says what that keyword returned and whether that is all there was:

```json
{
  "recordType": "keyword_status",
  "keyword": "python developer",
  "site": "AU",
  "searchLocation": "Sydney NSW",
  "resolvedLocation": "Sydney NSW 2000",
  "jobsReturned": 100,
  "jobsCharged": 100,
  "duplicatesSkipped": 0,
  "jobsAvailable": 1102,
  "resultDepthLimitReached": false,
  "descriptionsRequested": 0,
  "descriptionsResolved": 0,
  "descriptionBy": { "not_requested": 100 },
  "partial": false,
  "partialReason": null,
  "statusText": "Collected 100 job(s) for this keyword; this is everything Seek returned within your limits.",
  "collectedAt": "2026-10-24T06:12:40.120Z"
}
```

The last row of the dataset, also stored under the key `RUN_SUMMARY`, totals the run: jobs collected and charged, duplicates skipped, empty keywords, partial keywords by reason, advert bodies fetched, requests sent to Seek and why collection stopped.

### Limits worth knowing

- **500 results per search.** Seek's own paging ceiling, measured, not assumed: with `pageSize` 100 the sixth page comes back empty, and with 32 the twenty-fourth does. `jobsAvailable` tells you how many Seek claimed, so you can see what you are missing.
- **`companyName` and `advertiserName` can differ.** When a recruitment agency lists on a client's behalf, the card's company and the paying advertiser are not the same organisation. Both are written; neither is inferred from the other.
- **A `location` Seek cannot match returns zero jobs, not an error.** The keyword's status row shows `resolvedLocation: null` so a typo is visible rather than silent.
- **An advert can expire between the search and the description fetch.** Those rows carry `description: null` with `descriptionStatus: "not_found"`; the job itself is still collected from the search results.
- **One site per run.** AU and NZ are separate boards with separate job ids.

### Collection boundaries

Public job adverts only — what any logged-out visitor sees. No login, no cookies, no third-party or purchased accounts. Personal fields are kept to a minimum: organisations are named, individuals are not.

# Actor input Schema

## `keywords` (type: `array`):

One Seek search keyword per line, for example "registered nurse" or "python developer". Seek's own search syntax works, including quotes for an exact phrase. Every keyword is searched separately in the location below and its results are labelled with it. A job that matches several of your keywords is returned once and charged once.

## `site` (type: `string`):

Which Seek to search: Australia (au.seek.com) or New Zealand (nz.seek.com). They are separate job boards - a run searches one of them, so run the Actor twice to cover both.

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

A location as Seek names it in its own search box: "Sydney NSW", "All Melbourne VIC", "Brisbane QLD 4000", "Auckland", "Wellington". Leave it empty for the whole country ("All Australia" or "All New Zealand"). A spelling Seek cannot match returns zero jobs rather than an error, and the keyword's status row shows the location Seek did match.

## `maxJobs` (type: `integer`):

Upper bound on the rows one keyword may produce. Seek stops paging a search at 500 results whatever the page size, so 500 is the ceiling and a value above it does not reach more jobs - the keyword's status row then says resultDepthLimitReached, with jobsAvailable showing how many Seek claimed. Narrow the keyword or the location to reach past 500.

## `includeDescription` (type: `boolean`):

Also fetch each job's full advert text - one extra request per job, included in the price. Rows then carry description (the HTML Seek serves) and descriptionText (the same text with the tags removed). Leave it off for a much faster, lighter run: the teaser and bullet points still come from the search results. A job that expired between the search and the fetch gets description null with descriptionStatus "not_found".

## `maxUpstreamRequests` (type: `integer`):

Hard ceiling on the HTTP requests this run sends to Seek: search pages, advert-body fetches and retries together. At the ceiling collection stops - the keywords not reached are written with partialReason "upstream_request_cap" and the run then ends normally. Leave empty and the Actor works a ceiling out from your keywords and limits, so a run is never unbounded.

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

The proxy the requests to Seek go through. The default (Apify Proxy, datacenter) is the cheapest and is what this Actor is measured on; Seek's public search endpoint answers it without a bot check. Switch to residential, or to Australian exit IPs, only if searches start coming back blocked.

## Actor input object example

```json
{
  "keywords": [
    "registered nurse",
    "python developer"
  ],
  "site": "AU",
  "maxJobs": 100,
  "includeDescription": false,
  "proxyConfiguration": {
    "useApifyProxy": true
  }
}
```

# Actor output Schema

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

No description

## `runSummary` (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 = {
    "keywords": [
        "registered nurse",
        "python developer"
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("lanternlab318/seek-jobs").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 = { "keywords": [
        "registered nurse",
        "python developer",
    ] }

# Run the Actor and wait for it to finish
run = client.actor("lanternlab318/seek-jobs").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 '{
  "keywords": [
    "registered nurse",
    "python developer"
  ]
}' |
apify call lanternlab318/seek-jobs --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,lanternlab318/seek-jobs"
        }
    }
}
```

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/LDUvxKc5oocuQEGoo/builds/pfYbVIPmin8va7PQv/openapi.json
