# OnTheMarket Scraper - UK Property Listings & Agent Contacts (`abotapi/onthemarket-scraper`) Actor

Scrape onthemarket.com for-sale, to-rent and new-homes listings with 85+ fields: price, address & geo, bedrooms/bathrooms, full description, key info, nearest stations & schools, area stats, floorplans, virtual tours and complete agent contacts. Search by location + filters or paste URLs.

- **URL**: https://apify.com/abotapi/onthemarket-scraper.md
- **Developed by:** [Abot API](https://apify.com/abotapi) (community)
- **Categories:** Real estate
- **Features:** Limited permissions
- **Stats:** 1 total users, 0 monthly users, 100.0% runs succeeded, 1 bookmarks
- **User rating**: No ratings yet

## Pricing

from $1.00 / 1,000 property listings

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

## OnTheMarket Scraper: UK Property Listings, Prices and Agent Contacts

OnTheMarket Scraper turns onthemarket.com into a clean UK property data feed. Get for-sale, to-rent and new-homes listings with prices in GBP, full address and geolocation, bedrooms and bathrooms, property features and media, and complete estate-agent contact details. Search by location and filters, or paste your own search or property URLs, then turn on full property details for descriptions, key info, floorplans, nearby stations and schools, and area stats. Export the dataset to JSON, CSV or Excel, or read it straight through the API.

### Why This Scraper?

- **Two ways to find properties.** Search by location plus filters, or paste OnTheMarket search-result or property-details URLs. No need to build search URLs by hand.
- **Three channels in one actor.** For-sale, to-rent and new-homes listings, all read from the same search and detail pages.
- **Rich fields, two depths.** Every search card carries about 28 fields; turn on full property details to expand each record to 85+ fields.
- **Direct agent contacts.** Agent name, company, phone, website, contact URL and address are captured straight from the listing, never inferred or guessed.
- **Search-side filters.** Price, bedrooms, property type, radius and sort order, plus an include-under-offer / SSTC toggle, all applied as OnTheMarket's own query parameters so the site does the narrowing.
- **UK-specific detail.** EPC energy rating, tenure, council tax band, stamp duty label, and nearby stations and schools are captured on enriched records.
- **A steady connection.** The run rotates its connection automatically if a request is refused, so a large search keeps going without manual intervention.

### Use Cases

- **Property portals and lead tools:** mirror OnTheMarket's for-sale, to-rent and new-homes inventory into your own site, app or feed.
- **Estate agent directories and prospecting:** build a list of active agents, their listings and direct contact details by area.
- **Market and pricing research:** track asking prices, price qualifiers and stock levels across UK towns, postcodes and property types.
- **House-hunting and relocation tools:** feed a personal search (location, budget, bedrooms, property type) into a spreadsheet, bot or notification app.
- **Academic and planning research:** study housing supply, property mix and new-build activity across regions.

### Data You Get

> Sample shape: values are illustrative placeholders, not from a live record.

| Field | Example |
| --- | --- |
| `id` | `"12345678"` |
| `url` | OnTheMarket link to the property |
| `channel` | `"FOR-SALE"` (also `"TO-RENT"`, `"NEW-HOMES"`) |
| `propertyTitle` | `"3 bedroom detached house for sale"` |
| `humanisedPropertyType` | `"Detached house"` |
| `displayAddress` | `"42 Sample Road, Sampleton, London, SW0 0AA"` |
| `bedrooms` / `bathrooms` | `3` / `2` |
| `displayPrice` | `"£450,000"` |
| `price` | `450000` |
| `priceQualifier` | `"Guide price"` |
| `latitude` / `longitude` | `51.5074` / `-0.1278` |
| `mainLabel` | `"New"` |
| `daysSinceAddedReduced` | `"Added today"` |
| `features` | `["Detached", "Off-street parking", "Garden"]` |
| `images` | array of listing photo URLs |
| `agentName` / `agentId` | `"Sample Estates"` / `4321` (plus `shortPrice` and `agentLogo`) |
| `feesLabel` / `feesDescription` | tenancy or fee information text on the card (rentals) |
| `sourceUrl` | the results page the listing was read from |
| `seedType` / `seedValue` | what started the row: `"location"` with the location text, or `"url"` with the start URL |
| `rawAgent` | the complete verbatim agent object from the listing |
| `detailScraped` | `true` when full property details were fetched for this row |
| `description` | full listing description text (when full details are on) |
| `keyInfo` | tenure, council tax band, EPC rating and similar facts (when full details are on) |
| `station` / `school` | nearby stations / schools with distance (when full details are on) |
| `agentPhone` / `agentWebsiteUrl` / `agentContactUrl` | direct agent phone, website and contact-page link (when full details are on) |

With **Include full property details** on, records also carry `summary`, `rooms`, `priceRaw`, `stampDutyLabel`, `floorplans`, `videos`, `virtualtours`, `minimumAreaSqFt` / `minimumAreaSqM`, `areaStats`, `poi`, `canonicalUrl`, `agentCompanyName`, `agentPhoneEnquiries`, `agentAddress`, `agentPostcode` and `agentBranchId`.

### How to Use

1. Pick a **mode**: search by location and filters, or paste URLs.
2. For search mode, choose a **channel** and one or more **locations**, then narrow with price, bedrooms, property type, radius or sort order.
3. Turn on **Include full property details** if you need descriptions, key info, floorplans, nearby stations and schools, and full agent contacts.
4. Set **Max items** (and, if needed, **Max pages**) to control run size and cost, then click **Start**.

**Houses for sale in London, mid-range price band:**

```json
{
  "mode": "search",
  "channel": "FOR-SALE",
  "locations": ["London"],
  "minPrice": 500000,
  "maxPrice": 1000000,
  "propertyTypes": ["houses"],
  "sortType": "price-asc",
  "maxItems": 50
}
```

**Two to three bedroom rentals in Manchester, with full details:**

```json
{
  "mode": "search",
  "channel": "TO-RENT",
  "locations": ["Manchester"],
  "minBedrooms": 2,
  "maxBedrooms": 3,
  "includeDetails": true,
  "maxItems": 30
}
```

**Pasted Start URLs in search mode.** If you paste your own **Start URLs** but leave **Mode** on Search and **Locations** at its London default (or empty), the run switches to URL mode and scrapes your URLs, so you are not billed for London listings you did not ask for. If you changed **Locations**, Search mode wins and the pasted URLs are ignored with a warning in the run log; set **Mode** to URL to scrape them.

**URL mode left unedited.** If you switch to URL mode and leave **Start URLs** as it is, the run now scrapes one example of each supported URL shape (sale, filtered and sorted, page 2 of a search, to-rent, new homes by path or by flag, postcode, a property page with or without the trailing slash, a place landing page, an agent branch and an agents directory), up to **Max items**. Replace the list with your own links for a real run. Filters, sort and page in a pasted URL are kept as written.

**Agent pages.** An agent branch page returns one agent record and an agents directory returns one record per agent, walking its pages up to **Max items** or **Max pages**. Listing rows carry `recordType: "property"` and agent records carry `recordType: "agent"`, are billed like any other delivered dataset row, and are not part of incremental change tracking.

**Paste a search page and a property page together:**

```json
{
  "mode": "url",
  "startUrls": [
    "https://www.onthemarket.com/for-sale/property/bath/",
    "https://www.onthemarket.com/details/12345678/"
  ],
  "includeDetails": true,
  "maxItems": 40
}
```

**New homes near a postcode area, including under offer:**

```json
{
  "mode": "search",
  "channel": "NEW-HOMES",
  "locations": ["SW19"],
  "radius": "5",
  "includeUnderOffer": true,
  "maxItems": 25
}
```

#### Run it from your code

Python:

```python
from apify_client import ApifyClient

client = ApifyClient("<YOUR_APIFY_TOKEN>")
run = client.actor("abotapi/onthemarket-scraper").call(run_input={
    "mode": "search",
    "channel": "FOR-SALE",
    "locations": ["London"],
    "maxItems": 20,
})
for listing in client.dataset(run["defaultDatasetId"]).iterate_items():
    print(listing["displayAddress"], listing["displayPrice"])
```

JavaScript:

```javascript
import { ApifyClient } from 'apify-client';

const client = new ApifyClient({ token: '<YOUR_APIFY_TOKEN>' });
const run = await client.actor('abotapi/onthemarket-scraper').call({
  mode: 'search',
  channel: 'FOR-SALE',
  locations: ['London'],
  maxItems: 20,
});
const { items } = await client.dataset(run.defaultDatasetId).listItems();
```

Or connect it to Make, Zapier, n8n, Google Sheets or webhooks from the **Integrations** tab.

#### How Max items and Max pages interact

By default there is no page limit: the run walks forward through OnTheMarket's result pages until **Max items** is reached or the site stops returning a next page. Set **Max pages** only if you want a hard ceiling on how many pages a single location or pasted URL walks, independent of Max items.

#### Incremental mode (recurring monitoring)

Turn on **Incremental mode** to run the same search on a schedule and get only what changed. The first run returns every matching listing as `NEW`. Later runs compare each listing with the last run and return only `NEW`, `UPDATED` and `REAPPEARED` listings. Each row carries `changeType`, `changedFields` (for `UPDATED`, the fields that moved), `firstSeenAt` and `lastSeenAt`.

- The days-since-added counter, the age and promotion labels (`mainLabel`, `dataLabelId`, `primaryPropertyLabel`, `propertySticker`, `premium`, `spotlight`), image lists, area statistics, the view counter and the similar-property lists stay in the output but are never compared, so a listing cannot turn into `UPDATED` just because it got older ("Added today" to "Recently added") or its promotion slot changed. Add more with **Ignore fields when detecting changes**.
- State is kept per search setup (locations or URLs, channel, filters, details on or off) or under your **State key**.
- **Emit unchanged** and **Emit expired** add `UNCHANGED` and `EXPIRED` rows. Billing: every row the run returns is stored in the default dataset and billed through the same per-listing event as a normal run (plus the detail event when details are on), so `UNCHANGED` and `EXPIRED` rows you ask for are billed like any other returned row. Listings that are suppressed because they did not change are not returned and not billed, so leave both switches off to pay only for `NEW`, `UPDATED` and `REAPPEARED` rows.
- **Max items** counts every returned row (including `UNCHANGED` and `EXPIRED` rows when switched on); suppressed unchanged listings use no slot. A listing is only remembered as delivered once its row was really stored: rows cut by Max items or the maximum cost limit are not saved to the baseline, so the next run returns them again. A run cut short by Max items, Max pages, Resume, a failed page, a single listing URL or a results page opened past its first page never marks anything `EXPIRED`.
- With **Include full property details** on, a listing whose details page could not be loaded this run is left as it was, not reported as changed.

#### Searching more than one location or URL in one run

Within a single run, a property that turns up again under a later location or pasted URL is only returned, and billed, once. Genuinely different listings found afterwards are not affected, so an overlapping search area does not cost you extra rows or lose the rest of that page's results.

### Send results into your apps (MCP connectors)

Optionally pipe the scraped results into the apps you already use, via Model Context Protocol (MCP) connectors. This is an extra delivery step **after** the scrape: the Apify dataset is never changed, and only rows delivered to the dataset are sent.

**What gets written to the connector:** a condensed, human-readable **summary** of each record, not the full JSON. The **complete record always stays in the Apify dataset**.

1. Authorize a connector once under **Apify → Settings → Integrations** (Notion, Linear, Airtable, or Apify).
2. Select it in the `mcpConnectors` field ("Pipe results into your apps"). If the picker is empty, you have not authorized a connector yet.
3. For **Notion**, also set `notionParentPageUrl` to the page where items should be created.
4. `maxNotifyListings` caps how many items are written to each connector per run.

The connection is mediated by Apify's MCP proxy, so this actor never sees your third-party credentials. Leave the field empty to skip.

### Input Parameters

| Parameter | Type | Default | Description |
| --- | --- | --- | --- |
| `mode` | string | `search` | `search` (location + filters) or `url` (paste links). |
| `channel` | string | `FOR-SALE` | Which listings to scrape (search mode): `FOR-SALE`, `TO-RENT`, or `NEW-HOMES`. |
| `locations` | array | `["London"]` | Towns, cities, postcodes or areas to search (search mode). If left empty in search mode, the run searches London. |
| `startUrls` | array | one example of each URL shape | URL mode only: OnTheMarket search-result URLs (sale, to-rent, new homes, postcode, with filters, sort and page), `/details/{id}/` URLs, `/property/{place}/` landing pages (read as the for-sale search for that place), agent branch pages and agents directories. |
| `minPrice` | integer | (none) | Lowest price: sale price, or monthly rent for to-rent. |
| `maxPrice` | integer | (none) | Highest price: sale price, or monthly rent for to-rent. |
| `minBedrooms` | integer | (none) | Fewest bedrooms. |
| `maxBedrooms` | integer | (none) | Most bedrooms. |
| `propertyTypes` | array | (none) | Filter by property type. Includes `flats-apartments` (flat + apartment) and `mobile-park-homes`. Leave empty for all types. |
| `radius` | string | (none) | Expand the search around the location, from a quarter mile up to 40 miles. |
| `sortType` | string | `recommended` | `recommended`, `price-asc`, or `price-desc`. |
| `includeUnderOffer` | boolean | `false` | Also include listings that are under offer or sold subject to contract. |
| `addedToSite` | string | none | Only listings added in the last `24-hours`, `3-days` or `7-days`. |
| `includeDetails` | boolean | `false` | Fetch each property's details page for the full description, key info, floorplans, nearby stations and schools, area stats, and complete agent contact details. |
| `maxItems` | integer | `20` | Maximum number of listings to return (the run cap). Set to `0` for no item limit. |
| `maxPages` | integer | `0` | Cap on how many result pages are walked. `0` means no page limit (stops at Max items). |
| `proxyConfiguration` | object | `{"useApifyProxy": true}` | Proxy settings for the run. Runs on any Apify plan. |
| `resumeFromRunId` | string | (none) | Resume: ID of a previous run (or dataset) of this actor. Listings and agent records already in that dataset are skipped, so the run returns only the rows not collected yet. |
| `incrementalMode` | boolean | `false` | Recurring monitoring: first run returns everything as NEW, later runs return only NEW, UPDATED and REAPPEARED listings. |
| `stateKey` | string | (none) | Optional name for a monitoring campaign (incremental mode only). Empty derives a key from the search settings. |
| `emitUnchanged` | boolean | `false` | Also return listings that did not change, marked UNCHANGED (billed like any returned row, counted in Max items). |
| `emitExpired` | boolean | `false` | Also return listings no longer found after a complete scan, marked EXPIRED (billed like any returned row, counted in Max items). |
| `ignoreFieldsForChanges` | array | (none) | Extra top-level field names to leave out of change detection (incremental mode only). |
| `mcpConnectors` | array | (none) | Optional MCP connectors (Notion, Linear, Airtable, Apify) that receive a summary of each delivered row. |
| `notionParentPageUrl` | string | (none) | Notion page under which item pages are created (Notion connector only). |
| `maxNotifyListings` | integer | `50` | Cap on items written to each connector per run. Does not affect the dataset. |

### Output Example

> Sample shape: values are illustrative placeholders, not from a live record.

```json
{
  "id": "12345678",
  "url": "https://www.onthemarket.com/details/12345678/",
  "channel": "FOR-SALE",
  "propertyTitle": "3 bedroom detached house for sale",
  "humanisedPropertyType": "Detached house",
  "bedrooms": 3,
  "bathrooms": 2,
  "displayPrice": "£450,000",
  "price": 450000,
  "priceQualifier": "Guide price",
  "displayAddress": "42 Sample Road, Sampleton, London, SW0 0AA",
  "latitude": 51.5074,
  "longitude": -0.1278,
  "mainLabel": "New",
  "daysSinceAddedReduced": "Added today",
  "features": ["Detached", "Off-street parking", "Garden"],
  "images": ["https://media.onthemarket.com/sample/1.jpg", "https://media.onthemarket.com/sample/2.jpg"],
  "description": "A well presented three bedroom detached house close to local amenities and transport links.",
  "keyInfo": ["Tenure: Freehold", "Council tax band: D", "EPC rating: C"],
  "station": [{ "name": "Sample Park", "distanceMiles": 0.4 }],
  "school": [{ "name": "Sample Primary School", "distanceMiles": 0.3 }],
  "agentName": "Sample Estates",
  "agentCompanyName": "Sample Estates Ltd",
  "agentPhone": "01632 960001",
  "agentWebsiteUrl": "https://www.sample-estates.example",
  "agentContactUrl": "https://www.onthemarket.com/agents/sample-estates/contact/",
  "agentAddress": "1 Sample Parade, Sampleton, SW0 0AA",
  "rawAgent": { "id": 4321, "name": "Sample Estates" },
  "detailScraped": true
}
```

`keyInfo`, `station`, `school`, `areaStats`, `poi` and `rawAgent` are passed through as OnTheMarket publishes them, so their inner structure follows the site and the nested values above are only a sketch.

### Plan Requirement

The default proxy setting (datacenter) returned pages in our checks, so the run works out of the box on any Apify plan. If a request is refused, the run rotates its connection automatically. For large or frequent searches, the residential proxy group gives more headroom; pick it under Connection. If every request is refused for the whole run, the run fails with a clear message instead of finishing empty (see the FAQ).

### FAQ

#### How much does it cost?

Billing uses three events: one per delivered dataset row (listing rows and agent rows alike), one per listing enriched with **Include full property details**, and one per-run start. Rows that are not delivered (unchanged listings suppressed by Incremental mode, or failed pages) are not billed; every row that is delivered is billed, including `UNCHANGED` and `EXPIRED` rows when you switch them on. The **Pricing** tab shows the current rates. Use **Max items** to cap the cost of any run.

#### Is it legal to scrape OnTheMarket?

This actor collects only publicly available property listing data as published on OnTheMarket. You are responsible for how you use it: follow OnTheMarket's terms and the laws that apply to you, including data-protection rules where you use agent contact details for outreach, and get legal advice if you plan commercial redistribution.

#### Can I get only new listings on a schedule?

Yes. Turn on **Incremental mode** (see above) and schedule the run: each run returns only new, updated or reappeared listings, and you are billed only for the rows it returns. To continue one specific interrupted run instead, paste its run ID into **Resume from a previous run**.

#### Why did a run return fewer listings than Max items?

A few honest reasons: **Max pages** stopped the page walk before Max items was reached; the search area genuinely has fewer results than Max items; or OnTheMarket did not recognise a location string, so the run tried a best-guess area name built from your text, and when that page did not exist the location was skipped with a note in the run log. Use the place names OnTheMarket's own search box suggests to avoid this. A listing that appears again under a later location or URL in the same run is only counted once, not treated as new.

#### Why did my run fail with "Could not load any results page"?

If every request to OnTheMarket is refused and no page is retrieved at all, the run ends as **Failed** with the message "Could not load any results page (the requests failed), so nothing was collected. This is a connection problem, not an empty search. Please try again later." It is not reported as an empty success, and nothing is billed for rows that were never delivered. The run summary saved as `OUTPUT` in the key-value store shows `requests`, `blocks` and `rotations`. Retry later or choose the residential proxy group under Connection.

A search that genuinely has no matches, or a page OnTheMarket reports as not found (HTTP 404), is different: the run finishes normally with zero items and a "No results" note in the log. Open the same search on onthemarket.com to confirm it really has results.

#### Does this include estate agent reviews or ratings?

No. OnTheMarket does not publish estate-agent reviews, ratings or testimonials, so none are collected. The EPC energy rating shown in `keyInfo` is a property attribute, not an agent rating.

#### Can I use it with AI agents or automation platforms?

Yes. Call it from the API, the Scheduler, or any Apify integration (Make, Zapier, n8n, Google Sheets, webhooks), or from an MCP client through Apify's own MCP server.

### 🔗 Want more real estate data?

Pair this actor with these related scrapers from the same team:

<table>
<tr><td>🏠 <a href="https://apify.com/abotapi/rightmove-scraper"><b>Rightmove</b></a><br>Fast, reliable Rightmove.co.uk scraper for sale, rent, and sold-price listings. Search by...</td><td>🏠 <a href="https://apify.com/abotapi/propertyguru-sg-scraper"><b>PropertyGuru SG Scraper</b></a><br>Scrape PropertyGuru.com.sg sale and rental listings with 30+ structured fields, including...</td></tr>
<tr><td>🏠 <a href="https://apify.com/abotapi/jitty-uk-scraper"><b>Jitty Scraper</b></a><br>Scrape residential property listings from Jitty.com across the UK. Search with 20+...</td><td>🏠 <a href="https://apify.com/abotapi/homes-co-nz-scraper"><b>Homes.co.nz Scraper</b></a><br>Scrape Homes.co.nz listings by location or URL with pagination. Extract property details...</td></tr>
<tr><td>🏠 <a href="https://apify.com/abotapi/apartments-com-scraper"><b>Apartments.com Scraper</b></a><br>Scrape Apartments.com rentals by city, ZIP, neighborhood, address or URL. Extract rent...</td><td>🤖 <a href="https://apify.com/abotapi/property-au-scraper"><b>AU Residential Property Scraper</b></a><br>Scrape Australian real estate listings with detailed property information. Extract...</td></tr>
</table>

👉 [Browse all abotapi scrapers](https://apify.com/abotapi)

### 💬 Support

- 🐞 **Found a bug or a missing field?** Open a ticket on the [Issues tab](https://apify.com/abotapi/onthemarket-scraper/issues/open). We usually reply within hours.
- 🛠️ **Need another site, extra fields or a private build?** Email <contact@abotapi.com>.
- ⭐ **Enjoying it?** A quick review on the actor page helps other users find it.

# Actor input Schema

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

How to start the scrape. 'Search' builds result pages from a location + filters. 'URL list' scrapes the search or property-details URLs you paste.

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

Which listings to scrape (search mode).

## `locations` (type: `array`):

One or more places to search - a town, city, postcode or area (e.g. 'London', 'Manchester', 'SW19'). Each is resolved to its OnTheMarket search area.

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

URL mode only. Paste OnTheMarket search-result URLs (sale, to-rent, new homes, postcode, with filters, sort and page), property-details URLs, a place landing page (/property/london/), an agent branch page or an agents directory. Left unedited it scrapes one example of each shape. If you paste your own URLs but leave Mode on Search and Locations at London, the run switches to URL mode; if you changed Locations, Search mode ignores these URLs (warning in the log).

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

Lowest price (sale price, or monthly rent for to-rent).

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

Highest price (sale price, or monthly rent for to-rent).

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

Fewest bedrooms.

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

Most bedrooms.

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

Filter by property type. Leave empty for all types.

## `radius` (type: `string`):

Expand the search around the location by this many miles. Works with town / postcode locations.

## `sortType` (type: `string`):

How to order results.

## `includeUnderOffer` (type: `boolean`):

Also include listings that are under offer or sold subject to contract.

## `addedToSite` (type: `string`):

Only listings added within this window (24 hours, 3 days or 7 days). Leave empty for all.

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

Fetch each property's details page for the full description, key info, floorplans, nearby stations & schools, area stats, and complete agent contact details. Slower and adds a per-record detail charge.

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

Maximum number of listings to return. This is the run cap. Set to 0 for no item limit (stops when Max pages is reached).

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

No page limit by default - the run stops at Max items. Set a value only to cap how many result pages are walked.

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

Proxy settings for the run. Runs on any Apify plan.

## `resumeFromRunId` (type: `string`):

Optional. ID of a previous run of this actor (or a dataset ID). Listings and agent records already in that dataset are skipped, so this run returns only listings not collected yet. Max items then counts only the new listings. For recurring monitoring of the same search, use Incremental mode below instead.

## `incrementalMode` (type: `boolean`):

Turn this on for daily or recurring monitoring. The first run returns all matching listings as NEW. Later runs compare every listing against the last run (price, qualifier, bedrooms, bathrooms, title, description, features, agent details, labels and the other listing fields) and return only NEW, UPDATED and REAPPEARED listings, with an UPDATED row's changedFields naming exactly what moved. The days-since-added counter, the age and promotion labels (main label, label id, sticker, premium, spotlight), image lists, area statistics, view counter and similar-property lists are never compared, since they change on their own. Every returned row is billed as a dataset item; unchanged listings are suppressed (not returned, not billed) unless you turn on Emit unchanged. Max items counts returned rows. A listing is remembered as delivered only once its row was stored, so rows cut by Max items or the maximum cost limit come back on the next run. State is kept separately for each search setup (locations or URLs, channel, filters, details on or off). To continue one specific interrupted run instead, use Resume above.

## `stateKey` (type: `string`):

Optional. Name this monitoring campaign to keep its state stable, or to deliberately share state across differently configured runs. Leave empty to derive a key automatically from the search settings.

## `emitUnchanged` (type: `boolean`):

Off by default. Turn on to also return listings that have not changed since the last run, marked UNCHANGED. These rows are returned in the dataset, so they are billed like any other returned row and count toward Max items.

## `emitExpired` (type: `boolean`):

Off by default. Turn on to also return listings that were present in a previous run but are no longer found, marked EXPIRED. Only produced after a complete scan of the tracked search, never when Max items, Max pages, a single listing URL or Resume cut it short. These rows are returned in the dataset, so they are billed like any other returned row and count toward Max items.

## `ignoreFieldsForChanges` (type: `array`):

Optional. Top-level field names to exclude from UPDATED change detection, in addition to the built-in handling (days since added, age and promotion labels, images, area statistics, view counter and similar-property lists never trigger an UPDATED). Use this if another field turns out to be noisy in your setup.

## `mcpConnectors` (type: `array`):

Optionally send the scraped results into the apps you already use, via Model Context Protocol (MCP) connectors. Authorize a connector once under Apify → Settings → Integrations, then select it here. The connector receives a condensed, human-readable summary per item (title + key fields), not the full JSON — the complete record stays in the dataset. Leave empty to skip. Supported: Notion (https://mcp.notion.com/mcp), Linear (https://mcp.linear.app/sse), Airtable (https://mcp.airtable.com/mcp), Apify (https://mcp.apify.com).

## `notionParentPageUrl` (type: `string`):

URL (or id) of the Notion page under which item pages are created. Required to enable the Notion export; ignored by other connectors.

## `maxNotifyListings` (type: `integer`):

Cap on items written to each connector per run. Does not affect the dataset.

## Actor input object example

```json
{
  "mode": "search",
  "channel": "FOR-SALE",
  "locations": [
    "London"
  ],
  "startUrls": [
    {
      "url": "https://www.onthemarket.com/for-sale/property/london/"
    },
    {
      "url": "https://www.onthemarket.com/for-sale/property/london/?page=2"
    },
    {
      "url": "https://www.onthemarket.com/for-sale/property/london/?sort-field=price&sort-order=asc&page=2"
    },
    {
      "url": "https://www.onthemarket.com/for-sale/2-bed-flats-apartments/london/?min-price=300000&max-price=900000&sort-field=price&radius=3"
    },
    {
      "url": "https://www.onthemarket.com/for-sale/detached/london/"
    },
    {
      "url": "https://www.onthemarket.com/for-sale/property/sw19/"
    },
    {
      "url": "https://www.onthemarket.com/to-rent/property/manchester/"
    },
    {
      "url": "https://www.onthemarket.com/to-rent/property/london/?page=2"
    },
    {
      "url": "https://www.onthemarket.com/new-homes/property/london/"
    },
    {
      "url": "https://www.onthemarket.com/for-sale/property/london/?new-homes=true"
    },
    {
      "url": "https://www.onthemarket.com/details/20490019/"
    },
    {
      "url": "https://www.onthemarket.com/details/20622528"
    },
    {
      "url": "https://www.onthemarket.com/property/london/"
    },
    {
      "url": "https://www.onthemarket.com/agents/branch/1-clickproperties-acton/"
    },
    {
      "url": "https://www.onthemarket.com/agents/london/"
    }
  ],
  "sortType": "recommended",
  "includeUnderOffer": false,
  "includeDetails": false,
  "maxItems": 20,
  "maxPages": 0,
  "proxyConfiguration": {
    "useApifyProxy": true
  },
  "incrementalMode": false,
  "emitUnchanged": false,
  "emitExpired": false,
  "ignoreFieldsForChanges": [],
  "maxNotifyListings": 50
}
```

# Actor output Schema

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

Individual property records. Search cards carry ~28 fields; with 'Include full property details' enabled each record carries 85+ fields including full description, key info, nearest stations & schools, area stats, floorplans, virtual tours and complete agent contact details (phone, website, contact URL, address).

## `summary` (type: `string`):

Run statistics: total items, pages fetched, details fetched, blocks, proxy-tier health, duration and throughput.

## `search_cards` (type: `string`):

Search cards view of the dataset.

## `detailed` (type: `string`):

Detailed view of the dataset.

## `agent_contacts` (type: `string`):

Agent contacts view of the dataset.

## `changes` (type: `string`):

Changes view of the dataset.

# 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",
    "locations": [
        "London"
    ],
    "startUrls": [
        {
            "url": "https://www.onthemarket.com/for-sale/property/london/"
        },
        {
            "url": "https://www.onthemarket.com/for-sale/property/london/?page=2"
        },
        {
            "url": "https://www.onthemarket.com/for-sale/property/london/?sort-field=price&sort-order=asc&page=2"
        },
        {
            "url": "https://www.onthemarket.com/for-sale/2-bed-flats-apartments/london/?min-price=300000&max-price=900000&sort-field=price&radius=3"
        },
        {
            "url": "https://www.onthemarket.com/for-sale/detached/london/"
        },
        {
            "url": "https://www.onthemarket.com/for-sale/property/sw19/"
        },
        {
            "url": "https://www.onthemarket.com/to-rent/property/manchester/"
        },
        {
            "url": "https://www.onthemarket.com/to-rent/property/london/?page=2"
        },
        {
            "url": "https://www.onthemarket.com/new-homes/property/london/"
        },
        {
            "url": "https://www.onthemarket.com/for-sale/property/london/?new-homes=true"
        },
        {
            "url": "https://www.onthemarket.com/details/20490019/"
        },
        {
            "url": "https://www.onthemarket.com/details/20622528"
        },
        {
            "url": "https://www.onthemarket.com/property/london/"
        },
        {
            "url": "https://www.onthemarket.com/agents/branch/1-clickproperties-acton/"
        },
        {
            "url": "https://www.onthemarket.com/agents/london/"
        }
    ],
    "incrementalMode": false,
    "emitUnchanged": false,
    "emitExpired": false,
    "ignoreFieldsForChanges": []
};

// Run the Actor and wait for it to finish
const run = await client.actor("abotapi/onthemarket-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 = {
    "mode": "search",
    "locations": ["London"],
    "startUrls": [
        { "url": "https://www.onthemarket.com/for-sale/property/london/" },
        { "url": "https://www.onthemarket.com/for-sale/property/london/?page=2" },
        { "url": "https://www.onthemarket.com/for-sale/property/london/?sort-field=price&sort-order=asc&page=2" },
        { "url": "https://www.onthemarket.com/for-sale/2-bed-flats-apartments/london/?min-price=300000&max-price=900000&sort-field=price&radius=3" },
        { "url": "https://www.onthemarket.com/for-sale/detached/london/" },
        { "url": "https://www.onthemarket.com/for-sale/property/sw19/" },
        { "url": "https://www.onthemarket.com/to-rent/property/manchester/" },
        { "url": "https://www.onthemarket.com/to-rent/property/london/?page=2" },
        { "url": "https://www.onthemarket.com/new-homes/property/london/" },
        { "url": "https://www.onthemarket.com/for-sale/property/london/?new-homes=true" },
        { "url": "https://www.onthemarket.com/details/20490019/" },
        { "url": "https://www.onthemarket.com/details/20622528" },
        { "url": "https://www.onthemarket.com/property/london/" },
        { "url": "https://www.onthemarket.com/agents/branch/1-clickproperties-acton/" },
        { "url": "https://www.onthemarket.com/agents/london/" },
    ],
    "incrementalMode": False,
    "emitUnchanged": False,
    "emitExpired": False,
    "ignoreFieldsForChanges": [],
}

# Run the Actor and wait for it to finish
run = client.actor("abotapi/onthemarket-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 '{
  "mode": "search",
  "locations": [
    "London"
  ],
  "startUrls": [
    {
      "url": "https://www.onthemarket.com/for-sale/property/london/"
    },
    {
      "url": "https://www.onthemarket.com/for-sale/property/london/?page=2"
    },
    {
      "url": "https://www.onthemarket.com/for-sale/property/london/?sort-field=price&sort-order=asc&page=2"
    },
    {
      "url": "https://www.onthemarket.com/for-sale/2-bed-flats-apartments/london/?min-price=300000&max-price=900000&sort-field=price&radius=3"
    },
    {
      "url": "https://www.onthemarket.com/for-sale/detached/london/"
    },
    {
      "url": "https://www.onthemarket.com/for-sale/property/sw19/"
    },
    {
      "url": "https://www.onthemarket.com/to-rent/property/manchester/"
    },
    {
      "url": "https://www.onthemarket.com/to-rent/property/london/?page=2"
    },
    {
      "url": "https://www.onthemarket.com/new-homes/property/london/"
    },
    {
      "url": "https://www.onthemarket.com/for-sale/property/london/?new-homes=true"
    },
    {
      "url": "https://www.onthemarket.com/details/20490019/"
    },
    {
      "url": "https://www.onthemarket.com/details/20622528"
    },
    {
      "url": "https://www.onthemarket.com/property/london/"
    },
    {
      "url": "https://www.onthemarket.com/agents/branch/1-clickproperties-acton/"
    },
    {
      "url": "https://www.onthemarket.com/agents/london/"
    }
  ],
  "incrementalMode": false,
  "emitUnchanged": false,
  "emitExpired": false,
  "ignoreFieldsForChanges": []
}' |
apify call abotapi/onthemarket-scraper --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,abotapi/onthemarket-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/Wis05USPpzMbAQWGZ/builds/Xz3zAE1eiZPFT55WQ/openapi.json
