# SEEK Job Listings $0.99/1K — JobsDB Jobs, New Zealand Jobs (`snow_leo_data/seek-jobstreet-jobsdb-australia-jobs-scraper`) Actor

$0.99/1K | 8 job markets — SEEK AU & NZ, JobStreet MY/SG/PH/ID, JobsDB HK/TH. The public API stops at 500; this goes past it. Australia job listings, Australian jobs, Hong Kong jobs, Philippines jobs, JobStreet job listings, JobsDB job listings, APAC job listings. Brisbane jobs, Gold Coast jobs.

- **URL**: https://apify.com/snow\_leo\_data/seek-jobstreet-jobsdb-australia-jobs-scraper.md
- **Developed by:** [Snow Leo Data](https://apify.com/snow_leo_data) (community)
- **Categories:** Jobs, Automation, Developer tools
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $0.99 / 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?

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, JobStreet & JobsDB Scraper — eight markets, past the 500 cap

**$0.99 per 1,000 jobs.** One Actor for the whole SEEK family: SEEK Australia and
New Zealand, JobStreet Malaysia, Singapore, the Philippines and Indonesia,
JobsDB Hong Kong and Thailand.

|  |  |
|---|---|
| **8 markets in one run** | all served by the same API — elsewhere on this store they are three separate Actors |
| **The public API stops at 500 jobs per query** | measured: page 5 at 100 rows is the last with data, page 6 returns an empty list |
| **This Actor goes past it** | measured: 1,500 unique jobs in 120 s from a single run, zero duplicates |
| **41 fields** | salary parsed into numbers, address split into suburb/city/state/postcode, contacts pulled out of the advert body |
| **$0.99 / 1,000** | the separate SEEK and JobStreet Actors on this store charge $2.00 |

### The 500 wall

Ask the search API for page 6 at 100 rows per page and it hands back an empty
list. So one query can never give you more than **500** jobs, whatever limit you
set — and a market like Australia has far more than that.

This Actor splits a wide search until every slice fits under the wall. How the
split is chosen, and why slices may overlap, is set out further down.

### Who this is for

**Recruiters and agencies across APAC.** One run, eight markets, one bill.
A search for nurse jobs covers Sydney and Kuala Lumpur.
Turn on *Only jobs not seen before* and a daily schedule charges you only for
what appeared since yesterday.

**Job boards and aggregators.** Every row carries the full advert text and a
share link, so a listing can be republished without a second fetch.

**Pay research.** `salary_label` is the employer's own wording, never inferred.
Adverts that state no pay come back empty rather than guessed.

### Quick start

Press **Start**. With no search term you get a 300-job sample of the newest
Australian listings.

```json
{
  "query": "software engineer",
  "markets": ["AU-Main", "NZ-Main"],
  "where": "Sydney",
  "salaryMin": 100000,
  "postedWithinDays": 7,
  "maxItems": 0
}
```

### What the SEEK job board actually carries

The SEEK job board is the front door to the Australian job market, and SEEK job
ads are what this Actor collects — SEEK job data as rows: title, employer, the
advert body and the pay field as the employer filled it in. Australian job listings are the bulk of any
wide run, and Australian employers from mining to aged care post here rather
than on a careers site of their own, so Australian vacancies that never reach an
ATS are still in reach, Perth jobs and Canberra jobs included.

The same API serves the rest of the family. The JobStreet job board covers the
Singapore job market and the Malaysia job market, and the JobsDB job board covers
Hong Kong and Thailand — eight markets, one run, one bill.
Hong Kong jobs data and Thai adverts land in the same 41 columns as an
Australian row, so nothing has to be reconciled afterwards.

SEEK salary data is the field most scrapers get wrong. `salary_label` is the
employer's own wording taken from the job detail record, the numeric bounds are
parsed out of it, and an advert that states no pay comes back empty rather than
guessed.

Asia Pacific jobs arrive in one schema: the same 41 fields whether the row came
from SEEK, JobStreet or JobsDB, so the eight markets merge without a mapping
step of your own.

Australia hiring data is what a wide run adds up to — who is advertising, for
what, where and at what pay. The raw job ads data is kept as the employer wrote
it, headline, body and bullet points, and the dataset a run leaves behind is a
vacancy data API you read by URL rather than a file you have to move. Each row
keeps the job ad text as the employer published it, so a job listings feed built
from a run reads the way the advert did. Recruitment agency data comes with it:
where an agency placed the advert, the agency is the `company` value.

#### Finding an industry in the data

SEEK publishes no taxonomy endpoint — `/classifications` answers 404 — so this
Actor reads the tree back out of live results. Job classification data therefore
lands on every row as `classification` and `sub_classification`, and the name
SEEK uses is rarely the name you would have searched for:

| What you are after | `classification` on the row |
|---|---|
| Retail jobs data | Retail & Consumer Products |
| Teaching jobs data | Education & Training |
| Driver jobs data, warehouse jobs data | Manufacturing, Transport & Logistics |
| Construction jobs data | Construction |
| Accounting jobs data | Accounting |
| Mining jobs data | Mining, Resources & Energy |
| Healthcare jobs data, nursing and aged care | Healthcare & Medical |
| Engineering jobs data | Engineering |
| Sales jobs data | Sales |

Twenty-seven top-level classifications came back on the day this page was last
updated. Pass the ones you want into **Classifications** and the walk splits
that branch alone, which costs less than filtering a wide run afterwards.

`work_types` carries SEEK's own employment terms, so contract jobs data, casual
and part-time rows separate without anyone reading the advert; part time jobs
data is that field, not a keyword guess. Salary range data
is two numbers with a currency and a period, parsed from the employer's own line
and left empty when there was no line. Recruiter contact data — emails, phones
and links — is pulled out of the advert body, because most employers put the
contact in the text and not in a field, which makes it employer contact data you
can act on without opening the advert.

One endpoint serves all eight markets: the SEEK jobs API behind seek.com.au also
answers for JobStreet and JobsDB, so JobStreet job listings and Singapore jobs
data come back in the same 41 fields as an Australia jobs API call, and Asia job
listings need no second Actor.

### Fields

41 per row. The ones that do the work:

| Field | What it is |
|---|---|
| `title`, `teaser`, `bullet_points` | headline, one-line summary, the advert's own highlights |
| `company`, `company_logo` | employer and logo |
| `location_suburb`, `location_city`, `location_state`, `location_postcode` | the address split up, so you can filter by state instead of matching strings |
| `salary_min`, `salary_max`, `salary_currency`, `salary_period` | pay parsed into numbers — `$120,000 – $150,000 per year` becomes `120000`, `150000`, `AUD`, `year` |
| `salary_label` | and the employer's own wording, kept as published |
| `work_types`, `work_arrangement` | Full time / Contract, and On-site / Hybrid / Remote |
| `classification`, `sub_classification` | SEEK's own category tree |
| `description_text`, `description_markdown`, `description_html` | the full advert in three forms; pick one to keep rows small |
| `contact_emails`, `contact_phones`, `contact_urls` | pulled out of the advert body — most employers put the contact in the text, not in a field |
| `listed_at`, `posted_days_ago`, `expires_at` | when it appeared and when it comes down |
| `change_type` | in incremental runs: NEW, UPDATED or UNCHANGED |
| `url` | straight to the listing |

Empty means the employer did not publish it. A salary line with no figure in it
comes back with no currency either — an invented `AUD` would be data that was
never there.

### Built for monitoring, and for agents

**`onlyNew`** remembers what it delivered in a named store that survives between
runs, and marks every row `NEW`, `UPDATED` or `UNCHANGED`. Unchanged jobs are
not returned at all unless you ask for them with `emitUnchanged` — you already
paid for those once. An advert counts as updated when its title, company, salary
or the first 2,000 characters of its body change; invisible edits further down
do not churn your bill.

**`compactOutput`** returns 18 core fields and no advert body — the shape an LLM
pipeline actually consumes. **`excludeEmptyFields`** drops what the employer left
blank instead of handing you twenty nulls. **`descriptionFormat`** keeps one of
text, markdown or HTML rather than all three, which is most of a row's weight.

### Salary, and why most scrapers get it wrong

SEEK's `salaryLabel` is a **free-text field**, not a salary. Employers put
whatever they like in it. Real examples pulled from the live API:

```
"$120,000 – $140,000 per year"                        a salary
"$55 - $65 per hour + super"                          a salary
"Suited to detail-oriented engineers with strong QA"  marketing copy
"Weekly pay cycle"                                    not a figure
"Attractive package"                                  not a figure
```

Copy that field into a `salaryText` column and a third of your "salaries" are
advertising slogans. This Actor parses it and fills `salary_min`, `salary_max`,
`salary_currency` and `salary_period` **only when there is actually money in the
string**. A line with no figure comes back with no currency and no period —
never a plausible-looking guess.

Currency falls back to the market's own (AUD on the Australian site, MYR on the
Malaysian one) when the employer wrote only `$`. An explicit code in the string
always wins.

