# PropertyFinder Scraper & New-Listing Monitor - Dubai & UAE (`neverempty/propertyfinder-listings`) Actor

Turn monitoring on and every later run returns only the listings it has not returned before, so you are never charged for the same property twice. Every PropertyFinder listing as clean JSON: price with its meaning, bedrooms, bathrooms, area, community, furnishing, coordinates. Unofficial.

- **URL**: https://apify.com/neverempty/propertyfinder-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 $0.73 / 1,000 property 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?

Actors are web data automations that power AI and operations. They run on the Apify platform to scrape websites, process data, connect APIs, and automate workflows.
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.
Actors are written with capital "A".

## 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.
The best way to integrate Actors is as follows.

- **AI agents and MCP clients** — the [Apify MCP server](https://docs.apify.com/integrations/mcp.md) at `https://mcp.apify.com` (remote, streamable HTTP, OAuth on first use).
- **Agentic workflows and local Actor development** — [Agent Skills](https://apify.com/.well-known/agent-skills/index.json) with the [Apify CLI](https://docs.apify.com/cli/docs.md): `npm install -g apify-cli`, then `apify login`.
- **JavaScript/TypeScript projects** — the official [JS/TS client](https://docs.apify.com/api/client/js/docs.md): `npm install apify-client`.
- **Python projects** — the official [Python client](https://docs.apify.com/api/client/python/docs.md): `pip install apify-client`.
- **Any other language** — the [REST API](https://docs.apify.com/api/v2.md).

For usage examples, see the [API](#api) section below.

For more details, see Apify documentation as [Markdown index](https://docs.apify.com/llms.txt) and [Markdown full-text](https://docs.apify.com/llms-full.txt).

# README

## PropertyFinder Scraper - Real Estate Data for Dubai and the UAE

Pull property listings from **PropertyFinder.ae** — the UAE's largest property portal — as clean JSON: the price as a **number with the period it belongs to**, the size in sqft, bedrooms, bathrooms, furnishing, amenities, map coordinates, the brokerage and the date it was listed.

*Unofficial. This Actor is not affiliated with, endorsed by, or sponsored by Property Finder. All product names are trademarks of their respective owners.*

No API key. Reads the same public pages a visitor sees.

***

### Run it tomorrow and you only get what is new

Schedule this Actor with `monitoringMode: true` and each run returns **only the listings it has not returned before**. Not "here is everything again, please de-duplicate it yourself" — the Actor remembers what it already gave you, so a daily feed costs you the handful of new homes, not the whole board every morning. **You are never charged for the same property twice.**

Listings your filters removed are remembered too, so widening a filter later does not resurrect them as false "new" listings. Listings that did not fit under your limit are **not** remembered, so they are still waiting on the next run instead of being silently lost.

***

### Why this one is different

#### The filters run here, because PropertyFinder's robots.txt says they must

`robots.txt` (read on 2026-09-04) disallows every filter parameter — `?price=`, `?bedrooms=`, `?furnished=`, `?location=`, `?category=`, `?built_up_area=` and more — and disallows a page number combined with anything else (`*?*page=*&*`, `*&*page=*`). It allows exactly one thing:

```
## Allow pagination when standalone
Allow: *?page=*
```

So this Actor requests only the plain SEO addresses and `?page=N` on its own, and applies **every filter to rows it has already fetched**. Nothing extra is requested from PropertyFinder, nothing extra is charged, and if you paste a search URL with filter parameters in it, **those parameters are stripped before the request is made**.

#### A price is useless without its period — so both are always there

A sale price is a total. A rent on PropertyFinder is **per year**. Selling you a bare number would let a 90,000 rent sit in the same column as a 2,950,000 sale, so every row carries `price`, `priceCurrency` and `pricePeriod` (`sell` or `yearly`) together, plus `pricePerArea` when PropertyFinder computed one.

Nothing is guessed to fill a gap: a property whose price is withheld comes back with `price: null` and `isPriceHidden: true` — never a zero.

#### Broker and agent phone numbers and emails are removed, on purpose

PropertyFinder's own page data carries the brokerage's **phone number on 104 of 111 properties measured**, and **65 distinct email addresses** across the same sample, with more written into the advert text.

This Actor **does not output those fields at all**, and it **removes email addresses and UAE phone numbers from the description text** as well. You get the brokerage name, the agent name and a link to the brokerage's page. Property data, not a contact list. If you need contact data, use a tool that says so.

#### One request, 20 to 28 complete properties

PropertyFinder ships the full property — description, price, size, bedrooms, amenities, coordinates, brokerage — inside the listing page itself. This Actor reads all of them from that single page and **never opens individual property pages**. 200 properties costs about 10 requests, not 210.

The page also carries new-project adverts in the same slots; those are **not** counted or charged as properties.

#### "studio" is a word, not a number — and it is kept as both

PropertyFinder writes `bedrooms: "studio"`. That comes back as `bedrooms: "studio"` and `bedroomsValue: 0`, so you can read it and sort by it.

#### Values we cannot confirm are not invented

`furnishing` is passed through exactly as PropertyFinder publishes it — `NO` on 76, `YES` on 27 and `PARTLY` on 8 of the 111 properties measured, so roughly a quarter come furnished. `completionStatus` is published on sale listings only; on rentals it comes back `null` rather than an empty string pretending to be an answer. Currency and size unit are taken from the page rather than assumed, even though every property measured was AED and sqft.

#### An empty search is an answer, not a silent fallback

A city and property type PropertyFinder does not have returns **one `no-results` row saying so, and it is not charged** — it never quietly falls back to every property in the country. A page that could not be read at all is a different row (`unreadable`) carrying the HTTP status. And if your filters removed everything, that is a **third** kind of row (`no-filter-match`) which states how many properties were actually read — so "my filter was too tight" is never confused with "the portal is empty".

***

### Input

Either paste a search URL, or build one from three fields.

| Field | Type | Default | Description |
|---|---|---|---|
| `searchUrl` | string | — | e.g. `https://www.propertyfinder.ae/en/buy/dubai/villas-for-sale.html`. Query parameters are stripped |
| `offeringType` | string | `buy` | `buy`, `rent`, `commercial-buy`, `commercial-rent` |
| `city` | string | `dubai` | `dubai`, `abu-dhabi`, `sharjah`, `ajman`, `ras-al-khaimah`, `fujairah`, `umm-al-quwain`, `al-ain`. Empty = the whole UAE |
| `propertyType` | string | `apartments` | `properties` (all), `apartments`, `villas`, `townhouses`, `penthouses`, `offices`, `shops`, `warehouses`… |
| `maxListings` | integer | 20 | 1-1000. One property = one row = one charged event |
| `maxPages` | integer | 0 | Hard cap on requests; 0 = decide from `maxListings` |
| `includeDescription` | boolean | `true` | Full advert text, HTML stripped, contacts removed |
| `useProxy` | boolean | `false` | Off by default — PropertyFinder answers Apify's network directly. Switches on by itself if blocked |
| `keywords` | array | — | Keep only properties containing one of these words |
| `keywordMatch` | string | `any` | `any` or `all` |
| `excludeKeywords` | array | — | Drop properties containing one of these |
| `brokerNames` | array | — | Keep only these brokerages (partial match) |
| `minPrice` / `maxPrice` | integer | 0 | AED. **A property with no published price is kept** |
| `requirePrice` | boolean | `false` | Drop properties whose price is withheld |
| `minBedrooms` / `maxBedrooms` | integer | -1 | -1 = off. `0` keeps studios and up |
| `minBathrooms` | integer | -1 | -1 = off |
| `minAreaSqft` / `maxAreaSqft` | integer | 0 | 0 = off |
| `furnishing` | string | `any` | `YES`, `NO`, `PARTLY` |
| `verifiedOnly` | boolean | `false` | Only listings PropertyFinder has verified (105 of 111 measured) |
| `completionStatus` | string | `any` | `completed` or `off_plan`. Sale listings only |
| `listedWithinDays` | integer | 0 | 0 = no limit. **An unreadable date is kept** |
| `monitoringMode` | boolean | `false` | Return only properties not returned on a previous run |
| `resetMonitoringState` | boolean | `false` | One-shot: forget what was already returned |

```json
{ "offeringType": "buy", "city": "dubai", "propertyType": "apartments", "maxListings": 100,
  "keywords": ["downtown", "marina"], "minPrice": 1000000, "maxPrice": 4000000,
  "minBedrooms": 2, "furnishing": "YES", "verifiedOnly": true }
```

#### Filters are applied before you are charged

A property that does not match is never delivered and never charged. Three of the filters refuse to guess: a property with **no published price is kept** by `minPrice` and `maxPrice` (an unknown price is not a low one), a property with an **unreadable listing date is kept** by `listedWithinDays`, and a property whose furnishing or completion status PropertyFinder did not publish is kept rather than dropped. Turn on `requirePrice` if you want the unknowns dropped.

#### Monitoring mode turns this into a daily feed

Schedule it and set `monitoringMode: true`: each run returns **only the properties it has not returned before**. The first run returns everything and remembers it. Properties that were read but removed by your filters are remembered too, so a later change of filters does not resurrect them as false "new" listings. Properties that were read but did not fit under `maxListings` are **not** remembered, so they are still waiting for you on the next run rather than being silently lost. You pay for new stock, not for the same page every morning.

Measured on 2026-09-04: **188,682** properties for sale across the UAE, **111,191** for sale and **122,336** for rent in Dubai alone.

***

### Output

One row per property:

```json
{
  "source": "propertyfinder.ae",
  "status": "ok",
  "listingId": "139221316",
  "referenceNumber": "BH-S-230009-3",
  "title": "Burj View | High Floor | Vacant",
  "url": "https://www.propertyfinder.ae/en/plp/buy/apartment-for-sale-dubai-downtown-dubai-boulevard-central-towers-boulevard-central-tower-1-139221316.html",
  "offeringType": "Residential for Sale",
  "propertyType": "Apartment",
  "completionStatus": "completed",
  "price": 2950000,
  "priceCurrency": "AED",
  "pricePeriod": "sell",
  "isPriceHidden": false,
  "pricePerArea": 2538,
  "pricePerAreaUnit": "sqft",
  "bedrooms": "2",
  "bedroomsValue": 2,
  "bathrooms": "2",
  "bathroomsValue": 2,
  "sizeValue": 1162,
  "sizeUnit": "sqft",
  "furnishing": "NO",
  "locationName": "Boulevard Central Tower 1",
  "locationFullName": "Boulevard Central Tower 1, Boulevard Central Towers, Downtown Dubai, Dubai",
  "locationBreadcrumb": "Dubai, Downtown Dubai, Boulevard Central Towers",
  "latitude": 25.191404342651367,
  "longitude": 55.273895263671875,
  "amenities": ["Balcony", "Built in Wardrobes", "Covered Parking", "Security"],
  "brokerName": "betterhomes - Business Bay",
  "brokerUrl": "https://www.propertyfinder.ae/en/broker/betterhomes-business-bay-2593",
  "agentName": "Mamoun Hemora",
  "agentLanguages": ["English"],
  "isVerified": true,
  "isNewConstruction": false,
  "imagesCount": 18,
  "listedAt": "2026-09-02T10:41:37Z",
  "description": "…",
  "scrapedAt": "2026-09-04T18:30:05.658Z"
}
```

Fill rates measured across 111 live properties on 2026-09-04: price, size, bedrooms, bathrooms, property type, location with coordinates, brokerage and listing date on **100%**; amenities on **101 of 111**; price per area and completion status on **100% of sale listings** (PropertyFinder does not publish either on rentals).

#### Rows that are never charged

| `status` | when |
|---|---|
| `no-results` | PropertyFinder has no listings for this search. That is its answer, not a failure |
| `no-such-search` | PropertyFinder has no page at that address - a mistyped emirate or property type. **Not** a claim that there is nothing there |
| `no-filter-match` | Listings were read, but your filters removed all of them. The row says how many were read |
| `no-new-listings` | Monitoring mode: nothing new since the previous run |
| `unreadable` | The page could not be read, or its embedded data was missing |

A page is reported as **`unreadable`, never as "no listings"** when its embedded data is missing **or when the page states a total above zero while yielding no properties** — the count PropertyFinder prints on the page is compared against what was actually read. So a redesign on PropertyFinder's side, or a bot check that empties the list, shows up as an error rather than as a quiet, permanent "there is nothing for sale".

***

### Pricing

Pay per property returned. Rows that report an empty search, a filter that matched nothing, or a page that could not be read are **not** charged. A property that appears twice is dropped before delivery, so it is never charged twice.

***

### Notes

- `robots.txt` was read in full on 2026-09-04. The `User-agent: *` section disallows filter parameters and `*?*page=*&*`, and allows standalone pagination (`Allow: *?page=*`). This Actor requests only the public SEO listing addresses plus `?page=N` on its own.
- Only public pages are read. No login, no API key.

# Actor input Schema

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

Paste a PropertyFinder listing page, for example https://www.propertyfinder.ae/en/buy/dubai/villas-for-sale.html, and the settings below are ignored. Any query string you paste is dropped: PropertyFinder's robots.txt disallows filter parameters, so filtering happens here instead, on the rows already fetched.

## `offeringType` (type: `string`):

Residential sale, residential rent, or the commercial equivalents.

## `city` (type: `string`):

dubai, abu-dhabi, sharjah, ajman, ras-al-khaimah, fujairah, umm-al-quwain or al-ain. Leave empty to search the whole UAE (188,000+ properties for sale at the time of writing).

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

properties (all types), apartments, villas, townhouses, penthouses, or a commercial type such as offices, shops or warehouses. It has to be the plural word PropertyFinder itself uses in the address.

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

How many properties to return. You are charged for the rows you actually receive.

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

A hard stop on how many pages are requested, whatever the filters do. 0 lets the run work it out from the number of properties you asked for.

## `includeDescription` (type: `boolean`):

The advert text as plain text. Turn it off for a smaller dataset.

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

Off by default because PropertyFinder answers Apify's own network directly, which is faster and costs you no proxy traffic. If PropertyFinder ever starts blocking, the run switches to a proxy on its own.

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

Keep only properties whose title, description, type, location, amenities or brokerage contain these words. No extra requests are made: the words are matched against rows already fetched.

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

any: at least one keyword. all: every keyword.

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

Drop properties containing any of these words.

## `brokerNames` (type: `array`):

Keep only properties listed by these brokerages. Partial names work.

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

Sale prices are totals; rents are per year, the way PropertyFinder publishes them.

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

0 means no maximum.

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

Off by default: a property with no price is not treated as a cheap one, it is simply kept.

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

0 keeps studios and up. -1 turns the filter off.

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

-1 turns the filter off.

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

-1 turns the filter off.

## `minAreaSqft` (type: `integer`):

0 means no minimum.

## `maxAreaSqft` (type: `integer`):

0 means no maximum.

## `furnishing` (type: `string`):

PropertyFinder publishes exactly three values: YES, NO and PARTLY.

## `verifiedOnly` (type: `boolean`):

PropertyFinder marks listings it has verified with the brokerage. 105 of 111 properties measured carried the mark.

## `completionStatus` (type: `string`):

Sale listings carry this; rental listings do not publish it, so this filter leaves rentals alone.

## `listedWithinDays` (type: `integer`):

0 means no date filter. A property whose date cannot be read is kept, not dropped.

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

Turn this on and schedule the Actor: each run returns only the properties it has never returned before, so you are charged for new stock rather than for the same pages again. The first run returns everything and remembers it.

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

Clears the memory for this search, so the next monitoring run starts from scratch.

## Actor input object example

```json
{
  "offeringType": "buy",
  "city": "dubai",
  "propertyType": "apartments",
  "maxListings": 20,
  "maxPages": 0,
  "includeDescription": true,
  "useProxy": false,
  "keywords": [],
  "keywordMatch": "any",
  "excludeKeywords": [],
  "brokerNames": [],
  "minPrice": 0,
  "maxPrice": 0,
  "requirePrice": false,
  "minBedrooms": -1,
  "maxBedrooms": -1,
  "minBathrooms": -1,
  "minAreaSqft": 0,
  "maxAreaSqft": 0,
  "furnishing": "any",
  "verifiedOnly": false,
  "completionStatus": "any",
  "listedWithinDays": 0,
  "monitoringMode": false,
  "resetMonitoringState": false
}
```

# Actor output Schema

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

One row per property: title, description, price with its currency and period, price per sqft, size, bedrooms and bathrooms, furnishing, completion status, amenities, the location with map coordinates, the brokerage, the agent name and the date it was listed. Searches with no results, filters that matched nothing, and pages that could not be read come back as their own rows and are not charged. Broker and agent phone numbers and emails are not collected.

# 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("neverempty/propertyfinder-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 = {}

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

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,neverempty/propertyfinder-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/6SuqRJhufMwDNajjF/builds/QDCf5NfEhcYyg35qz/openapi.json
