# Property Floorplan Extractor (`rainminer/property-floorplan-extractor`) Actor

Extract room-by-room data from property floorplans with AI vision. Paste a Rightmove or OnTheMarket listing or search URL — or any floorplan image — and get room names, room types, size in m², printed dimensions, storey, bedroom count, total floor area, plus balcony and garden flags.

- **URL**: https://apify.com/rainminer/property-floorplan-extractor.md
- **Developed by:** [rainminer](https://apify.com/rainminer) (community)
- **Categories:** Real estate, AI
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $1.12 / 1,000 floorplan analyzeds

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/platform/actors/running/actors-in-store#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

## Property Floorplan Extractor

The **Property Floorplan Extractor** is an Apify Actor that turns a property listing URL into structured, room-by-room data read straight from the listing's floorplan image. Paste a Rightmove or OnTheMarket link and get back every labelled room with its type, size in m², printed dimensions, and storey — plus bedroom count, total floor area, and balcony/garden flags.

Room-level data is the most valuable information on a property listing and the only part that is never published as text. Agents render it once, into a floorplan drawing, and it stays locked in that image. This Actor reads the drawing with AI vision and gives you the numbers as JSON.

***

### Key Features

- **Listing URL in, rooms out**: Go straight from a live listing page to structured rooms — no manual image hunting, no separate OCR step.
- **Search URLs supported**: Point it at a search results page and it walks the pagination, opening every listing that publishes a floorplan.
- **Per-room measurements**: Room name as printed, room type, area in m², the original dimension text, and the storey each room sits on.
- **Property-level rollups**: `totalRooms`, `bedroomCount`, `totalFloorAreaSqm`, `floors`, `hasBalcony`, and `hasGarden`.
- **Bring your own images**: `floorplanImageUrls` accepts any public floorplan image, so the Actor still works for portals it does not support natively.
- **Cost-aware crawling**: Listings that never uploaded a plan are skipped before they are fetched.
- **No API key required**: AI vision is included — you do not supply or pay for your own model key.
- **No login required**: Works entirely on publicly reachable listing pages.

***

### Why Extract Floorplan Data?

Portals publish a price, a postcode, and a bedroom count. They do not publish the size of the kitchen, whether the third bedroom actually fits a double bed, or how the space is distributed across storeys. That detail exists only inside the floorplan image, which means it is invisible to every filter, model, and spreadsheet downstream.

- **Valuation and AVMs**: Price per m² and room mix are far stronger predictors than bedroom count alone.
- **Property portals and search**: Build filters buyers actually want — "kitchen over 20 m²", "double bedrooms only".
- **Lettings, BTR and HMO analytics**: Check room sizes against licensing minimums across a whole portfolio.
- **Renovation and construction**: Estimate flooring, heating loads, and material quantities from room areas.
- **Proptech and iBuyers**: Normalise heterogeneous listings into a comparable room-level schema.
- **Market research**: Track how room mix and floor area vary by area, price band, and property age.

***

### Who Is It For?

- **Proptech and portal teams** enriching listing databases with room-level attributes.
- **Data scientists and quants** building valuation, AVM, or comparables models.
- **Letting agents and BTR operators** auditing room sizes at scale.
- **Surveyors and renovation companies** pre-qualifying jobs from floor areas.
- **Researchers** studying housing stock, space efficiency, and layout trends.

***

### How It Works

1. You supply Rightmove or OnTheMarket listing URLs, search URLs, or direct floorplan image URLs.
2. Search pages are read and every listing that publishes a floorplan is queued; listings without one are skipped.
3. Each listing page is parsed for its floorplan image and headline metadata (address, price, property type).
4. The floorplan image is downloaded and analyzed by AI vision, which transcribes every labelled room.
5. Areas are taken from the plan where printed, and otherwise derived from the printed dimensions.
6. One dataset row is pushed per floorplan image, with the floorplan set as `imageUrl` so it renders as a thumbnail in the Apify Console.

***

### Supported Sites

| Site | Listing pages | Search pages |
| --- | --- | --- |
| `rightmove.co.uk` | Yes | Yes |
| `onthemarket.com` | Yes | Yes |
| Any public floorplan image | via `floorplanImageUrls` | — |

***

### Input Schema

```json
{
  "startUrls": [
    {
      "url": "https://www.rightmove.co.uk/property-for-sale/find.html?locationIdentifier=REGION%5E87490"
    }
  ],
  "maxItems": 3,
  "maxFloorplansPerListing": 2,
  "proxyConfiguration": { "useApifyProxy": false }
}
```

**All fields are optional, but you must supply either `startUrls` or `floorplanImageUrls`.**

| Field | Type | Default | Description |
| --- | --- | --- | --- |
| `startUrls` | Array of objects | — | Rightmove or OnTheMarket listing or search URLs |
| `floorplanImageUrls` | Array of strings | — | Direct links to floorplan images (JPG, PNG, WebP, GIF) |
| `maxItems` | Integer | `10` | Maximum floorplan results **per start URL** |
| `maxFloorplansPerListing` | Integer | `2` | Maximum floorplan images analyzed per listing |
| `proxyConfiguration` | Object | `{ "useApifyProxy": false }` | Apify Proxy settings; not required |

***

### Output Schema

One dataset item per analyzed floorplan image:

```json
{
  "listingUrl": "https://www.rightmove.co.uk/properties/88326909",
  "portal": "rightmove",
  "listingId": "88326909",
  "address": "Edith Road, West Kensington, London, W14",
  "price": "£500,000",
  "listingBedrooms": 1,
  "propertyType": "Flat",
  "floorplanImageUrl": "https://media.rightmove.co.uk/property-floorplan/7ae619d97/88326909/7ae619d97d270b2117d3ebbecbf6f129.png",
  "imageUrl": "https://media.rightmove.co.uk/property-floorplan/7ae619d97/88326909/7ae619d97d270b2117d3ebbecbf6f129.png",
  "rooms": [
    {
      "name": "RECEPTION ROOM",
      "roomType": "living",
      "areaSqm": 16.7,
      "dimensions": "14'0 x 12'10 4.27m x 3.91m",
      "floor": "First Floor"
    },
    {
      "name": "KITCHEN",
      "roomType": "kitchen",
      "areaSqm": 4.89,
      "dimensions": "9'10 x 5'4 3.00m x 1.63m",
      "floor": "First Floor"
    },
    {
      "name": "BEDROOM",
      "roomType": "bedroom",
      "areaSqm": 15.68,
      "dimensions": "13'2 x 12'10 4.01m x 3.91m",
      "floor": "First Floor"
    },
    {
      "name": "BALCONY",
      "roomType": "outdoor",
      "areaSqm": null,
      "dimensions": null,
      "floor": "First Floor"
    }
  ],
  "totalRooms": 4,
  "bedroomCount": 1,
  "totalFloorAreaSqm": 50.1,
  "floors": 1,
  "hasBalcony": true,
  "hasGarden": false,
  "confidence": "high",
  "scrapedAt": "2026-08-01T07:29:10.423Z"
}
```

#### Field reference

| Field | Type | Description |
| --- | --- | --- |
| `listingUrl` | String | null | The listing page; `null` in direct-image mode |
| `portal` | String | `rightmove`, `onthemarket`, or `direct-image` |
| `listingId` | String | null | Portal listing identifier |
| `address` | String | null | Display address from the listing |
| `price` | String | null | Advertised price as shown |
| `listingBedrooms` | Number | null | Bedrooms advertised by the agent — useful to cross-check `bedroomCount` |
| `propertyType` | String | null | e.g. `Flat`, `Detached`, `Terraced house` |
| `floorplanImageUrl` | String | The analyzed floorplan image |
| `imageUrl` | String | Same image, for Console thumbnail rendering |
| `rooms` | Array | One entry per labelled room (see below) |
| `totalRooms` | Number | `rooms.length` |
| `bedroomCount` | Number | Rooms classified as `bedroom` |
| `totalFloorAreaSqm` | Number | null | Printed total when the plan states one, otherwise the sum of measured rooms |
| `floors` | Number | Distinct storeys on the plan |
| `hasBalcony` | Boolean | A balcony or terrace is drawn on the plan |
| `hasGarden` | Boolean | A garden or private outdoor ground is drawn on the plan |
| `confidence` | String | `high`, `medium`, or `low` — how legible the plan was |
| `scrapedAt` | String | ISO 8601 timestamp |

#### `rooms[]`

| Field | Type | Description |
| --- | --- | --- |
| `name` | String | Room label exactly as printed, in the original language |
| `roomType` | String | `bedroom`, `bathroom`, `kitchen`, `living`, `dining`, `hall`, `storage`, `office`, `garage`, `outdoor`, `other` |
| `areaSqm` | Number | null | Printed area, or derived from the printed dimensions |
| `dimensions` | String | null | Dimension text as printed on the plan |
| `floor` | String | null | Storey heading, e.g. `Ground Floor`, `First Floor` |

***

### Pricing

This Actor uses pay-per-event pricing:

| Event | Charged when |
| --- | --- |
| `listing-scraped` | A listing page is read for floorplan images |
| `floorplan-analyzed` | A floorplan image is sent to the AI vision model |
| `room-extracted` | Per individual room extracted from a floorplan |

Listings without a published floorplan cost only a single `listing-scraped` event, and Rightmove search results are filtered before fetching so plan-less listings are mostly skipped entirely.

***

### Notes and Limitations

- **One row per floorplan image, not per property.** Larger homes often publish a separate plan per storey. Raise `maxFloorplansPerListing` and group rows by `listingId` to assemble the whole property.
- **`bedroomCount` reflects the plan, not the advert.** It counts rooms labelled as bedrooms on the drawing, so it can differ from `listingBedrooms` when a plan labels staff bedrooms, studies, or annexe rooms that the agent does not market. Compare the two fields to spot these cases.
- **Only labelled rooms are returned.** Unlabelled spaces are deliberately not invented, so a plan that draws a bathroom without naming it will not produce a `bathroom` row.
- **Areas are as accurate as the plan.** Floorplans are marketing drawings; agents state that measurements are approximate. Rooms with no printed dimensions return `areaSqm: null`.
- **`totalFloorAreaSqm` falls back to a sum.** When the plan prints no gross internal area, the value is the sum of measured rooms, which excludes walls and unlabelled space and so reads low.
- **Check `confidence`.** Low-resolution, rotated, or heavily compressed plans return `low`; filter on this field for high-precision pipelines.
- **Public data only.** No login, account, or user-supplied credentials are involved.

***

### Image Credit

Image credit: [rightmove.co.uk](https://www.rightmove.co.uk/) and [onthemarket.com](https://www.onthemarket.com/)

# Actor input Schema

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

Rightmove or OnTheMarket property URLs. Both individual listing pages (e.g. https://www.rightmove.co.uk/properties/152205953) and search pages (e.g. https://www.rightmove.co.uk/property-for-sale/find.html?locationIdentifier=REGION%5E87490) are supported.

## `floorplanImageUrls` (type: `array`):

Direct links to floorplan images (JPG, PNG, WebP or GIF). Each image is analyzed on its own and produces one dataset row.

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

Maximum number of floorplan results to output for each start URL. Keep this small for test runs.

## `maxFloorplansPerListing` (type: `integer`):

Maximum floorplan images to analyze per listing. Houses often publish one plan per storey — raise this to capture all of them.

## `proxyConfiguration` (type: `object`):

Apify Proxy settings used for listing pages and floorplan image downloads.

## Actor input object example

```json
{
  "startUrls": [
    {
      "url": "https://www.rightmove.co.uk/property-for-sale/find.html?locationIdentifier=REGION%5E87490"
    }
  ],
  "maxItems": 3,
  "maxFloorplansPerListing": 2,
  "proxyConfiguration": {
    "useApifyProxy": false
  }
}
```

# Actor output Schema

## `overview` (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 = {
    "startUrls": [
        {
            "url": "https://www.rightmove.co.uk/property-for-sale/find.html?locationIdentifier=REGION%5E87490"
        }
    ],
    "maxItems": 3,
    "maxFloorplansPerListing": 2,
    "proxyConfiguration": {
        "useApifyProxy": false
    }
};

// Run the Actor and wait for it to finish
const run = await client.actor("rainminer/property-floorplan-extractor").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 = {
    "startUrls": [{ "url": "https://www.rightmove.co.uk/property-for-sale/find.html?locationIdentifier=REGION%5E87490" }],
    "maxItems": 3,
    "maxFloorplansPerListing": 2,
    "proxyConfiguration": { "useApifyProxy": False },
}

# Run the Actor and wait for it to finish
run = client.actor("rainminer/property-floorplan-extractor").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 '{
  "startUrls": [
    {
      "url": "https://www.rightmove.co.uk/property-for-sale/find.html?locationIdentifier=REGION%5E87490"
    }
  ],
  "maxItems": 3,
  "maxFloorplansPerListing": 2,
  "proxyConfiguration": {
    "useApifyProxy": false
  }
}' |
apify call rainminer/property-floorplan-extractor --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,rainminer/property-floorplan-extractor"
        }
    }
}

```

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/wL6hr2SMQ4Dw17LTh/builds/goz5RAFwNepeBvWQ6/openapi.json