### What this Actor does not have

The competing SEEK Actor also exposes **applicant counts** per listing. That
number is not reachable through the public search API or the job detail query —
probed, not assumed — so this Actor does not pretend to have it. It also does not
send Telegram or Slack notifications; use Apify's own integrations for that.

### Remote is a field, not a word

`remoteOnly` uses SEEK's own work-arrangement value. Searching the advert text
for "remote" instead would match "no remote work" and "remote location" just as
happily — `tests/test_logic.py` keeps a trap job for exactly that case.

### Filters run before you are charged

Keywords, exclusions, location, classification, work type, remote, posting age,
pay-stated-only, employer allow and block lists. A row a filter removes never
reaches the dataset and never appears on the bill. Dataset views can only choose
columns — no view on Apify can drop rows — so filtering lives in the input,
where it is also cheaper.

`maxItems` counts what you are **given**, not what was looked at. Ask for 30
construction jobs and you get 30 construction jobs: the Actor keeps walking the
source until your filters have passed that many rows, and the industries you
pick are handed to SEEK as a search constraint rather than being matched
afterwards. Filters work the same whether or not you ask for job descriptions.

### How the split works

#### Why a limit setting cannot help you

The 500 ceiling belongs to the source, not to your input. `maxItems` says how
many rows you want; it cannot make the API hand over page 6. A scraper that
issues one query and pages through it stops at 500 rows whatever the input says.

