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.
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":
{
"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).
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.