SeekAnswer

Does Seek have an API? Australian job ads as JSON

Seek's API is for recruitment software, not job data. Our Python script saved 290 Seek job ads in 10 calls; 27% to 60% gave a salary figure.

5 min read

Short answer

  • The SEEK API is for recruitment software that posts ads and collects applications, and access comes through an integration request. It has no route for searching other employers' ads.
  • ToolzerHub's Seek API returns 32 job ads a page for a keyword and location, with advertiser, salary text, work type and listing date, at 1 credit per call.
  • Five pages gave 126 to 150 distinct ads of 160. 41 of 150 Sydney data analyst ads and 84 of 140 Brisbane electrician ads gave a salary figure.
On this page

Seek has an API, but it is not for reading job ads. The SEEK API connects recruitment software to Seek so employers can post ads and collect applications. To read job ads, salaries and employers as data, use our Seek API; the script below saves a search to CSV.

What the SEEK API is for

The SEEK API is for developers integrating recruitment software with Seek. You file an integration request, test against read-only mock data in a Playground, then ask for live credentials. Partner integrations post job ads, receive applications and search candidate profiles, and searching other employers' ads is not among them. If you build recruitment software for employers who advertise on Seek, apply for it.

Seek job ads with ToolzerHub

We make ToolzerHub. Our Seek API has three routes, each a GET with the x-api-key header at 1 credit a call:

  • /v1/seek/search?query=...&location=...&page=... returns 32 job ads a page, a total count and related searches.
  • /v1/seek/job?job_id=... returns one ad in full, with the description, structured location and expiry date.
  • /v1/seek/company?slug=... returns an employer's Seek profile.
bash
export TOOLZERHUB_API_KEY=your_key
curl -G -H "x-api-key: $TOOLZERHUB_API_KEY" \
  --data-urlencode "query=data analyst" --data-urlencode "location=Sydney NSW" \
  "https://api.toolzerhub.com/v1/seek/search"

The first ad in data.results, trimmed to the fields the script uses. It is a sponsored ad; ordinary listings have "ad_type": "ORGANIC":

json
{
  "job_id": "95364041",
  "title": "Reporting Analyst",
  "advertiser": { "id": "25502044", "name": "Perigon Group Pty Limited" },
  "locations": [{ "country_code": "AU", "label": "Sydney NSW" }],
  "classifications": [{ "id": "6143", "name": "Analysis & Reporting", "parent_id": "1200", "parent_name": "Accounting" }],
  "ad_type": "SPONSORED",
  "listed_at": "2026-10-19T23:41:17.000Z",
  "salary_label": "$140k-$170k+Super",
  "work_types": ["Contract/Temp"]
}

Each result also has a teaser, bullet points, a logo URL and a "2d ago" style date. salary_label is the advertiser's own text, not a number. The job route adds description_html, expires_at, a structured_location with suburb, state and postcode, and contact_matches, the emails and phone numbers found in the ad text.

Save a Seek search to CSV

The script runs a search over several pages, skips ads it has already written, and saves one row per ad with the advertiser, salary text, work type, category, listing date and link. Save it as seek_jobs.py and install requests (pip install requests).

python
import csv
import os
import re
import sys
import time

import requests

URL = "https://api.toolzerhub.com/v1/seek/search"
HEADERS = {"x-api-key": os.environ.get("TOOLZERHUB_API_KEY", "")}
FIELDS = ["job_id", "title", "advertiser", "location", "salary", "work_type", "classification",
          "listed_at", "ad_type", "url"]


def search(query, location, page, tries=3):
    # 429, 5xx and dropped connections are retried; failed calls cost no credits.
    params = {"query": query, "page": page, **({"location": location} if location else {})}
    for attempt in range(tries):
        last = attempt == tries - 1
        try:
            r = requests.get(URL, headers=HEADERS, params=params, timeout=120)
        except requests.RequestException:
            if last:
                raise
            time.sleep(10 * (attempt + 1))
            continue
        if r.status_code in (429, 500, 502, 503, 504) and not last:
            time.sleep(10 * (attempt + 1))
            continue
        if r.status_code == 402:
            sys.exit("out of credits: the CSV keeps the pages written so far")
        r.raise_for_status()
        return r.json()["data"]


def main(query, location="", pages="5", out=None):
    name = re.sub(r"\W+", "_", query)
    out = out or f"seek_{name}.csv"
    seen, repeats, fetched = set(), 0, 0
    with open(out, "w", newline="", encoding="utf-8-sig") as f:  # -sig: Excel reads UTF-8
        writer = csv.DictWriter(f, fieldnames=FIELDS)
        writer.writeheader()
        for page in range(1, int(pages) + 1):
            data = search(query, location, page)
            if not data:  # an empty page; stop here
                break
            fetched += 1
            if page == 1:
                print(f"{data['total_count']} jobs on Seek for {query!r} {location}".rstrip())
            for job in data["results"]:
                if job["job_id"] in seen:  # a job can repeat, even on the same page
                    repeats += 1
                    continue
                seen.add(job["job_id"])
                writer.writerow({
                    "job_id": job["job_id"], "title": job["title"],
                    "advertiser": (job.get("advertiser") or {}).get("name"),
                    "location": ", ".join(x.get("label", "") for x in job.get("locations") or []),
                    "salary": job.get("salary_label") or "",
                    "work_type": ", ".join(job.get("work_types") or []),
                    "classification": ", ".join(
                        c["name"] for c in job.get("classifications") or []),
                    "listed_at": (job.get("listed_at") or "")[:10], "ad_type": job.get("ad_type"),
                    "url": f"https://au.seek.com/job/{job['job_id']}",
                })
            if not data.get("has_more"):
                break
    print(f"{len(seen)} jobs from {fetched} page(s) written to {out}, {repeats} repeats skipped")


if __name__ == "__main__":
    if len(sys.argv) < 2:
        sys.exit('usage: python seek_jobs.py "data analyst" ["Sydney NSW"] [pages] [file.csv]')
    if not HEADERS["x-api-key"]:
        sys.exit("set TOOLZERHUB_API_KEY first")
    main(*sys.argv[1:])

python seek_jobs.py "data analyst" "Sydney NSW" 5 and python seek_jobs.py "electrician" "Brisbane QLD" 5 took 10 calls and 36 and 35 seconds, and saved 150 data analyst ads and 140 electrician ads.

Search pages repeat ads

Five pages of 32 are 160 rows, and the script skips the repeats, so the distinct count varies from run to run. Key your data on job_id:

Run Analysts Electricians
Saved pages 138 129
Script run 1 141 148
Script run 2 134 126
Script run 3 150 140

Distinct ads in 5 pages, same queries. The three script runs together found 181 distinct analyst ads and 183 electrician ads, more than any run alone, so merge runs on job_id.

How many ads show a salary

In the last run, 84 of the 140 electrician ads and 41 of the 150 data analyst ads gave a salary figure. 62 of the electrician figures were hourly rates and 10 of the analyst figures were day rates. The other labels gave no number. To compare pay, parse ranges, hourly and daily rates and "+ super" yourself.

What it costs

Each page is one call and 1 credit, so a 5-page search is 5 credits, and 1,000 distinct ads take about 35 credits. You get 100 free credits a month; $19 buys 10,000 credits that never expire, and plans for higher volume cover more searches. Set your key and run the script on your first search.

For US jobs, see Indeed job listings as JSON and Wellfound startup jobs; for LinkedIn, how to scrape LinkedIn and LinkedIn API pricing.

We are not affiliated with SEEK.

Get this data from your own code

The Seek API returns it as JSON with one x-api-key header. 100 free credits monthly.