#### What the Actor does instead

It asks how many jobs match, and if the answer is over the wall the search is
cut into slices that each fit under it:

1. **By classification** — SEEK's own category tree, read from the results of
   your own search rather than a hard-coded list, because every market files
   jobs differently.
2. **By location** — state and metro names, again taken from the results.
3. **By salary band** — under 40k, 40–70k, 70–100k, 100–150k, 150k and up.
4. **By posting age** — 1, 3, 7, 14 and 31 days.

Each step runs only when the one before it leaves a slice still too large.

```
walk: 620 unique jobs in 85 s, 28 queries, split by classification, where, salaryrange
```

The run report shows exactly which splits were used.

#### Why slices are allowed to overlap

A job in Sydney that pays $120,000 appears in both the location slice and the
salary slice. Rather than building a perfect partition — impossible when the
taxonomy is not published — the walk deduplicates by listing id as it goes. You
are charged once per job.

### Eight markets, one run

#### Which sites these actually are

| Market | Site | Country |
|---|---|---|
| SEEK Australia | seek.com.au | AU |
| SEEK New Zealand | seek.co.nz | NZ |
| JobStreet Malaysia | jobstreet.com.my | MY |
| JobStreet Singapore | jobstreet.com.sg | SG |
| JobStreet Philippines | jobstreet.com.ph | PH |
| JobStreet Indonesia | jobstreet.co.id | ID |
| JobsDB Hong Kong | hk.jobsdb.com | HK |
| JobsDB Thailand | th.jobsdb.com | TH |

They share one search API, which is why one Actor covers them all. Elsewhere on
this store they are sold as three separate Actors.

Six of the eight are Southeast Asia and Hong Kong — Malaysia, Singapore, the
Philippines, Indonesia, Thailand and Hong Kong — so a Southeast Asia jobs
dataset and an Australia one come out of the same run with the same 41 columns,
rather than out of three scrapers you then have to reconcile.

#### Which endpoint is being read

The list comes from SEEK's own jobsearch v5 endpoint — the same
`/api/jobsearch/v5/search` call the site's search page makes in your browser —
and the advert body from the site's GraphQL job view. That is why the fields are
the site's own: work type, classification, work arrangement and the advertised
salary range arrive as SEEK stores them, not as guesses from the advert text.
The seek jobsearch v5 response is JSON, so there is no HTML parsing and no
headless browser anywhere in the run.

#### Running several at once

Pick as many as you like in **Markets**. The Actor walks them one after another
and stops when your limit is reached, so a limit of 500 across two markets does
not mean 500 from each. Every row carries `market` and `country_code`, so a
combined run stays sortable.

### Fields in detail

#### Pay, parsed and preserved

`salary_label` keeps the employer's exact wording. `salary_min`, `salary_max`,
`salary_currency` and `salary_period` are filled only when that wording actually
contains money. See the section above on why that distinction matters more here
than on most boards.

#### Addresses split up

`location` is the label as SEEK prints it — `Doncaster East, Melbourne VIC`.
Alongside it you get `location_suburb`, `location_city`, `location_state` and
`location_postcode`, so filtering by state is a comparison rather than a string
match.

#### Contacts out of the advert body

Many employers, and almost every recruitment agency, put an email or a phone
number in the advert text rather than in a field. `contact_emails`,
`contact_phones` and `contact_urls` collect what is there. Measured on 300 live
rows: 99 carried at least one contact.

