# Southeast Asia Property Listings by Country & Bedroom Count (`dataspryva/southeast-asia-property-listings-by-country`) Actor

Search PropertyGuru for-sale and for-rent listings across Singapore, Malaysia, Thailand and Indonesia by property type, bedrooms and price, with agent details, area and photos per listing.

- **URL**: https://apify.com/dataspryva/southeast-asia-property-listings-by-country.md
- **Developed by:** [Dataspryva](https://apify.com/dataspryva) (community)
- **Categories:** Real estate, Lead generation, Automation
- **Security:** Limited permissions
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $0.49 / 1,000 results

This Actor is paid per event and usage. You are charged both the fixed price for specific events and for Apify platform usage.
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

## PropertyGuru & DDProperty Listings Scraper

Collect property listings from Singapore and Malaysia on PropertyGuru and from Thailand on DDProperty. Search by keyword, property type, district, price, bedrooms, bathrooms, size, tenure, furnishing, floor level, availability, MRT distance and sort order, or paste search, listing and project URLs. Every record keeps the source's price label, area unit and contact details instead of flattening them.

### What it does

- **Three live markets** — Singapore (`propertyguru.com.sg`), Malaysia (`propertyguru.com.my`) and Thailand (`ddproperty.com/en`) with local currencies and area units.
- **Filter-first search** — keyword or area, sale or rent, property type, Singapore district D01-D28, price, exact bedroom and bathroom chips, floor size, tenure, furnishing, floor level, move-in availability, MRT distance and eight sort orders.
- **Paste any URL** — search-result URLs paginate, listing URLs return one detailed record, project URLs include the project's unit types. The market is inferred from the host.
- **Optional detail enrichment** — opens each listing page for the description, agent mobile, licence, rating, agency, coordinates, nearby transit and schools, amenities, facilities, floor plans, videos and virtual tours.
- **Project and new-launch aware** — reads unit-level prices, bedroom and bathroom counts, floor-area ranges and floor plans; development contact replaces agent contact where the source publishes a developer instead of an agent.
- **Deep pagination with deduplication** — follows the site's own page count until your item or page limit, and removes repeated promoted cards.

### Output per listing

| Group | Fields |
|---|---|
| Identity | `recordType`, `listingId`, `title`, `listingUrl`, `transactionType`, `country`, `sourceDomain`, `sourceUrl`, `sourcePage`, `statusCode`, `listerType`, `accountType` |
| Price | `price`, `priceText`, `priceType`, `priceMin`, `priceMax`, `currency`, `pricePerSqft`, `pricePerSqm`, `pricePerArea`, `pricePerAreaUnit` |
| Property | `propertyType`, `propertyTypeCode`, `propertyTypeGroup`, `bedrooms`, `bathrooms`, `floorArea`, `floorAreaUnit`, `floorAreaSqft`, `floorAreaSqm`, `landArea`, `landAreaUnit`, `landAreaSqft`, `landAreaSqm`, `tenure`, `tenureCode`, `completionYear`, `furnishing`, `floorLevel`, `availability`, `isNewProject`, `isVerified`, `isOfficialListing`, `isDeveloperListing` |
| Project | `projectName`, `projectId`, `projectUrl`, `developer`, `developerUrl`, `developerWhatsapp`, `unitTypes` |
| Location | `address`, `fullAddress`, `shortAddress`, `streetName`, `district`, `districtCode`, `region`, `regionCode`, `postalCode`, `latitude`, `longitude` |
| Nearby | `nearbyMrt`, `nearestTransit`, `nearbySchools` |
| Contact | `agentId`, `agentName`, `agentProfileUrl`, `agentLicense`, `agentVerified`, `agentJobTitle`, `agentMobile`, `agentRating`, `agentReviewCount`, `agencyName`, `agencyId` |
| Media | `imageUrls`, `imageCount`, `thumbnailUrl`, `floorPlanUrls`, `videoUrls`, `virtualTourUrls` |
| Detail | `headline`, `description`, `amenities`, `facilities`, `highlights` |
| Dates | `postedOn`, `postedOnUnix`, `recency`, `scrapedAt` |

Empty fields are omitted from each record instead of being returned as null. `area` is kept as an alias of `floorArea` for compatibility with older datasets.

### Input

| Field | Type | Default | Description |
|---|---|---|---|
| `mode` | select, required | `search` | Search by filters, or scrape pasted URLs. |
| `country` | select | `SG` | `SG`, `MY` or `TH` (mode=search). |
| `listingType` | select | `sale` | Sale or rent. |
| `searchQuery` | text | – | Area, project, station or keyword. |
| `propertyType` | select | `any` | Condo, apartment, HDB, landed, bungalow, semi-detached, terrace, land or commercial. |
| `district` | select | `any` | Singapore district D01-D28. |
| `minPrice` / `maxPrice` | integer | – | Price range in SGD, MYR or THB. |
| `bedrooms` | multi-select | – | Studio (0) through 5+. |
| `bathrooms` | multi-select | – | 1 through 5+. |
| `minSize` / `maxSize` | integer | – | Floor area in sqft (SG/MY) or sqm (TH). |
| `tenure` | select | `any` | Freehold or leasehold (sale only). |
| `furnishing` | select | `any` | Unfurnished, partially or fully furnished. |
| `floorLevel` | select | `any` | Ground, low, mid, high or penthouse. |
| `availability` | select | `any` | Immediate to after three months (rent only). |
| `maxDistanceToMrt` | select | `any` | 250 m to 1.5 km (SG/TH). |
| `sort` | select | `recommended` | Newest, price, price per area or size. |
| `maxItems` | integer | `100` | Stop after this many listings. |
| `maxPages` | integer | `50` | Stop after this many search pages. |
| `includeDetails` | boolean | `true` | Enrich each listing with detail-page fields. |
| `startUrls` | URL list | – | Search, listing or project URLs (mode=byUrl). |

#### Example: two-bedroom condos in Singapore District 20

```json
{
  "mode": "search",
  "country": "SG",
  "listingType": "sale",
  "propertyType": "condo",
  "district": "D20",
  "bedrooms": ["2"],
  "maxItems": 20,
  "includeDetails": true
}
```

#### Example: rentals in Kuala Lumpur

```json
{
  "mode": "search",
  "country": "MY",
  "listingType": "rent",
  "searchQuery": "Mont Kiara",
  "minPrice": 2000,
  "maxPrice": 6000,
  "furnishing": "fully",
  "maxItems": 30
}
```

#### Example: Bangkok condos near the MRT

```json
{
  "mode": "search",
  "country": "TH",
  "listingType": "sale",
  "propertyType": "condo",
  "searchQuery": "Sukhumvit",
  "maxDistanceToMrt": "500m",
  "sort": "priceAsc",
  "maxItems": 20
}
```

#### Example: paste a search page and a project page

```json
{
  "mode": "byUrl",
  "startUrls": [
    { "url": "https://www.propertyguru.com.sg/property-for-sale?district_code=D09" },
    { "url": "https://www.propertyguru.com.my/property-listing/project/enlace-suite-ii-for-sale-by-amona-development-sdn-bhd-501086101" }
  ],
  "maxItems": 50
}
```

### Use cases

- **Market comparison** — compare asking prices and price per area across Singapore, Malaysia and Thailand in one dataset.
- **Lead research** — collect the public agent name, mobile, licence and agency for non-project listings, or the developer contact for project listings.
- **Investment screening** — combine district, price, size, tenure and MRT filters to shortlist candidates.
- **New-launch tracking** — follow project pages with unit-level prices, layouts and floor plans.
- **Listing audits** — re-scrape a saved set of URLs to spot price or availability changes.

### Notes and limitations

- **Indonesia is not available.** The PropertyGuru Indonesia marketplace closed on 2023-11-30, so the actor reports that closure instead of returning empty data.
- **Thailand uses DDProperty's English site.** That is the live Thai marketplace for this listing data; the old PropertyGuru Thailand domain no longer serves listings.
- **Agent email is not published.** The actor returns the public agent mobile, licence, profile and rating where the source shows them; it does not invent an email address.
- **Project pricing is a range or a "from" price.** When the source only gives unit ranges, the record carries `priceMin` and `priceMax` plus the source label rather than a made-up single price.
- **Short links are not supported.** Paste canonical search, listing or project URLs.
- **Pasted detail pages can be protected.** If a Thai listing or project page is blocked but the same id still appears in search, the record is rebuilt from search-card data. Detail-only fields such as the description, amenities and coordinates are then omitted, and the run status reports how many records used this fallback.

### FAQ

**Is this affiliated with PropertyGuru or DDProperty?**
No. This is an independent, third-party actor that reads publicly visible listing pages. It is not operated by or affiliated with either marketplace.

**Do I need an account, cookie or API key?**
No. The actor works without credentials or a proxy setup.

**Why is the agent mobile missing on some records?**
Mobile numbers are only published on some listing pages. Project and new-launch listings usually show a developer contact instead, so those records carry developer fields rather than agent fields. Email addresses are not published at all.

**Why does one listing have `price` and another only `priceMin`/`priceMax`?**
A fixed asking price becomes `price`; a range or a project with several unit prices becomes `priceMin`/`priceMax`. `priceText` always keeps the label shown by the source, including "Price on ask" or "Starting From".

**Why is `floorAreaUnit` sometimes sqft and sometimes sqm?**
Singapore and Malaysia publish square feet; Thailand publishes square metres. Both `floorAreaSqft` and `floorAreaSqm` are provided so records from different markets can be compared directly.

**What happens when a filter is not available in a market?**
The actor ignores that filter and says so in the run's status message. Singapore districts, for example, do not apply to Malaysia or Thailand.

**How many requests does a run make?**
One request per search page. With detail enrichment on, it also makes one request per emitted listing. Turning enrichment off gives the fastest and smallest run.

**Can I scrape more than a few thousand listings?**
Yes. Pagination follows the site's own last page and stops at `maxItems` or `maxPages`; duplicate promoted cards are removed. Very large runs take longer because pages are read at a polite pace.

**Are images and media links usable?**
Yes. Image, floor-plan, video and virtual-tour URLs are absolute links to the source's public media host.

**Who is responsible for how the contact data is used?**
Agent and developer contact details are personal or business data. You are responsible for using them lawfully in your jurisdiction and for respecting the marketplace's terms.

# Actor input Schema

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

Search listings by filters, or scrape specific search, listing and project URLs.

## `country` (type: `string`):

Market to search (mode=search). Singapore and Malaysia use PropertyGuru; Thailand uses DDProperty.

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

Sale or rental listings (mode=search).

## `searchQuery` (type: `string`):

Free-text area, project, MRT station or keyword, for example 'Thomson', 'Mont Kiara' or 'Sukhumvit' (mode=search).

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

Filter by property type (mode=search). Types a market does not have are ignored with a status note.

## `district` (type: `string`):

Filter by Singapore district D01-D28 (mode=search). Ignored for Malaysia and Thailand.

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

Minimum asking price in the market's local currency (SGD, MYR or THB).

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

Maximum asking price in the market's local currency (SGD, MYR or THB).

## `bedrooms` (type: `array`):

Return any of these exact bedroom counts. 0 means studio. Leave empty for all.

## `bathrooms` (type: `array`):

Return any of these exact bathroom counts. Leave empty for all.

## `minSize` (type: `integer`):

Minimum floor area in square feet for Singapore and Malaysia, square metres for Thailand.

## `maxSize` (type: `integer`):

Maximum floor area in square feet for Singapore and Malaysia, square metres for Thailand.

## `tenure` (type: `string`):

Freehold or leasehold (mode=search, sale only).

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

Furnishing level of the property (mode=search).

## `floorLevel` (type: `string`):

Floor level of the unit (mode=search).

## `availability` (type: `string`):

Move-in availability (mode=search, rent only).

## `maxDistanceToMrt` (type: `string`):

Maximum walking distance to an MRT/LRT station (Singapore and Thailand).

## `sort` (type: `string`):

Order of the results (mode=search).

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

Stop after this many listings.

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

Stop after this many search-result pages. The site's own last page also stops the run.

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

Open each listing page for description, agent mobile, coordinates, transit, schools, amenities and media. One extra request per listing.

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

Paste PropertyGuru SG/MY or DDProperty TH search-result, listing or project URLs (mode=byUrl).

## Actor input object example

```json
{
  "mode": "search",
  "country": "SG",
  "listingType": "sale",
  "propertyType": "any",
  "district": "any",
  "bedrooms": [],
  "bathrooms": [],
  "tenure": "any",
  "furnishing": "any",
  "floorLevel": "any",
  "availability": "any",
  "maxDistanceToMrt": "any",
  "sort": "recommended",
  "maxItems": 2,
  "maxPages": 1,
  "includeDetails": false,
  "startUrls": []
}
```

# Actor output Schema

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

Dataset containing all scraped property listings, including enriched detail fields when enabled.

# 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",
    "country": "SG",
    "listingType": "sale",
    "propertyType": "any",
    "district": "any",
    "bedrooms": [],
    "bathrooms": [],
    "tenure": "any",
    "furnishing": "any",
    "floorLevel": "any",
    "availability": "any",
    "maxDistanceToMrt": "any",
    "sort": "recommended",
    "maxItems": 2,
    "maxPages": 1,
    "includeDetails": false,
    "startUrls": []
};

