# Private Property South Africa Listings & New-Listing Monitor (`neverempty/privateproperty-listings`) Actor

Every privateproperty.co.za listing for sale or to rent as clean JSON: price, bedrooms, bathrooms, floor and land size, suburb, city, agency and photos. Monitoring mode returns only listings that are new since your last run, and its first run is free. Unofficial.

- **URL**: https://apify.com/neverempty/privateproperty-listings.md
- **Developed by:** [NeverEmpty](https://apify.com/neverempty) (community)
- **Categories:** Real estate, Lead generation, Automation
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $1.15 / 1,000 listing returneds

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

## Private Property South Africa Listings & New-Listing Monitor

Get every property for sale or to rent on **privateproperty.co.za** as clean, ready-to-use rows: price in ZAR, bedrooms, bathrooms, floor and land size, address, suburb, city, province, property type, agency and photo URLs. Search by province, city or suburb, or paste any results page you already filtered on the site.

Turn on **monitoring mode** and schedule the Actor: each run returns only the listings that appeared since the last run, so a daily schedule becomes a feed of new South African listings. The first monitoring run only remembers what is listed today and is free.

### What you can do with it

- **Track new listings** in a suburb, city or province every day and send them to a sheet, CRM or alert.
- **Spot price drops** with the price-reduced filter, or follow auctions, show days and private sellers.
- **Compare prices** per area, property type and size with numbers you can sort and chart.
- **Build a lead list of agencies** active in an area, with their logos and the listings they carry.

### What you get for each listing

| Field | Example |
|---|---|
| `listingId` | `T5660237` |
| `title` | `2 Bedroom Apartment` |
| `price` / `priceCurrency` | `1679000` / `ZAR` |
| `priceType` / `rentalTerm` | `sale`, or `monthly-rent` with `monthly` |
| `priceText` | `R 1 679 000` (kept as written, also for "POA" or "Bank Negotiable") |
| `bedrooms`, `bathrooms`, `parkingSpaces` | `2`, `1`, `1` |
| `floorSizeSqm`, `erfSizeSqm` | `64`, `null` |
| `propertyType` | `Apartment`, `House`, `Townhouse`, `Vacant Land`, ... |
| `address`, `suburb`, `area`, `city`, `province` | `62 Twin Oaks, 101 Ronelle`, `Brackenfell South`, `Brackenfell`, `Cape Town`, `Western Cape` |
| `latitude`, `longitude` | `-33.8964803`, `18.7140406` |
| `labels` and flags | `New`, `Reduced`, `On Show` (+ `onShowDate`), `Auction`, `Under Offer`, `Sold`; `isNew`, `isReduced`, ... |
| `isPrivateSeller` | `false` |
| `agencyName`, `agencyLogoUrl` | `eXp South Africa`, logo URL |
| `listingTier` | `standard` or `featured` |
| `availableFrom` | rentals: the date the property is available |
| `listedDate` | with "Open each listing": the date shown on the listing page |
| `mainImageUrl`, `imageUrls` | photo URLs |
| `url`, `searchedLocation`, `scrapedAt` | listing link, the place the site resolved your search to, time of the run |

Every row names the place the site resolved your search to (`searchedLocation`), so you can see at a glance that "Cape Town" really meant Cape Town, Western Cape.

### How to use it

**Search by place**

```json
{
  "channel": "sale",
  "location": "cape-town",
  "maxListings": 100
}
```

`location` takes a province (`gauteng`, `western-cape`, ...), a region from the list in the input form (`cape-town`, `johannesburg`, `durban`, `pretoria`, ...), or any area path copied from a results address, such as `western-cape/cape-town/blouberg/60`.

**Paste a results page** you already filtered on the site:

```json
{ "searchUrl": "https://www.privateproperty.co.za/to-rent/gauteng/johannesburg/33?fp=5000&tp=15000&bd=2" }
```

**Daily feed of new listings with a price drop**

```json
{
  "location": "western-cape/cape-town/blouberg/60",
  "monitoringMode": true,
  "onlyReduced": true
}
```

Schedule it in Apify (for example every morning). The first run remembers today's listings for free; each later run returns only listings that are new since the previous run.

#### Filters

- Price (ZAR), bedrooms, bathrooms, parking, floor size and land size. Price and bedroom limits are also sent to the site, so fewer pages are read.
- Property types, suburbs, keywords (any / all), keywords to exclude, agency names.
- Only price-reduced, only marked New, only on show, only private sellers; exclude auctions; exclude under offer and sold.
- A listing whose value is unknown (for example "POA" with a price filter) is kept rather than dropped, unless you tick "Only listings with a published price".

#### Order

Searches by place return the newest listings first, which is what a monitoring feed needs. You can also choose lowest price first, or the site's own order. A pasted results page keeps the order in its address.

### Input

| Field | What it does |
|---|---|
| `searchUrl` | A results page copied from privateproperty.co.za. When set, it replaces `channel` and `location`, and its own filters are kept. |
| `channel` | `sale` or `rent` |
| `location` | Province, region or area path (see above) |
| `sortBy` | `newest`, `price-low` or `site` (the site's own order) |
| `maxListings` / `maxPages` | Stop after this many listings returned / results pages read |
| `includeDetails` | Open each returned listing for `listedDate`, the exact property type, the full description and photos |
| `useProxy` | Use Apify Proxy from the first request. Off by default; the Actor switches to a proxy by itself if the site blocks a request. |
| `keywords`, `keywordMatch`, `excludeKeywords` | Words in the title, property type, description, address, place names, agency or price text; `keywordMatch` is `any` or `all` |
| `agencyNames` | Keep only these agencies (part of the name is enough). Listings whose agency name is not shown are left out. |
| `minPrice`, `maxPrice`, `requirePrice` | Price in ZAR; `requirePrice` drops "POA" and other unpriced listings |
| `minBedrooms`, `maxBedrooms`, `minBathrooms`, `minParkingSpaces` | Room counts |
| `minFloorSizeSqm`, `maxFloorSizeSqm`, `minErfSizeSqm`, `maxErfSizeSqm` | Floor and land size in m² |
| `propertyTypes`, `suburbs` | Keep only these property types or suburbs |
| `onlyReduced`, `onlyNew`, `onlyOnShow`, `onlyPrivateSellers` | Keep only listings with that label |
| `excludeAuctions`, `excludeUnderOffer` | Drop auctions; drop listings under offer or sold |
| `monitoringMode`, `resetMonitoringState` | Return only listings new since the last run; start the memory again |

### Pricing

You pay per listing returned. Rows that explain a result instead of returning a listing (no results for the search, an unknown area, filters that matched nothing, nothing new since the last monitoring run, a page that could not be read, the first monitoring run) are free. Set a spending limit on the run and the Actor stops at that limit; in monitoring mode, listings it could not return are kept for the next run.

### FAQ

**How do I get only new listings from privateproperty.co.za every day?**
Turn on "Monitoring mode" and schedule the Actor. The first run is free and remembers what is listed now; every later run with the same input returns only listings that appeared since the run before.

**How do I find price-reduced properties in a South African suburb?**
Set `location` to the suburb's area path (copy it from a results address) and tick "Only price-reduced listings". Combine it with monitoring mode to be told about new reductions only.

**Can I export Private Property rentals with the monthly rent as a number?**
Yes. Rentals come with `price` as a number and `rentalTerm` (`monthly`, `weekly`, `daily`). Weekly and daily rents are never compared with a monthly budget, so they are kept rather than dropped by mistake.

**Does it give the listing date?**
Tick "Open each listing for its date, full description and photos". The Actor then opens each returned listing and adds `listedDate`, the exact property type, the full description and the photo URLs shown on that page. The price per listing is the same.

**Are agents' phone numbers or names included?**
No. Rows contain the agency, not the people: agents' personal names are left out and South African phone numbers inside descriptions are replaced with "\[phone removed]".

### Limitations

- The results pages show a short description (sales) and one to three photos per listing; the full description, the listing date and the full photo set come from "Open each listing", which reads one more page per listing.
- Featured tiles do not print the agency name. The Actor fills it in when the same agency's logo appears on a standard tile in the same run; otherwise `agencyName` stays empty.
- "Newest first" is the site's own order by when a listing was (re)activated, so a listing that was taken off and listed again appears as new.
- This is an unofficial tool and is not affiliated with Private Property. Use the data in line with the site's terms and the law that applies to you.

### Thanks for using this Actor

We build these tools for people who run them every day, and we improve them from what users tell us.

- **Missing a field, or need another filter?** Tell us in the **Issues** tab. If the data is there, we add it.
- **Found a bug or a wrong value?** Post the run ID in the **Issues** tab. Wrong data is the thing we fix first.

If this Actor saved you time, a short review helps other people find it.

# Actor input Schema

## `searchUrl` (type: `string`):

Paste a results page from privateproperty.co.za, e.g. https://www.privateproperty.co.za/for-sale/western-cape/cape-town/55 or https://www.privateproperty.co.za/to-rent/gauteng/johannesburg/33?bd=2. The place and every filter in the address are kept; only the page number is replaced. When set, channel and location are ignored.

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

sale = homes for sale, rent = homes to rent. The rent unit (per month and so on) is returned as rentalTerm.

## `location` (type: `string`):

A province (western-cape, gauteng, kwazulu-natal, eastern-cape, free-state, limpopo, mpumalanga, north-west, northern-cape), all of the country (south-africa), a region (cape-town, johannesburg, pretoria, centurion, durban, garden-route, boland, nelson-mandela-bay ...), or an area path with its number copied from any results address, e.g. western-cape/cape-town/blouberg/60. Empty with no search URL = Cape Town.

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

Empty = newest first for a location, or the pasted address's own order for a search URL. Monitoring mode always reads newest first.

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

How many listings to return. You are charged for the rows you actually receive. The site shows 20 listings per page.

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

Hard cap on the number of results pages requested. 0 = decide from maxListings (20 listings per page, plus a margin; further when filters drop listings). Reading pages is free; only returned listings are charged.

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

Also opens every returned listing's own page to add listedDate, the site's exact property type, the full description and the photo URLs shown on that page (up to 9 on most listings). Slower (one more page per listing); the price per listing is the same.

## `useProxy` (type: `boolean`):

Off by default: the site answers Apify's network directly. If a request is blocked (403/429/503) the Actor switches to a proxy session by itself and retries.

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

Keep only listings whose title, type, description, address, suburb, area, city, province, agency or price text contains these words. Case-insensitive.

## `keywordMatch` (type: `string`):

any = at least one keyword must appear; all = every keyword must appear.

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

Drop listings containing any of these words.

## `agencyNames` (type: `array`):

Keep only listings from these agencies (partial match). A listing without an agency name (private sellers, and promoted tiles whose agency could not be named) is kept.

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

Sale price, or monthly rent. 0 = off. With a location, the limit is also sent to the site so fewer pages are read.

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

Sale price, or monthly rent. 0 = off. With a location, the limit is also sent to the site so fewer pages are read.

## `requirePrice` (type: `boolean`):

Drop listings without a number for a price (On Auction, Bank Negotiable and similar).

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

-1 = off. The site lists studios as 0.5 bedrooms. With a location, the limit is also sent to the site.

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

-1 = off.

## `minBathrooms` (type: `number`):

0 = off. Half bathrooms are counted (2.5).

## `minParkingSpaces` (type: `integer`):

0 = off.

## `minFloorSizeSqm` (type: `integer`):

0 = off. A listing with no published floor size is kept.

## `maxFloorSizeSqm` (type: `integer`):

0 = off.

## `minErfSizeSqm` (type: `integer`):

0 = off. Land given in hectares is converted to m². A listing with no published land size is kept.

## `maxErfSizeSqm` (type: `integer`):

0 = off.

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

Partial match on the type: Apartment, House, Townhouse, Cluster, Duplex, Flat, Studio Apartment, Land ... A listing with no type is kept.

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

Keep only listings in these suburbs (partial match, e.g. Sea Point).

## `onlyReduced` (type: `boolean`):

Keep only listings carrying the site's Reduced banner.

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

Keep only listings carrying the site's New banner.

## `onlyOnShow` (type: `boolean`):

Keep only listings with an On Show banner (the show day is returned as onShowDate).

## `onlyPrivateSellers` (type: `boolean`):

Keep only listings marked as listed privately (no agency).

## `excludeAuctions` (type: `boolean`):

Drop listings shown as On Auction or with an Auction badge.

## `excludeUnderOffer` (type: `boolean`):

Drop listings carrying an Under Offer or Sold banner.

## `monitoringMode` (type: `boolean`):

Turn this on and schedule the Actor. The first run only remembers the listings on the site now and is free. Every later run reads newest first and returns only listings that appeared since, up to maxListings. Listings removed by your filters are remembered too; listings that did not fit under the limit are kept for the next run.

## `resetMonitoringState` (type: `boolean`):

One-shot: forget every listing remembered so far for this search. The next monitoring run starts again with a free first run.

## Actor input object example

```json
{
  "channel": "sale",
  "location": "cape-town",
  "maxListings": 20,
  "maxPages": 0,
  "includeDetails": false,
  "useProxy": false,
  "keywords": [],
  "keywordMatch": "any",
  "excludeKeywords": [],
  "agencyNames": [],
  "minPrice": 0,
  "maxPrice": 0,
  "requirePrice": false,
  "minBedrooms": -1,
  "maxBedrooms": -1,
  "minBathrooms": 0,
  "minParkingSpaces": 0,
  "minFloorSizeSqm": 0,
  "maxFloorSizeSqm": 0,
  "minErfSizeSqm": 0,
  "maxErfSizeSqm": 0,
  "propertyTypes": [],
  "suburbs": [],
  "onlyReduced": false,
  "onlyNew": false,
  "onlyOnShow": false,
  "onlyPrivateSellers": false,
  "excludeAuctions": false,
  "excludeUnderOffer": false,
  "monitoringMode": false,
  "resetMonitoringState": false
}
```

# Actor output Schema

## `results` (type: `string`):

One row per listing: listing number, heading, the price as a number in ZAR with what it means (sale, monthly, weekly or daily rent, as the listing says; prices on application stay null with the site's own text kept), bedrooms, bathrooms, parking spaces, floor and land size, street address where the site publishes it, suburb, area, city, province and map position, property type, labels (New, Reduced, On Show with its date, Auction, Under Offer, Sold, Available from), private seller or agency, the agency name and logo, and the photo URLs. Every row names the place the site resolved the search to. Agents' personal names are never returned. Searches with no results, unknown area ids, filters that matched nothing, monitoring runs with nothing new and pages that could not be read come back as their own rows and are not charged.

# 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 = {
    "searchUrl": "",
    "location": "cape-town",
    "keywords": [],
    "excludeKeywords": [],
    "agencyNames": [],
    "propertyTypes": [],
    "suburbs": []
};

// Run the Actor and wait for it to finish
const run = await client.actor("neverempty/privateproperty-listings").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 = {
    "searchUrl": "",
    "location": "cape-town",
    "keywords": [],
    "excludeKeywords": [],
    "agencyNames": [],
    "propertyTypes": [],
    "suburbs": [],
}

# Run the Actor and wait for it to finish
run = client.actor("neverempty/privateproperty-listings").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 '{
  "searchUrl": "",
  "location": "cape-town",
  "keywords": [],
  "excludeKeywords": [],
  "agencyNames": [],
  "propertyTypes": [],
  "suburbs": []
}' |
apify call neverempty/privateproperty-listings --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,neverempty/privateproperty-listings"
        }
    }
}
```

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/OxZaMRTBUTwyslcOe/builds/QbnozpWQf45bh1Uyz/openapi.json