### Running it on a schedule

#### Paying only for what changed

**Only jobs not seen before** keeps delivered ids in a named key-value store
that survives between runs. Every row then carries `change_type`:

- `NEW` — not seen in any previous run.
- `UPDATED` — seen before, but the title, company, salary or the first 2,000
  characters of the body changed.
- `UNCHANGED` — seen before and identical; not returned unless you ask.

Invisible edits deeper in the advert do not churn your bill, which is the point
of hashing only the part a reader would notice.

#### Where the output can go

The run page hands you the dataset as JSON, CSV, XML or Excel, which is the
whole workflow for a one-off market map. Anything repeatable goes through the
API instead:

```
https://api.apify.com/v2/datasets/<datasetId>/items?format=csv&clean=true
```

You do not have to track dataset ids. This address always answers with the last
finished run of the Actor, which is what a nightly job wants:

```
https://api.apify.com/v2/acts/<actorId>/runs/last/dataset/items?format=json
```

#### No-code pipelines, and the webhook underneath them

Apify maintains a connector for the tools most buyers already run —
[Make](https://docs.apify.com/platform/integrations/make),
[n8n](https://docs.apify.com/platform/integrations/n8n),
[Zapier](https://docs.apify.com/platform/integrations/zapier) and
[Slack](https://docs.apify.com/platform/integrations/slack) — and a
[webhook](https://docs.apify.com/platform/integrations/webhooks) for everything
else: when a run finishes, Apify calls your URL with the run id and the dataset
id in the payload.

In Make and n8n the scenario is the same two blocks every time: run the Actor,
then read its dataset. Name the storage if the pipeline depends on it — unnamed
storages on Apify are temporary by design, named ones stay.

#### Three things people build with this

**A candidate feed for one market.** Pick a single market, set the search term
and the salary floor, schedule it daily with the delta switch on, and new
adverts land in Slack or a sheet with the apply link already attached.

**Pay benchmarking across eight markets.** Run the same search term against
SEEK, JobStreet and JobsDB, keep only the adverts that state pay, and you have
a salary distribution per country that nobody had to guess at.

**A regional job board.** Every row carries the advert body as text or HTML, the
location broken up, the work type and the direct apply URL, so a listing can be
republished without a second fetch.

### Questions people ask

#### How many jobs can one run return?

As many as your limit allows. The 500 ceiling applies to a single query, not to
a run. A measured run returned 1,500 unique jobs in 120 seconds across a single
market, and the walk continues until the search is exhausted or your limit is.

#### Why do so few jobs have a salary?

Because most advertisers do not publish one, and because SEEK's salary field is
free text that often holds marketing copy instead of pay. Measured on 300 live
rows: 65 had a real figure. This Actor reports what is there and nothing more.

#### What is the difference between Remote only and searching for "remote"?

`remoteOnly` reads SEEK's own work-arrangement value. A text search for the word
also matches "no remote work" and "remote location", which is why this Actor
keeps a deliberately trapped job in its test suite for that exact case.

#### How do I get only new jobs each morning?

Turn on **Only jobs not seen before** and schedule the Actor daily. The first
run fills the memory; every run after that returns and bills for new or changed
listings only.

#### What does a first run cost?

The form starts at 300 jobs, which is under thirty cents. With no search term at all the Actor treats the run as a trial and keeps it to
that same starting size, so an accidental click cannot pull a whole market.

#### Can I feed this straight into an LLM?

Turn on **Compact output** for 18 core fields with no advert body, and
**Drop empty fields** to leave out what the employer never filled in. Set
**Description format** to markdown if you do want the body in a form a model
reads cleanly.

#### Does it need proxies?

No. It calls the same public endpoints the sites' own search pages call, keeps a
shared per-host pace and backs off when asked. Nothing in the run depends on a
proxy or a browser, which is why it is fast and why it costs what it costs.

#### A run succeeded but the dataset is empty — is that a failure?

Not necessarily, and the Actor always says which case it is. The run report
carries `empty_reason` and the log carries a warning naming it:
`nothing-changed` (the incremental mode working as asked — every job had
already been delivered by an earlier run, so nothing was written and nothing
was charged), `filtered-out` (your filters removed everything that was
collected), `source-returned-nothing` (the search matched no jobs on that
market at all), or `charge-limit-reached` (the run's spending limit was already
used up). A silent empty dataset is treated as a defect, not as a normal
outcome: tests assert that light runs with any single filter return rows.

### A number this README deliberately does not quote

SEEK's `totalCount` looks like "how many jobs exist", and it is not: the same
query returns **182,021** at the default page size, **168,267** at 100 rows and
**17,153** at 1 row, and it drifts between identical calls. So this README never
states a site total — only jobs this Actor actually collected and counted.
`tests/test_live.py` asserts that the field is unreliable, so nobody is tempted
to quote it later.

### Reproducing the numbers

```
python3 tests/run_all.py --live
```

Every figure above comes from that run.

# Actor input Schema

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

Keyword as you would type it on the site — `java`, `nurse`, `accountant`. Leave empty to take everything.

## `markets` (type: `array`):

Pick one or more. Each is a separate site sharing the same API — competitors sell them as separate Actors.

## `where` (type: `string`):

As the site spells it — `Sydney`, `Auckland`, `Kuala Lumpur`. Leave empty for the whole market.

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

In the market's own currency, as SEEK filters it.

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

Leave 0 for no upper bound.

## `postedWithinDays` (type: `integer`):

0 = no date filter.

## `remoteOnly` (type: `boolean`):

Uses SEEK's own work-arrangement field, not a word search in the advert — looking for "remote" in the text also matches "no remote work".

## `withSalaryOnly` (type: `boolean`):

Most adverts do not publish a salary. This drops the ones that do not.

## `workTypes` (type: `array`):

Substring match, e.g. `Full time`, `Contract`, `Casual`.

## `classifications` (type: `array`):

Substring match on SEEK's own category, e.g. `Information & Communication Technology`.

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

Checked against title, teaser, bullet points and the full advert.

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

Drops a job if any of these appears in its title, teaser or advert text.

## `locationContains` (type: `string`):

Extra text match on the location label, narrower than the Location field above.

## `companies` (type: `array`):

Substring match on the employer name — keeps only these.

## `excludeCompanies` (type: `array`):

Useful for leaving out recruitment agencies that repost the same role.

## `withDescriptionOnly` (type: `boolean`):

Drops listings whose advert body is empty at the source.

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

On: one extra request per job brings the full advert, the salary as published, the expiry date and the share link. Off: much faster and cheaper.

## `descriptionFormat` (type: `string`):

Three representations of the advert triple the row size. Keep the one you use.

## `compactOutput` (type: `boolean`):

18 core fields only, no advert body — for AI agents and MCP pipelines where the advert text is noise.

## `excludeEmptyFields` (type: `boolean`):

Leave out fields the employer did not publish instead of returning them empty.

## `newestFirst` (type: `boolean`):

Sort by listing date instead of relevance.

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

For schedules. Delivered jobs are remembered in a named store that survives between runs, so a daily run returns — and charges for — only what appeared since yesterday.

## `emitUnchanged` (type: `boolean`):

Off by default: an unchanged job is something you already paid for. Turn on if you need a full snapshot every run.

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

0 = no limit. The form starts at 300 so a first run is quick and cheap.

## Actor input object example

```json
{
  "query": "software engineer",
  "markets": [
    "AU-Main"
  ],
  "where": "",
  "salaryMin": 0,
  "salaryMax": 0,
  "postedWithinDays": 0,
  "remoteOnly": false,
  "withSalaryOnly": false,
  "workTypes": [],
  "classifications": [],
  "keywords": [],
  "excludeKeywords": [],
  "locationContains": "",
  "companies": [],
  "excludeCompanies": [],
  "withDescriptionOnly": false,
  "includeDescription": true,
  "descriptionFormat": "text",
  "compactOutput": false,
  "excludeEmptyFields": false,
  "newestFirst": true,
  "onlyNew": false,
  "emitUnchanged": false,
  "maxItems": 300
}
```

# Actor output Schema

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

Every job collected in this run.

## `report` (type: `string`):

How the query was split past the cap and what the filters removed before billing.

# 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": "software engineer",
    "markets": [
        "AU-Main"
    ],
    "maxItems": 300
};

// Run the Actor and wait for it to finish
const run = await client.actor("snow_leo_data/seek-jobstreet-jobsdb-australia-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": "software engineer",
    "markets": ["AU-Main"],
    "maxItems": 300,
}

# Run the Actor and wait for it to finish
run = client.actor("snow_leo_data/seek-jobstreet-jobsdb-australia-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": "software engineer",
  "markets": [
    "AU-Main"
  ],
  "maxItems": 300
}' |
apify call snow_leo_data/seek-jobstreet-jobsdb-australia-jobs-scraper --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,snow_leo_data/seek-jobstreet-jobsdb-australia-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/W2SQwzZ9XFuvqRHg6/builds/7UmsxcvmlVrs4EIeI/openapi.json
