# Property24 Scraper - Properties, Prices, Agents & Listings (`abotapi/property24-scraper`) Actor

From $1/1K. Extract structured property listings from Property24, South Africa’s largest property portal. Search by location or use Property24 URLs. Get 50+ fields including price, beds, baths, sizes, GPS coordinates, photos, property details, and agent/agency information.

- **URL**: https://apify.com/abotapi/property24-scraper.md
- **Developed by:** [Abot API](https://apify.com/abotapi) (community)
- **Categories:** Real estate, Developer tools, Automation
- **Features:** Limited permissions
- **Stats:** 12 total users, 4 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $1.00 / 1,000 results

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

## Property24 Scraper: South Africa Property Listings, Agents & GPS

Property24 Scraper turns Property24, South Africa's largest property portal, into structured data. Get price, bedrooms, bathrooms, floor and erf size, GPS coordinates, the full grouped property overview, every photo, agent and agency details, and 50+ fields per listing. Search by suburb, city or province name, or paste Property24 search and single-listing URLs directly, then export to JSON, CSV or Excel, or pull results through the API.

### Why This Scraper?

- **More fields per listing.** GPS latitude and longitude, numeric floor and erf size, postal code, the complete grouped property overview (rooms, external features, building, security), nearby suburbs, listing date and full multi-size photo URLs, alongside the standard price, beds, baths and description.
- **Two ways to find listings.** Search by any suburb, city or province name, resolved automatically, or paste Property24 URLs directly: search pages paginate forward, single-listing URLs are fetched directly.
- **Filters that actually narrow the data.** Price, bedroom and bathroom filters are sent in the search request and re-checked on the returned records; property type is matched on the results; sort and amenity filters are applied through the search request. Results match what you asked for even if the site ignores a parameter.
- **Cost under your control.** Turn detail-page fetching off for a cheaper search-result-only run, or on for full listing depth, and cap any run with Max Listings.
- **Only new listings on repeat runs.** Point a run at a previous run or dataset ID and already-collected listings are skipped, so you only pay for what's new.
- **Built for an unreliable site.** The actor automatically tries a different connection when a request is refused, so runs keep making progress instead of stopping at the first blocked page.
- **Clean, flat output.** One record per listing, ready for spreadsheets, BI tools, ETL pipelines and CRMs, with optional delivery into the apps you already use.

### Use Cases

- **Property portals and listing aggregators:** pull fresh Property24 listings into your own search index or comparison site.
- **Real estate agencies and agents:** monitor competing listings and price positioning by suburb or city.
- **Investors and analysts:** track price per square metre, erf size and inventory across South African suburbs over time.
- **PropTech and CRM integrations:** feed listings and agent and agency details into your CRM or lead pipeline, including through MCP connectors.
- **Market researchers and journalists:** build historical datasets of pricing and listing volume for reporting.

### Data You Get

> Sample shape: values are illustrative placeholders, not from a live listing.

| Field | Example |
| --- | --- |
| `listingNumber` | `"100000001"` |
| `url` | `https://www.property24.com/for-sale/sample-suburb/sample-city/western-cape/0000/100000001` |
| `listingType` | `"For Sale"` |
| `title` | `"3 Bedroom House for sale in Sample Suburb"` |
| `price` / `displayPrice` | `2600000` / `"R 2 600 000"` |
| `currency` | `"ZAR"` |
| `priceOnApplication` | `false` |
| `bedrooms` / `bathrooms` | `3` / `2` |
| `garages` / `parkingSpaces` | `2` / `4` |
| `floorSize` / `erfSize` | `248.0` / `554.0` |
| `propertyType` | `"House"` |
| `suburbName` / `cityName` / `provinceName` | `"Sample Suburb"` / `"Sample City"` / `"Western Cape"` |
| `latitude` / `longitude` | `-33.0000` / `18.0000` |
| `description` | `"Full listing description text."` |
| `agencyName` / `agentName` | `"Sample Agency"` / `"Agent Name"` |
| `contacts` | `[{ "id": 0, "name": "Agent Name", "contactType": "Agent", "jobTitle": "Property Practitioner", "hasContactNumber": true }]` |
| `keyFeatures` | `["Bedrooms 3", "Bathrooms 2", "Garden"]` |
| `propertyOverview` | `{ "Type of Property": "House", "Erf Size": "554 m2" }` |
| `photos` / `photoCount` | `[{ "imageIdentifier": "000000000", "imageUrl": "..." }]` / `1` (equal to the length of `photos`) |
| `floorPlans` | `[{ "imageIdentifier": "000000000", "imageUrl": "..." }]`, floor-plan images when the listing has them (detail pages only) |
| `nearbySuburbs` | `["Sample Suburb A", "Sample Suburb B"]` |
| `auction` / `onShow` / `repossessed` | `false` / `false` / `false` |
| `datePosted` | `"2026-01-01"` |
| `scrapedAt` | `"2026-01-01T00:00:00.000000+00:00"` |

GPS coordinates, the full description, agent and agency details, the property overview, photos and nearby suburbs only populate when **Fetch Detail Pages** is on. A search-result-only run still returns listing number, url, type, title, price, bedrooms, bathrooms, garages, floor and erf size, suburb name and a short excerpt.

### How to Use

1. Pick a **mode**: `search` (find listings by place name) or `url` (paste Property24 search or listing links).
2. In search mode, choose a listing type (buy, rent, or commercial) and one or more locations, then set any price, bedroom or amenity filters.
3. Turn **Fetch Detail Pages** on for the full record, or off for a faster, cheaper search-result-only run.
4. Set **Max Listings** to control run size and cost, then click **Start**. Download the dataset as JSON, CSV or Excel, or read it through the API.

**Search by location (basic):**

```json
{
  "mode": "search",
  "listingType": "for-sale",
  "locations": ["Cape Town"],
  "maxListings": 50
}
```

**Search with filters:**

```json
{
  "mode": "search",
  "listingType": "for-sale",
  "locations": ["Sandton", "Umhlanga"],
  "propertyType": "house",
  "minPrice": 1000000,
  "maxPrice": 3000000,
  "minBedrooms": 3,
  "sortBy": "price-asc",
  "maxListings": 200,
  "fetchDetails": true
}
```

**Rentals:**

```json
{
  "mode": "search",
  "listingType": "to-rent",
  "locations": ["Cape Town"],
  "maxListings": 100
}
```

#### URL mode: supported URL shapes

Behaviour note: with **mode** set to `url` and the **urls** field left untouched, the actor now scrapes the built-in examples below (one per shape) instead of returning nothing. Clear the field and paste your own URLs to avoid this.

| URL shape | Example path | Handling |
|---|---|---|
| City search | `/for-sale/cape-town/western-cape/432` | Scraped |
| Paginated search | `.../432/p2` | Scraped, walks forward from the given page |
| Filtered / sorted search | `.../432?sp=pf%3D1000000%26pt%3D3000000%26so%3DPriceLow` | Scraped, pasted filters and sort are kept |
| Rent search | `/to-rent/sandton/gauteng/109` | Scraped |
| Commercial for sale / to rent | `/commercial-property-for-sale/...`, `/commercial-property-to-rent/...` | Scraped |
| Suburb search | `/for-sale/gardens/cape-town/western-cape/9145` | Scraped |
| Province search | `/for-sale/western-cape/9` | Scraped |
| Listing detail | `/for-sale/<suburb>/<city>/<province>/<id>/<listing>` | Scraped in full |
| Mobile host | `m.property24.com/...` | Converted to www.property24.com, then scraped |
| New developments (search and detail) | `/new-developments-for-sale/...` | Skipped with a warning in the log |
| Estate agent profile | `/estate-agents/...` | Skipped with a warning in the log |
| Estate agency directory | `/estate-agencies/...` | Skipped with a warning in the log |

**Direct URLs (search pages and single listings, mixed):**

```json
{
  "mode": "url",
  "urls": [
    "https://www.property24.com/for-sale/cape-town/western-cape/432",
    "https://www.property24.com/for-sale/belhar/cape-town/western-cape/8132/117073882"
  ],
  "maxPages": 3
}
```

#### Run it from your code

Python:

```python
from apify_client import ApifyClient

client = ApifyClient("<YOUR_APIFY_TOKEN>")
run = client.actor("abotapi/property24-scraper").call(run_input={"mode": "search", "locations": ["Cape Town"], "maxListings": 50})
for listing in client.dataset(run["defaultDatasetId"]).iterate_items():
    print(listing["title"], listing["displayPrice"])
```

JavaScript:

```javascript
import { ApifyClient } from 'apify-client';

const client = new ApifyClient({ token: '<YOUR_APIFY_TOKEN>' });
const run = await client.actor('abotapi/property24-scraper').call({ mode: 'search', locations: ['Cape Town'], maxListings: 50 });
const { items } = await client.dataset(run.defaultDatasetId).listItems();
```

Or connect it to Make, Zapier, n8n, Google Sheets or webhooks from the **Integrations** tab.

#### Incremental mode

Set `incrementalMode` to track one search over time. The baseline is stored per state key (derived from your search settings unless you set `stateKey`). Each returned row gains `changeType` (NEW, UPDATED, REAPPEARED, EXPIRED or UNCHANGED), `changedFields`, `firstSeenAt` and `lastSeenAt`; the **Changes** view shows them. Unchanged listings are skipped unless `emitUnchanged` is on. EXPIRED rows appear only with `emitExpired` and only after a complete scan: a run that hits Max Listings, a page limit, a failed page or a resume never marks anything EXPIRED. Billing: the result and detail-enrichment events are charged only for delivered NEW, UPDATED and REAPPEARED rows; UNCHANGED and EXPIRED rows are never charged, even when returned. The scrape time is always ignored when comparing; add other fields to `ignoreFieldsForChanges`.

#### Run size and resume

- **Max Listings** is the run cap across every search or URL, divided evenly across them. Leave **Max Pages Per Search** empty to let Max Listings decide how far each search goes, or set it to cap result pages per search directly.
- **Resume from a previous run (delta)** (`resumeFromRunId`): paste a previous run ID or dataset ID and this run skips every listing already collected there, returning only new listings. Combine both datasets for the full set. For scheduled monitoring that also reports price changes and removed listings, use **Incremental mode** above.
- If a run is interrupted by a platform migration, an internal checkpoint restores the same run's progress automatically so nothing already collected is re-scraped or re-charged.

### Send results into your apps (MCP connectors)

Optionally pipe the scraped results into the apps you already use, via Model Context Protocol (MCP) connectors. This is an extra delivery step **after** the scrape: the Apify dataset is never changed.

**What gets written to the connector:** a condensed, human-readable **summary** of each record, not the full JSON. Each item becomes one entry with a **title** and its key fields flattened to plain text. The **complete record always stays in the Apify dataset**.

1. Authorize a connector once under **Apify → Settings → Integrations** (Notion, Linear, Airtable, or Apify).
2. Select it in the **"Pipe results into your apps"** input field. (If the picker is empty, you haven't authorized a connector yet.)
3. For **Notion**, also set `notionParentPageUrl` to the page where items should be created.

The connection is mediated by Apify's MCP proxy, so this actor never sees your third-party credentials. Leave the field empty to skip.

### Input Parameters

| Parameter | Type | Default | Description |
| --- | --- | --- | --- |
| `mode` | string | `search` | `search` builds URLs from the fields below; `url` scrapes the URLs you paste. |
| `listingType` | string | `for-sale` | `for-sale` (Buy), `to-rent` (Rent), `commercial-for-sale`, or `commercial-to-rent` (search mode). The commercial options search Property24's separate commercial vertical. |
| `locations` | array | none (prefill: `["Cape Town"]`) | Suburb, city or province names (search mode). Resolved automatically. |
| `propertyType` | string | `any` | Narrow to house, apartment, townhouse, vacant land, farm, commercial, or industrial (applied to results). |
| `minPrice` | integer | none | Minimum price in Rand (search mode). |
| `maxPrice` | integer | none | Maximum price in Rand (search mode). |
| `minBedrooms` | integer | none | Minimum bedrooms (search mode). Only the minimum is sent in the search request; both min and max are applied to results. |
| `maxBedrooms` | integer | none | Maximum bedrooms (search mode). Applied to results; not sent in the search request. |
| `minBathrooms` | integer | none | Minimum bathrooms (search mode). |
| `parkingSpaces` | integer | none | Minimum parking spaces or garages (search mode). |
| `minErfSize` | integer | none | Minimum erf (land) size in m² (search mode). |
| `minFloorSize` | integer | none | Minimum floor size in m² (search mode). |
| `hasPool` | boolean | `false` | Only listings with a swimming pool. |
| `hasGarden` | boolean | `false` | Only listings with a garden. |
| `hasFlatlet` | boolean | `false` | Only listings with a flatlet / granny flat. |
| `petFriendly` | boolean | `false` | Only pet-friendly listings. |
| `securityEstate` | boolean | `false` | Only listings inside a security estate. |
| `repossessed` | boolean | `false` | Only bank-repossessed properties. |
| `retirement` | boolean | `false` | Only retirement listings. |
| `sortBy` | string | `default` | `newest`, `price-asc`, `price-desc`, or `size`. |
| `onShowOnly` | boolean | `false` | Only listings with a scheduled show day. |
| `auctionsOnly` | boolean | `false` | Only properties going to auction. |
| `urls` | array | one example per supported shape (14 URLs) | Property24 URLs to scrape (url mode). Filter fields are ignored in this mode. |
| `fetchDetails` | boolean | `true` | Open each listing for GPS, full description, photos, agent, agency, and the property overview. |
| `startUrls` | array | none | Alias of `urls` (accepts the standard request-list format); use either. |
| `maxItems` | integer | none | Alias of `maxListings`; use either. |
| `maxPages` | integer | none | Optional bound on result pages walked per search (about 45 listings per page). Leave empty to walk every result page; the run size is controlled by `maxListings`, which this field defers to. |
| `maxListings` | integer | `20` | THE run cap: max listings across all searches. `0` for unlimited. |
| `incrementalMode` | boolean | false | Remember listings between runs and label each row NEW, UPDATED, REAPPEARED or EXPIRED. |
| `stateKey` | string | derived from the search | Name of the saved baseline. |
| `emitUnchanged` | boolean | false | Also return UNCHANGED listings (never charged). |
| `emitExpired` | boolean | false | Return EXPIRED rows for listings gone from a complete scan (never charged). |
| `ignoreFieldsForChanges` | array | none | Top-level fields that must not count as a change. |
| `resumeFromRunId` | string | none | ID of a previous run of this actor (or a dataset ID). Listings already in that dataset are skipped, so this run returns only new listings (a delta). |
| `proxy` | object | none (prefill: Apify Proxy with the Residential group) | Connection settings. |
| `mcpConnectors` | array | none | Optional: send a summary of each record to apps you authorized under Integrations. |
| `notionParentPageUrl` | string | none | Notion connector only: page under which records are created. |
| `maxNotifyListings` | integer | `50` | Cap on items written to each connector per run. Does not affect the dataset. |

The dataset has two views in the Console: **Overview** (core columns) and **Detail** (GPS, agent, agency and overview fields).

### Output Example

> Sample shape: values are illustrative placeholders, not from a live listing.

```json
{
  "listingNumber": "100000001",
  "url": "https://www.property24.com/for-sale/sample-suburb/sample-city/western-cape/0000/100000001",
  "listingType": "For Sale",
  "title": "3 Bedroom House for sale in Sample Suburb",
  "price": 2600000,
  "displayPrice": "R 2 600 000",
  "currency": "ZAR",
  "priceOnApplication": false,
  "bedrooms": 3,
  "bathrooms": 2,
  "garages": 2,
  "floorSize": 248,
  "erfSize": 554,
  "propertyType": "House",
  "suburbName": "Sample Suburb",
  "cityName": "Sample City",
  "provinceName": "Western Cape",
  "provinceId": "9",
  "latitude": -33.0000,
  "longitude": 18.0000,
  "descriptionHeader": "Sample headline for the listing.",
  "description": "Full listing description text appears here when Fetch Detail Pages is on.",
  "agencyName": "Sample Agency",
  "agentName": "Agent Name",
  "contacts": [{ "id": 0, "name": "Agent Name", "contactType": "Agent", "jobTitle": "Property Practitioner", "hasContactNumber": true }],
  "keyFeatures": ["Bedrooms 3", "Bathrooms 2", "Garden"],
  "propertyOverview": { "Type of Property": "House", "Erf Size": "554 m2" },
  "photos": [{ "imageIdentifier": "000000000", "imageUrl": "https://images.prop24.com/000000000/Ensure960x540" }],
  "photoCount": 1,
  "datePosted": "2026-01-01",
  "scrapedAt": "2026-01-01T00:00:00.000000+00:00"
}
```

### Plan Requirement

Residential proxy is recommended. In our measurement through Apify proxies, the Residential group (South Africa) loaded every search and listing page we tried (8 of 8), while the Apify datacenter group loaded only 2 of 5 search pages and 1 of 5 listing pages; the rest came back as a short rejection stub from Property24. The input form comes pre-filled with the Residential option, which needs an Apify plan that includes Residential proxy. If you choose a datacenter group, the actor uses it as selected and retries on a fresh exit when a request is rejected, so expect gaps. API calls that leave the proxy field out start on the default connection and the actor checks whether Residential is available to your account.

If Residential is not available (for example on an Apify free plan), the run logs this warning and continues on a backup connection:

> The selected connection is unavailable to this run. Apify free plans do not include residential proxies. Continuing on a backup connection; some pages may still be unavailable.

If no connection can be opened at all, the run stops with: "The proxy connection was refused for this account, so no page could be read. Your Apify plan does not include proxy access; a paid plan is required." If the backup connection also cannot load the pages, the run ends with "No results: residential proxies are unavailable on this run ... Upgrade at https://apify.com/pricing or supply your own proxy URLs, then try again." Any other run where no page loads ends with "Could not load any results page (the requests failed), so nothing was collected. This is a connection problem, not an empty search."

### FAQ

#### How much does it cost?

This actor uses pay-per-event pricing with three events: an actor-start event charged once per run, a result event charged for each listing delivered to the dataset, and a detail-enrichment event charged for each listing detail page fetched, only when Fetch Detail Pages is on. This applies to pasted listing URLs too: with Fetch Detail Pages off, a pasted listing URL is charged the result event only. In Incremental mode, UNCHANGED and EXPIRED rows are never charged, even when returned. The **Pricing** tab shows the current rates. Use **Max Listings** and **Fetch Detail Pages** to control the cost of any run.

#### Is it legal to scrape Property24?

This actor collects only publicly available listing data. You are responsible for how you use it: follow Property24's terms and the laws that apply to you, and get legal advice if you plan commercial redistribution. Listing prices and specifications are generally facts, but photos and agency branding may be subject to third-party rights.

#### Can I get only new listings on a schedule?

Yes. Switch on **Incremental mode** (`incrementalMode`) and schedule the actor with the same search: each row is labelled NEW, UPDATED (with `changedFields`), REAPPEARED or EXPIRED, and unchanged listings are skipped. `resumeFromRunId` remains available as a simple one-way delta against a past run.

#### Why are GPS coordinates, photos or the full description missing from my results?

Those fields only populate when **Fetch Detail Pages** is on. With it off, you get the faster, cheaper search-result fields only (price, beds, baths, floor and erf size, suburb, a short excerpt and a main image).

#### Why did my run return an empty dataset instead of failing?

An empty dataset usually means none of your **Locations** could be resolved to a Property24 area, or none of your **Property24 URLs** were valid or reachable; check the run log for "Could not resolve location" or similar warnings. The run fails outright only when `resumeFromRunId` points to a run or dataset it cannot read, since continuing would risk re-collecting and re-charging listings you already have.

#### Can I use it with AI agents or MCP?

Yes. Call it from any Apify integration or MCP client, and use the connector field to push results into Notion, Linear or Airtable.

### 🔗 Want more real estate data?

Pair this actor with these related scrapers from the same team:

<table>
<tr><td>🏠 <a href="https://apify.com/abotapi/homely-com-au-scraper"><b>Homely.com.au</b></a><br>Scrape Homely.com.au properties for sale, rent, and sold, plus agent finder, suburb...</td><td>🏠 <a href="https://apify.com/abotapi/fotocasa-es-scraper"><b>Fotocasa.es</b></a><br>Scrape Spain property listings from fotocasa.es. Get price, surface, rooms, baths...</td></tr>
<tr><td>🏠 <a href="https://apify.com/abotapi/trulia-scraper"><b>Trulia Scraper</b></a><br>Scrape Trulia.com property listings by location, filters, or URL. Extract prices, beds...</td><td>🏠 <a href="https://apify.com/abotapi/realcommercial-au-scraper"><b>Commercial Property AU Scraper</b></a><br>Scrape Australian commercial property listings with full detail data. Extract...</td></tr>
<tr><td>🏠 <a href="https://apify.com/abotapi/zonaprop-scraper"><b>Zonaprop Argentina</b></a><br>Scrape Zonaprop.com.ar property listings across Argentina. Extract 140+ fields including...</td><td>🏨 <a href="https://apify.com/abotapi/hipflat-scraper"><b>Hipflat Scraper Thailand Property &amp; Projects</b></a><br>Scrape hipflat.co.th property listings and new-project data across Thailand: price, price...</td></tr>
</table>

👉 [Browse all abotapi scrapers](https://apify.com/abotapi)

### 💬 Support

- 🐞 **Found a bug or a missing field?** Open a ticket on the [Issues tab](https://apify.com/abotapi/property24-scraper/issues/open). We usually reply within hours.
- 🛠️ **Need another site, extra fields or a private build?** Email <contact@abotapi.com>.
- ⭐ **Enjoying it?** A quick review on the actor page helps other users find it.

# Actor input Schema

## `mode` (type: `string`):

How to find listings. 'Search by Location' builds Property24 search URLs from the fields below. 'Direct URLs' scrapes the Property24 search or listing URLs you paste.

## `listingType` (type: `string`):

Which Property24 vertical to search (Search mode only). Buy and Rent are residential; the two Commercial options search Property24's separate commercial-property vertical.

## `locations` (type: `array`):

Place names to search (Search mode only). Type a suburb, city or province, e.g. 'Cape Town', 'Sandton', 'Western Cape', 'Umhlanga'. Each name is resolved to the matching Property24 area automatically. Multiple locations supported.

## `propertyType` (type: `string`):

Narrow to one property type (Search mode only). Applied to results.

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

Minimum price in Rand (Search mode only). Applied both in the search URL and to results.

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

Maximum price in Rand (Search mode only). Applied both in the search URL and to results.

## `minBedrooms` (type: `integer`):

Minimum number of bedrooms (Search mode only). Applied both in the search URL and to results.

## `maxBedrooms` (type: `integer`):

Maximum number of bedrooms (Search mode only). Applied to results.

## `sortBy` (type: `string`):

Result ordering (Search mode only).

## `onShowOnly` (type: `boolean`):

Only listings with a scheduled show day (Search mode only).

## `auctionsOnly` (type: `boolean`):

Only properties going to auction (Search mode only).

## `minBathrooms` (type: `integer`):

Minimum number of bathrooms (Search mode only). Applied in the search URL and to results.

## `parkingSpaces` (type: `integer`):

Minimum parking spaces or garages (Search mode only).

## `minErfSize` (type: `integer`):

Minimum erf (land) size in square metres (Search mode only).

## `minFloorSize` (type: `integer`):

Minimum floor size in square metres (Search mode only).

## `hasPool` (type: `boolean`):

Only listings with a swimming pool (Search mode only).

## `hasGarden` (type: `boolean`):

Only listings with a garden (Search mode only).

## `hasFlatlet` (type: `boolean`):

Only listings with a flatlet / granny flat (Search mode only).

## `petFriendly` (type: `boolean`):

Only pet-friendly listings (Search mode only).

## `securityEstate` (type: `boolean`):

Only listings inside a security estate (Search mode only).

## `repossessed` (type: `boolean`):

Only bank-repossessed properties (Search mode only).

## `retirement` (type: `boolean`):

Only retirement listings (Search mode only).

## `urls` (type: `array`):

Property24 search-result or single-listing URLs to scrape (URL mode only). Supported: for-sale, to-rent and commercial searches (city, suburb, province, /pN pages, ?sp= filters and sorting), single listings, and the m.property24.com host. New-development, estate-agent and estate-agency pages are skipped with a warning. Filter fields are ignored in this mode. Search pagination starts at the page in the URL and walks forward.

## `startUrls` (type: `array`):

Alias of Property24 URLs. When filled it replaces Property24 URLs and switches the run to URL mode.

## `fetchDetails` (type: `boolean`):

Open each listing to add GPS coordinates, full description, all photos, agent and agency details, the full property overview (erf and floor size, rooms, features, security), and points of interest. Turn off for a faster, cheaper run with search-result fields only.

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

Optional bound on result pages walked per search (about 45 listings per page). Leave empty to walk every result page; the run size is controlled by Max Listings, which this field defers to.

## `maxListings` (type: `integer`):

THE run cap: maximum listings to return across all searches. Default 20. Set 0 for unlimited. For large pulls, add many locations or price bands; a single Property24 search exposes a finite number of pages.

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

Alias of Max Listings. When filled it replaces Max Listings. 0 means unlimited.

## `proxy` (type: `object`):

Residential proxy is required for reliable results. Property24 rejects most Datacenter connections (especially on listing detail pages), so Datacenter is not recommended. The actor rotates Residential exits across countries (South Africa first) and uses a backup pool when needed. Residential proxy needs a paid Apify plan.

## `resumeFromRunId` (type: `string`):

Optional. ID of a previous run of this actor (or a dataset ID). Listings already in that dataset are skipped, so this run returns only NEW listings (a delta). Combine both runs' datasets for the full set. Max Listings then counts only the new listings.

## `incrementalMode` (type: `boolean`):

Remember the listings from earlier runs of the same search and label every row as NEW, UPDATED, REAPPEARED or EXPIRED. Listings that did not change are not returned and not charged. Leave off for a plain scrape. A run that stops at Max Listings, a page limit or a failed page never marks anything EXPIRED.

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

Name of the saved baseline. Leave empty to derive it from your search settings, so the same search always continues the same baseline. Set it to share or separate baselines on purpose.

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

Return listings that did not change, labelled UNCHANGED. They are never charged. Only applies when Incremental mode is on.

## `emitExpired` (type: `boolean`):

Return one EXPIRED row for each listing that was in the baseline but is gone from a complete scan. EXPIRED rows are never charged. Only applies when Incremental mode is on.

## `ignoreFieldsForChanges` (type: `array`):

Top-level field names that must not make a listing count as UPDATED (for example photoCount). The scrape time is always ignored. Only applies when Incremental mode is on.

## `mcpConnectors` (type: `array`):

Optionally send the scraped results into the apps you already use, via Model Context Protocol (MCP) connectors. Authorize a connector once under Apify → Settings → Integrations, then select it here. The connector receives a condensed, human-readable summary per item (title + key fields), not the full JSON — the complete record stays in the dataset. Leave empty to skip. Supported: Notion (https://mcp.notion.com/mcp), Linear (https://mcp.linear.app/sse), Airtable (https://mcp.airtable.com/mcp), Apify (https://mcp.apify.com).

## `notionParentPageUrl` (type: `string`):

URL (or id) of the Notion page under which item pages are created. Required to enable the Notion export; ignored by other connectors.

## `maxNotifyListings` (type: `integer`):

Cap on items written to each connector per run. Does not affect the dataset.

## Actor input object example

```json
{
  "mode": "search",
  "listingType": "for-sale",
  "locations": [
    "Cape Town"
  ],
  "propertyType": "any",
  "sortBy": "default",
  "onShowOnly": false,
  "auctionsOnly": false,
  "hasPool": false,
  "hasGarden": false,
  "hasFlatlet": false,
  "petFriendly": false,
  "securityEstate": false,
  "repossessed": false,
  "retirement": false,
  "urls": [
    "https://www.property24.com/for-sale/cape-town/western-cape/432",
    "https://www.property24.com/for-sale/cape-town/western-cape/432/p2",
    "https://www.property24.com/for-sale/cape-town/western-cape/432?sp=pf%3D1000000%26pt%3D3000000%26so%3DPriceLow",
    "https://www.property24.com/to-rent/sandton/gauteng/109",
    "https://www.property24.com/commercial-property-for-sale/cape-town/western-cape/432",
    "https://www.property24.com/commercial-property-to-rent/sandton/gauteng/109",
    "https://www.property24.com/for-sale/gardens/cape-town/western-cape/9145",
    "https://www.property24.com/for-sale/western-cape/9",
    "https://www.property24.com/for-sale/somerset-west-rural/somerset-west/western-cape/17468/117643707",
    "https://www.property24.com/new-developments-for-sale/cape-town/western-cape/432",
    "https://www.property24.com/new-developments-for-sale/woodstock/cape-town/western-cape/1-on-albert/10164/3161",
    "https://www.property24.com/estate-agents/matterhorn-properties/ronnie-du-toit/511147",
    "https://www.property24.com/estate-agencies/cape-town/western-cape/432",
    "https://m.property24.com/for-sale/cape-town/western-cape/432"
  ],
  "fetchDetails": true,
  "maxListings": 20,
  "proxy": {
    "useApifyProxy": true,
    "apifyProxyGroups": [
      "RESIDENTIAL"
    ]
  },
  "incrementalMode": false,
  "emitUnchanged": false,
  "emitExpired": false,
  "maxNotifyListings": 50
}
```

# Actor output Schema

## `overview` (type: `string`):

No description

## `detail` (type: `string`):

No description

## `changes` (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 = {
    "mode": "search",
    "locations": [
        "Cape Town"
    ],
    "urls": [
        "https://www.property24.com/for-sale/cape-town/western-cape/432",
        "https://www.property24.com/for-sale/cape-town/western-cape/432/p2",
        "https://www.property24.com/for-sale/cape-town/western-cape/432?sp=pf%3D1000000%26pt%3D3000000%26so%3DPriceLow",
        "https://www.property24.com/to-rent/sandton/gauteng/109",
        "https://www.property24.com/commercial-property-for-sale/cape-town/western-cape/432",
        "https://www.property24.com/commercial-property-to-rent/sandton/gauteng/109",
        "https://www.property24.com/for-sale/gardens/cape-town/western-cape/9145",
        "https://www.property24.com/for-sale/western-cape/9",
        "https://www.property24.com/for-sale/somerset-west-rural/somerset-west/western-cape/17468/117643707",
        "https://www.property24.com/new-developments-for-sale/cape-town/western-cape/432",
        "https://www.property24.com/new-developments-for-sale/woodstock/cape-town/western-cape/1-on-albert/10164/3161",
        "https://www.property24.com/estate-agents/matterhorn-properties/ronnie-du-toit/511147",
        "https://www.property24.com/estate-agencies/cape-town/western-cape/432",
        "https://m.property24.com/for-sale/cape-town/western-cape/432"
    ],
    "proxy": {
        "useApifyProxy": true,
        "apifyProxyGroups": [
            "RESIDENTIAL"
        ]
    }
};

// Run the Actor and wait for it to finish
const run = await client.actor("abotapi/property24-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 = {
    "mode": "search",
    "locations": ["Cape Town"],
    "urls": [
        "https://www.property24.com/for-sale/cape-town/western-cape/432",
        "https://www.property24.com/for-sale/cape-town/western-cape/432/p2",
        "https://www.property24.com/for-sale/cape-town/western-cape/432?sp=pf%3D1000000%26pt%3D3000000%26so%3DPriceLow",
        "https://www.property24.com/to-rent/sandton/gauteng/109",
        "https://www.property24.com/commercial-property-for-sale/cape-town/western-cape/432",
        "https://www.property24.com/commercial-property-to-rent/sandton/gauteng/109",
        "https://www.property24.com/for-sale/gardens/cape-town/western-cape/9145",
        "https://www.property24.com/for-sale/western-cape/9",
        "https://www.property24.com/for-sale/somerset-west-rural/somerset-west/western-cape/17468/117643707",
        "https://www.property24.com/new-developments-for-sale/cape-town/western-cape/432",
        "https://www.property24.com/new-developments-for-sale/woodstock/cape-town/western-cape/1-on-albert/10164/3161",
        "https://www.property24.com/estate-agents/matterhorn-properties/ronnie-du-toit/511147",
        "https://www.property24.com/estate-agencies/cape-town/western-cape/432",
        "https://m.property24.com/for-sale/cape-town/western-cape/432",
    ],
    "proxy": {
        "useApifyProxy": True,
        "apifyProxyGroups": ["RESIDENTIAL"],
    },
}

# Run the Actor and wait for it to finish
run = client.actor("abotapi/property24-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 '{
  "mode": "search",
  "locations": [
    "Cape Town"
  ],
  "urls": [
    "https://www.property24.com/for-sale/cape-town/western-cape/432",
    "https://www.property24.com/for-sale/cape-town/western-cape/432/p2",
    "https://www.property24.com/for-sale/cape-town/western-cape/432?sp=pf%3D1000000%26pt%3D3000000%26so%3DPriceLow",
    "https://www.property24.com/to-rent/sandton/gauteng/109",
    "https://www.property24.com/commercial-property-for-sale/cape-town/western-cape/432",
    "https://www.property24.com/commercial-property-to-rent/sandton/gauteng/109",
    "https://www.property24.com/for-sale/gardens/cape-town/western-cape/9145",
    "https://www.property24.com/for-sale/western-cape/9",
    "https://www.property24.com/for-sale/somerset-west-rural/somerset-west/western-cape/17468/117643707",
    "https://www.property24.com/new-developments-for-sale/cape-town/western-cape/432",
    "https://www.property24.com/new-developments-for-sale/woodstock/cape-town/western-cape/1-on-albert/10164/3161",
    "https://www.property24.com/estate-agents/matterhorn-properties/ronnie-du-toit/511147",
    "https://www.property24.com/estate-agencies/cape-town/western-cape/432",
    "https://m.property24.com/for-sale/cape-town/western-cape/432"
  ],
  "proxy": {
    "useApifyProxy": true,
    "apifyProxyGroups": [
      "RESIDENTIAL"
    ]
  }
}' |
apify call abotapi/property24-scraper --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,abotapi/property24-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/rwfuZfOLbo4xd4imD/builds/87rgwDNphNh363sge/openapi.json
