# HDB Resale Prices Scraper (`scrapyx/hdb-resale-prices-scraper`) Actor

Every registered HDB resale flat transaction since 2017 from HDB's official data: town, flat type, block and street, storey, floor area, model, lease, resale price, price per sqm and sqft. Filter by town, flat type, month and price.

- **URL**: https://apify.com/scrapyx/hdb-resale-prices-scraper.md
- **Developed by:** [Ibnu Adzim](https://apify.com/scrapyx) (community)
- **Categories:** Real estate, Lead generation
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $0.84 / 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

## HDB Resale Prices Scraper (Singapore)

Every **registered HDB resale flat transaction since January 2017**, from
HDB's official open data on data.gov.sg: month, town, flat type, block and
street, storey range, floor area, flat model, lease start, remaining lease
and resale price — plus price per sqm and per sqft, storeys and remaining
lease as numbers.

Filter by town, flat type, month window and price; sort by date or price.
No key, no login, no proxy.

### What it is for

- **Valuing a flat** — what 4-room flats in Bishan actually sold for this
  year, with price per sqft.
- **Market tracking** — monthly volumes and median prices by town and type.
- **Agents and analysts** — a clean, typed table instead of HDB's text
  columns.

### Input

| field | what it does |
| --- | --- |
| `towns` | HDB towns, any case (`bishan`, `Tampines`, `kallang` → KALLANG/WHAMPOA). Empty = all 26. |
| `flatTypes` | `1 ROOM` … `5 ROOM`, `EXECUTIVE`, `MULTI-GENERATION` (`4-room`, `4rm` work too). |
| `monthFrom` / `monthTo` | Registration months, `YYYY-MM`. Default: the last 12 months. |
| `minPrice` / `maxPrice` | Price range in S$. |
| `sortBy` | Newest or oldest month, highest or lowest price. |
| `maxItems` | Default 1,000; `0` = all. |

### Things about this data worth knowing

#### 1. The current month is still filling up

Transactions are listed by the month they were **registered**, and keep
arriving for weeks. On 25 September 2026, September had 1,788 resales
against 2,521 for August. Leave the current month out when comparing
volumes; the summary adds a note when your window includes it.

#### 2. Town and flat-type names must be exact at the source

HDB's data answers `bishan` with zero rows and no error — only `BISHAN`
matches. This Actor normalises what you type and checks it against the 26
towns and 7 flat types, so a typo stops the run with the valid list instead
of returning an empty table.

#### 3. Remaining lease comes in three spellings

`"49 years 10 months"`, `"61 years 01 month"`, `"61 years"`. The Actor
adds `remainingLeaseMonths` and `remainingLeaseYears`, parsed from all three.

#### 4. Only the price is a number at the source

Floor area, storey range and lease start are text. The Actor adds
`floorAreaSqm`, `floorAreaSqft`, `pricePerSqm`, `pricePerSqft`, `storeyMin`,
`storeyMax` and `leaseCommenceYear`, and keeps HDB's original fields beside
them.

#### 5. Price filters

data.gov.sg has no price-range filter, so prices are checked as rows
arrive. Sorted by price, the Actor stops as soon as it passes your bound:
all 1,801 resales of S$1M or more in the last 12 months took one request.

### Output

One `RESALE_TRANSACTION` row per resale and one `SEARCH_SUMMARY` with the
matching total, median price and median price per sqm of the returned rows.

```json
{
  "recordType": "RESALE_TRANSACTION",
  "month": "2026-09",
  "town": "ANG MO KIO",
  "flat_type": "2 ROOM",
  "address": "323 ANG MO KIO AVE 3",
  "storey_range": "10 TO 12",
  "storeyMin": 10,
  "storeyMax": 12,
  "floorAreaSqm": 44.0,
  "floorAreaSqft": 473.6,
  "flat_model": "Improved",
  "leaseCommenceYear": 1977,
  "remaining_lease": "49 years 10 months",
  "remainingLeaseYears": 49.83,
  "resalePrice": 285000.0,
  "pricePerSqm": 6477.27,
  "pricePerSqft": 601.76,
  "currency": "SGD"
}
```

```json
{
  "recordType": "SEARCH_SUMMARY",
  "towns": ["BISHAN", "TAMPINES", "KALLANG/WHAMPOA"],
  "flatTypes": ["4 ROOM", "5 ROOM"],
  "monthFrom": "2025-01",
  "monthTo": "2026-09",
  "matchingTransactions": 3818,
  "returnedCount": 3818,
  "medianPrice": 768000,
  "medianPricePerSqm": 7405.21
}
```

### Speed and limits

- 5,000 transactions per request; data.gov.sg limits anonymous callers, so
  requests are 6 seconds apart (a rate-limit answer is waited out and
  retried). 3,818 rows took 15 seconds.
- Resales before 2017 are in separate HDB datasets and are not included.
- Resale flats only: no rentals, BTO or private property.

# Actor input Schema

## `towns` (type: `array`):

HDB towns, any case: ANG MO KIO, BEDOK, BISHAN, BUKIT BATOK, BUKIT MERAH, BUKIT PANJANG, BUKIT TIMAH, CENTRAL AREA, CHOA CHU KANG, CLEMENTI, GEYLANG, HOUGANG, JURONG EAST, JURONG WEST, KALLANG/WHAMPOA, MARINE PARADE, PASIR RIS, PUNGGOL, QUEENSTOWN, SEMBAWANG, SENGKANG, SERANGOON, TAMPINES, TOA PAYOH, WOODLANDS, YISHUN. Empty = all.

## `flatTypes` (type: `array`):

1 ROOM ... 5 ROOM, EXECUTIVE, MULTI-GENERATION ('4-room', '4rm' also work). Empty = all.

## `monthFrom` (type: `string`):

YYYY-MM (registration month). Default: 12 months ago. Data starts 2017-01.

## `monthTo` (type: `string`):

YYYY-MM. Default: this month (which is still filling up).

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

Applied to rows as they arrive (the API has no price range filter).

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

Applied to rows as they arrive.

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

Order of rows.

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

0 = all matching (all towns and types since 2017 is ~240,000).

## `minRequestInterval` (type: `number`):

data.gov.sg rate-limits anonymous callers; 6 s was measured to avoid it. 5,000 rows per request.

## Actor input object example

```json
{
  "towns": [
    "BISHAN",
    "TAMPINES"
  ],
  "flatTypes": [
    "4 ROOM"
  ],
  "monthFrom": "2026-01",
  "sortBy": "newest",
  "maxItems": 1000,
  "minRequestInterval": 6
}
```

# Actor output Schema

## `items` (type: `string`):

One row per scraped record. See the dataset's default view for field definitions.

# 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 = {
    "towns": [
        "BISHAN",
        "TAMPINES"
    ],
    "flatTypes": [
        "4 ROOM"
    ],
    "monthFrom": "2026-01"
};

// Run the Actor and wait for it to finish
const run = await client.actor("scrapyx/hdb-resale-prices-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 = {
    "towns": [
        "BISHAN",
        "TAMPINES",
    ],
    "flatTypes": ["4 ROOM"],
    "monthFrom": "2026-01",
}

# Run the Actor and wait for it to finish
run = client.actor("scrapyx/hdb-resale-prices-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 '{
  "towns": [
    "BISHAN",
    "TAMPINES"
  ],
  "flatTypes": [
    "4 ROOM"
  ],
  "monthFrom": "2026-01"
}' |
apify call scrapyx/hdb-resale-prices-scraper --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,scrapyx/hdb-resale-prices-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/LlAoUIph1HLitvN2r/builds/H392fpbryEpXnkdJe/openapi.json
