# Domain.com.au Scraper: Sold Prices, Schools & Suburb Trends (`kotaro001/domain-com-au-scraper`) Actor

Scrape domain.com.au sale, rent and sold property listings by suburb. Rows carry price, beds, baths, parking, land size, sale method and inspection times; detail pages add school catchments, suburb medians, clearance rate, owner-occupier mix and agent phones.

- **URL**: https://apify.com/kotaro001/domain-com-au-scraper.md
- **Developed by:** [Tuyen Tran](https://apify.com/kotaro001) (community)
- **Categories:** Real estate
- **Stats:** 2 total users, 1 monthly users, 0.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $1.60 / 1,000 listings

This Actor is paid per event. You are not charged for the Apify platform usage, but only a fixed price for specific events.
Since this Actor supports Apify Store discounts, the price gets lower the higher subscription plan you have.

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

### Live Sample Dataset & Verified Run Benchmark

Two runs on **3 November 2026** on Apify Cloud, build `0.1.14`. Both datasets are public: open the JSON or CSV link in a browser, no login and no run needed.

**Multi-state list run** — `IbR4vKezpggCQ6aqT` · **30 rows** · 68 columns · 65.4 s · charged `apify-actor-start` 1, `listing-scraped` 30.
[JSON](https://actors.zapstackguides.com/samples/domain-list.json) · [CSV](https://actors.zapstackguides.com/samples/domain-list.csv)

**Detail run** — `guWDXXVQesCUXp1ij` · **3 rows** · 68 columns · 45.5 s · charged `apify-actor-start` 1, `listing-scraped` 3, `detail-scraped` 3.
[JSON](https://actors.zapstackguides.com/samples/domain-detail.json) · [CSV](https://actors.zapstackguides.com/samples/domain-detail.csv)

Coverage: Sydney NSW 8 rows, Melbourne VIC 8, Brisbane QLD 8, Perth WA 6. All fetched live.

| # | Address | Price | Beds | Baths | Sale method | Source |
|---|---|---|---|---|---|---|
| 1 | 46/155-161 Macquarie Street, Sydney NSW 2000 | Guide $5,500,000 | 3 | 2 | Private treaty | [Open](https://www.domain.com.au/46-155-161-macquarie-street-sydney-nsw-2000-2021293805) |
| 2 | 1804/281 Elizabeth Street, Sydney NSW 2000 | Contact Agent | 2 | 2 | Private treaty | [Open](https://www.domain.com.au/1804-281-elizabeth-street-sydney-nsw-2000-2021293362) |
| 3 | 301/45 Shelley Street, Sydney NSW 2000 | Buyer guide: $850,000 to $900,000 | 1 | 1 | Private treaty | [Open](https://www.domain.com.au/301-45-shelley-street-sydney-nsw-2000-2021292099) |

Compare the three rows with the source pages. That is the whole test, under five minutes.

### What does the Domain.com.au Scraper do?

Scrape **domain.com.au** sale, rent and sold listings by suburb without opening each page by hand. One row per property carries the address, price, bedrooms, bathrooms, parking, land size, sale method, inspection times and agent phone.

Domain.com.au is the second-largest property portal in Australia and the parent company of allhomes.com.au. It publishes three things the smaller portals do not put in one place: **suburb performance numbers**, **neighbourhood demographics** and **school catchment zones**. This actor puts all three on the row, next to the listing itself.

The actor reads public result and detail pages. It does not log in, does not bypass a paywall and does not invent a value. "Contact Agent" and "Price Withheld" stay as text, with an empty number.

### Why scrape domain.com.au with this Actor?

| # | Feature | What it means for you | Measured on live runs |
|---|---|---|---|
| 1 | Three channels | Sale, rent and sold results share one input shape and one output shape | 30 rows across NSW, VIC, QLD and WA in one run |
| 2 | Suburb insight block | Median price, average days on market and auction clearance rate arrive on the row | 3/3 detail rows carried a median and a clearance rate |
| 3 | Neighbourhood demographics | Owner-occupier share and renter share as percentages | 3/3 detail rows, one decimal |
| 4 | School catchments | Every government and independent school with distance and catchment flag | 12 to 13 schools per Sydney detail page |
| 5 | Inspection schedule | Open-for-inspection windows as ISO timestamps | 100% of the 10 buy rows in the sample |
| 6 | Sale method | "Sold at auction", "Sold by private treaty", "Sold prior to auction" | 10/10 sold rows |
| 7 | Change tracking | Price drops, previous price and percent change across runs | Daily mode with a stable state key |
| 8 | Flat output | CSV, JSON, Sheets, API, webhooks, n8n, Make, Zapier, MCP | 68 columns, 0 duplicate names |

### What data can you extract from domain.com.au?

| Group | Fields | List mode | Needs detail |
|---|---|---|---|
| Identity | listing id, source portal, channel, status, listing URL | Yes | No |
| Address | full address, street, suburb, state, postcode, latitude, longitude | Yes | No |
| Price | price display, price numeric, price history, previous price, change percent | Yes | No |
| Sale | sold date, auction date, sale method | Sold rows | Sold detail |
| Property | property type, bedrooms, bathrooms, car spaces, land size | Yes | No |
| Agent | agent name, agent phone, agency name, agency phone | No | **Yes** |
| Schools | school list, school count, closest school name | No | **Yes** |
| Suburb | median price, average days on market, clearance rate, owner-occupier share, renter share | No | **Yes** |
| Content | description, features, images, sale and price history, inspection times | No | **Yes** |
| Lifecycle | days on market, new listing flag, price changed flag, direction, change count | Yes | No |

Six fields belong to this portal:

1. **Neighbourhood demographics.** `owner_occupier_pct` and `renter_pct` separate a family market from an investor market, both as percentages.
2. **Suburb performance.** `suburb_median_price`, `suburb_avg_days_on_market` and `suburb_clearance_rate_pct` come from the detail page, not a separate report.
3. **School catchment zones.** `schools_count`, `closest_school_name` and `schools` carry name, type, sector, year range, distance and catchment flag.
4. **Open for inspection.** `inspection_times` is a list of ISO windows, ready for a calendar tool.
5. **Sale method.** `sale_method` separates an auction result from a private treaty sale when you compare clearance rates.
6. **Exact listing date.** `posted_at` is the printed listing date, so `daysOnMarket` is real even on a first run with details on.

Price honesty rules are strict. A number is taken only when the card prints a dollar figure. "Auction Saturday 14 Nov", "Price Withheld", "Contact Agent", "EOI" and "Tender" never become a price. An auction time becomes `auction_date`, never `priceNumeric`. A sold row takes its price and method from the sold record, not from the current asking price.

### How to scrape domain.com.au listings and price history

1. Pick a channel: sale, rent or sold.
2. Enter suburb, state and postcode; Domain needs all three in the URL.
3. Set `maxItems`. One page carries 20 cards.
4. Turn on detail pages for agent phones, schools and suburb numbers. They are cached.
5. Schedule daily with a stable `stateKey` to track first and last seen dates.

For several suburbs in one run, pass the hidden `suburbs` array through the API. The row cap is split across entries, so the first suburb cannot eat the run.

Brisbane is the one slug that differs from the obvious spelling: Domain uses `brisbane-city-qld-4000`. The actor maps it for you; a hand-built URL with `brisbane-qld-4000` returns HTTP 404, which is why the mapping exists.

Price history is built across runs. The first run records the price, the second compares. A drop appends a `priceHistory` entry with old price, new price, date and percent change.

### How much does it cost to scrape domain.com.au?

| Event | Standard tier | Discounted tiers |
|---|---|---|
| Actor start, per run | $0.005 | $0.004 |
| One list row | $0.002 | $0.0016 |
| One detail row | $0.006 | $0.0048 |

A 1,000-row list run costs **$2.005** at the standard tier: $0.005 start plus $2.00 for rows. With details on every row it costs **$8.005**: $0.005, $2.00 listings, $6.00 details. Discounted tiers: about **$1.60** and **$6.40**.

The three-row detail test cost **$0.029**: $0.005 start, three listings, three details. Exact charge, not an estimate.

A detail row is billed whether it was opened live or served from cache, because the data is delivered either way. A list-only run never pays for a detail it does not ship.

The Free plan returns up to **50 rows per run**. The start event is charged once per run.

**100% of the platform compute and proxy cost is already inside the event price.** There is no separate infrastructure line and no surprise at month end. You pay for events; the container and unblocker proxy come out of that price.

**Pre-calculated 1,000-listing total** (one actor start plus the rows):

1. List only, `includeDetails: false`: **$2.005** on Standard (Free/Bronze), **$1.604** on Silver+.
2. Full details, `includeDetails: true`: **$8.005** on Standard (Free/Bronze), **$6.404** on Silver+.

### Input parameters and configuration examples

| Parameter | Type | Default | What it does |
|---|---|---|---|
| `channel` | select | buy | Sale, rent or sold results |
| `suburb` | text | Sydney | Suburb name |
| `state` | select | NSW | State code |
| `postcode` | text | 2000 | Four digit postcode |
| `propertyTypes` | multi-select | empty | Keep only the printed types |
| `minPrice` | number | empty | Keep rows at or above this price |
| `maxPrice` | number | empty | Keep rows at or below this price |
| `minBeds` | number | empty | Minimum bedrooms |
| `minDaysOnMarket` | number | 0 | Keep older listings; falls back with a warning instead of returning nothing |
| `maxItems` | number | 20 | Row cap for the run |
| `maxPages` | number | 1 | Result pages to read, 20 cards each |
| `includeDetails` | checkbox | false | Open detail pages for agent, schools and suburb numbers |
| `maxDetails` | number | 50 | Cap on detail pages |
| `onlyEmitChanges` | checkbox | false | Emit only new, repriced or removed rows |
| `startUrls` | text | empty | Paste search URLs to skip the suburb form |
| `stateKey` | text | default | Separate history for each customer |

A typical daily job:

```json
{
  "channel": "buy",
  "suburb": "Brisbane",
  "state": "QLD",
  "postcode": "4000",
  "maxItems": 40,
  "includeDetails": true,
  "onlyEmitChanges": true,
  "stateKey": "brisbane-daily"
}
```

A light test with no detail pages:

```json
{
  "channel": "rent",
  "suburb": "Perth",
  "state": "WA",
  "postcode": "6000",
  "maxItems": 10
}
```

### Output example

```json
{
  "listingId": "2021293805",
  "channel": "buy",
  "propertyType": "apartment / unit / flat",
  "fullAddress": "46/155-161 Macquarie Street, Sydney NSW 2000",
  "suburb": "Sydney",
  "state": "nsw",
  "postcode": "2000",
  "priceDisplay": "Guide $5,500,000",
  "priceNumeric": 5500000,
  "sale_method": "Private treaty",
  "auction_date": "",
  "inspection_times": ["2026-11-04T12:00:00 - 2026-11-04T12:30:00"],
  "bedrooms": 3,
  "bathrooms": 2,
  "carSpaces": 2,
  "landSizeSqm": null,
  "schools_count": 13,
  "closest_school_name": "St Mary's Cathedral College",
  "suburb_median_price": 3199000,
  "owner_occupier_pct": 30.7,
  "renter_pct": 69.3,
  "agent1_phone": "0414 990 990",
  "posted_at": "2026-11-02",
  "daysOnMarket": 1,
  "listingUrl": "https://www.domain.com.au/46-155-161-macquarie-street-sydney-nsw-2000-2021293805"
}
```

Every key there is a dataset column. The schema declares 68 columns and a live row carries 68, so the export adds nothing and hides nothing.

### 🇦🇺 Australian Real Estate Data Suite (6 Portals)

| # | Portal | Actor | Best for |
|---|---|---|---|
| 1 | realestate.com.au | [Realestate.com.au Scraper](https://apify.com/kotaro001/realestate-com-au-scraper) | Largest listing base and agent phones |
| 2 | property.com.au | [Property.com.au Scraper](https://apify.com/kotaro001/property-com-au-scraper) | Sold history, estimates and rent estimates |
| 3 | realcommercial.com.au | [Realcommercial.com.au Scraper](https://apify.com/kotaro001/realcommercial-com-au-scraper) | Commercial, industrial and retail |
| 4 | onthehouse.com.au | [OnTheHouse.com.au Scraper](https://apify.com/kotaro001/onthehouse-com-au-scraper) | Valuations and multi-decade history |
| 5 | allhomes.com.au | [Allhomes.com.au Scraper](https://apify.com/kotaro001/allhomes-com-au-scraper) | Canberra, EER ratings and land value |
| 6 | domain.com.au | This actor | Suburb performance, demographics and school catchments |

All six actors share the same 25 core column names, so a workbook can union their output without renaming anything.

### Frequently Asked Questions (FAQ) & Legal Disclaimer

**Does this actor need a Domain account?** No. It reads public result and detail pages.

**Why is the price empty on some rows?** Domain prints "Contact Agent", "Price Withheld" or an auction time. The actor keeps the printed text and leaves the number empty instead of guessing.

**Why did a suburb return nothing?** A wrong slug or postcode returns HTTP 404. Brisbane needs `brisbane-city`; the actor maps it.

**What happens with minDaysOnMarket on a new suburb?** If no listing is old enough, the actor logs a warning and returns the full list sorted by days on market, so a first run is never empty.

**Is the detail cache shared between customers?** Yes. It is keyed by listing, so a second customer needs no new fetch. Each keeps a separate history through `stateKey`.

**Legal note.** Domain.com.au is operated by Domain Group. Its terms and robots rules restrict automated collection. This actor reads public pages and returns printed facts. You are responsible for how you use the data. Do not republish an entire portal.

# Changelog

This Actor's version history is a separate document: https://apify.com/kotaro001/domain-com-au-scraper/changelog.md

# Actor input Schema

## `channel` (type: `string`):

buy = for sale, rent = rental listings, sold = sold results with price and sale method.

## `suburb` (type: `string`):

Suburb name, e.g. Surry Hills. Use suburbs for several areas in one run.

## `state` (type: `string`):

Two or three letter state code: NSW, VIC, QLD, WA, SA, TAS, ACT, NT.

## `postcode` (type: `string`):

Four digit postcode. The URL needs suburb, state and postcode together.

## `suburbs` (type: `array`):

API only: array of 'Suburb, STATE, POSTCODE' entries. One search per entry. Leave empty to use suburb, state, postcode.

## `propertyTypes` (type: `array`):

Keep only these printed types, e.g. House, Apartment / Unit / Flat, Townhouse.

## `minPrice` (type: `integer`):

Keep rows priced at or above this number. Rows with no printed price are kept.

## `maxPrice` (type: `integer`):

Keep rows priced at or below this number.

## `minBeds` (type: `integer`):

Keep rows with at least this many bedrooms.

## `minDaysOnMarket` (type: `integer`):

Keep only listings listed at least this many days ago. When a fresh suburb has nothing that old, the actor logs a warning and returns the full list sorted by daysOnMarket instead of an empty dataset.

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

Hard cap on rows shipped in one run. Free plan stops at 50.

## `maxPages` (type: `integer`):

Result pages to read, 20 cards each.

## `includeDetails` (type: `boolean`):

Adds school catchments, suburb medians, description, photos and agent phone. Billed per detail row, including rows served from the cache.

## `maxDetails` (type: `integer`):

Cap on detail pages opened in one run.

## `onlyEmitChanges` (type: `boolean`):

Emit only new, repriced or removed rows. Changes mode only emits changed rows; in daily mode every emitted row is billed.

## `startUrls` (type: `string`):

Paste domain.com.au search URLs, separated by a comma or a new line, to bypass the suburb form.

## `stateKey` (type: `string`):

Separate lifecycle state for this input. Two customers can share the central detail cache and still keep their own history.

## `max_sources` (type: `integer`):

Run only the first N sources (0 = all).

## Actor input object example

```json
{
  "channel": "buy",
  "suburb": "Sydney",
  "state": "NSW",
  "postcode": "2000",
  "suburbs": [],
  "minDaysOnMarket": 0,
  "maxItems": 20,
  "maxPages": 1,
  "includeDetails": false,
  "maxDetails": 50,
  "onlyEmitChanges": false,
  "stateKey": "default",
  "max_sources": 0
}
```

# Actor output Schema

## `listings` (type: `string`):

No description

## `stats` (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 = {};

// Run the Actor and wait for it to finish
const run = await client.actor("kotaro001/domain-com-au-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 = {}

# Run the Actor and wait for it to finish
run = client.actor("kotaro001/domain-com-au-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 '{}' |
apify call kotaro001/domain-com-au-scraper --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,kotaro001/domain-com-au-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/lzdpmrQRkUqR7Ai64/builds/lPCfUt0sVUYdzvTdM/openapi.json