// Run the Actor and wait for it to finish
const run = await client.actor("dataspryva/southeast-asia-property-listings-by-country").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",
    "country": "SG",
    "listingType": "sale",
    "propertyType": "any",
    "district": "any",
    "bedrooms": [],
    "bathrooms": [],
    "tenure": "any",
    "furnishing": "any",
    "floorLevel": "any",
    "availability": "any",
    "maxDistanceToMrt": "any",
    "sort": "recommended",
    "maxItems": 2,
    "maxPages": 1,
    "includeDetails": False,
    "startUrls": [],
}

# Run the Actor and wait for it to finish
run = client.actor("dataspryva/southeast-asia-property-listings-by-country").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",
  "country": "SG",
  "listingType": "sale",
  "propertyType": "any",
  "district": "any",
  "bedrooms": [],
  "bathrooms": [],
  "tenure": "any",
  "furnishing": "any",
  "floorLevel": "any",
  "availability": "any",
  "maxDistanceToMrt": "any",
  "sort": "recommended",
  "maxItems": 2,
  "maxPages": 1,
  "includeDetails": false,
  "startUrls": []
}' |
apify call dataspryva/southeast-asia-property-listings-by-country --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,dataspryva/southeast-asia-property-listings-by-country"
        }
    }
}
```

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/qPkm9zhap3uCleMSt/builds/nYkOHZZmevFgyM1O5/openapi.json
