# Otodom.pl Scraper - Property Listings, Prices & Rentals (`abotapi/otodom-pl-scraper`) Actor

Scrape Otodom.pl properties for sale and rent, including apartments, houses, plots, commercial spaces, garages, rooms and new developments. Build searches by city, transaction, property type and filters, or paste Otodom URLs directly.

- **URL**: https://apify.com/abotapi/otodom-pl-scraper.md
- **Developed by:** [Abot API](https://apify.com/abotapi) (community)
- **Categories:** Real estate, Automation, Developer tools
- **Features:** Limited permissions
- **Stats:** 35 total users, 5 monthly users, 97.9% runs succeeded, 3 bookmarks
- **User rating**: 5.00 out of 5 stars

## Pricing

from $0.80 / 1,000 results

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

## Otodom Scraper: Polish Property Listings, Prices, GPS & Agent Contacts

Otodom Scraper turns Otodom into a clean data feed of Polish property listings. Get price, area, rooms, full address, GPS coordinates, description, agent contact, building year, ownership, market type and the full feature list for apartments, houses, plots, commercial units, garages, new developments and rooms, for sale or for rent. Build a search from transaction, property type, city and filters, or paste any Otodom search URL, then export to JSON, CSV or Excel, or pull the results straight into your app through the API.

### Why This Scraper?

- **Two ways to search.** Build a query from transaction, property type, cities and filters, or paste any Otodom search URL refined in your browser. No need to construct search URLs by hand.
- **80 cities plus all 16 voivodeships recognized by name.** Warszawa, Kraków, Wrocław, Gdańsk, Łódź, Poznań, Szczecin, Sopot and more, built in. For a city or district that is not recognized, drop a URL into URL mode instead.
- **30+ fields per listing.** SERP fields (price, area, rooms, agency, photo) plus optional detail-page enrichment (city and province, GPS coordinates, full description, agent phone, building year, ownership, full feature list).
- **Detail depth on demand.** Switch `fetchDetails` on or off per run: SERP-only fields are always included, detail-page fields are added when you need them.
- **Forward pagination with cost caps.** Walks every result page per search by default, with per-run and per-search limits so a multi-city search returns balanced results instead of exhausting the budget on the first city.
- **Resume across runs.** Continue an interrupted full-catalogue walk from a previous run or dataset ID without re-scraping listings you already collected.
- **Incremental monitoring for scheduled runs.** Turn on incremental mode for a recurring search and every row is flagged `NEW`, `UPDATED`, `UNCHANGED`, `REAPPEARED` or `EXPIRED` against what you collected last time.

### Use Cases

- **Real estate market research:** track asking prices, price per square meter and inventory across cities, districts and property types over time.
- **Property investment analysis:** pull GPS-tagged listings with build year, ownership type and agent contact to compare deals across multiple cities.
- **Lead generation for agents and brokers:** filter by market type and advertiser type to find private-seller and secondary-market listings worth a call.
- **Rental market tracking:** compare rent, deposit and additional fees across cities, room counts and property types.
- **Proptech and data products:** feed structured Polish listings into a search app, price index or valuation model.

### Data You Get

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

| Field | Example |
| --- | --- |
| `id` | `"00000001"` |
| `url` | `"https://www.otodom.pl/pl/oferta/sample-listing-ID00000"` |
| `title` | `"Przytulne mieszkanie na Mokotowie, 2 pokoje"` |
| `transaction` | `"sprzedaz"` |
| `estate` | `"mieszkanie"` |
| `priceTotal` / `priceCurrency` | `750000` / `"PLN"` |
| `pricePerSquareMeter` | `15000` |
| `areaInSquareMeters` | `50.0` |
| `rooms` | `2` |
| `floor` / `buildingFloors` | `"3"` / `5` |
| `buildYear` | `2010` |
| `marketType` | `"secondary"` |
| `ownership` | `"full_ownership"` |
| `buildingType` | `"block"` |
| `city` / `province` / `district` | `"Warszawa"` / `"mazowieckie"` / `"Mokotów"` |
| `latitude` / `longitude` | `52.0000` / `21.0000` |
| `agencyName` / `advertiserType` | `"Sample Agency"` / `"agency"` |
| `phone` | `"+48000000000"` |
| `features` | `["meble", "balkon", "winda"]` |
| `imageUrl` | `"https://img.otodom.pl/api/v1/offers/sample-id/image"` |
| `scrapedAt` | `"2026-04-28T00:00:00.000Z"` |

### How to Use

1. Pick a **mode**: `search` (build a query from transaction, property type, cities and filters) or `url` (paste one or more Otodom search URLs).
2. Fill in the fields for that mode, and turn **Fetch detail pages** on if you need GPS coordinates, the full description, agent phone, building year, ownership or the feature list.
3. Set **Max pages** and **Max listings** to control run size and cost, then click **Start**.
4. Download the dataset as JSON, CSV or Excel, or read it through the API.

**Search mode, basic Warsaw apartments for sale:**

```json
{
  "mode": "search",
  "transaction": "sprzedaz",
  "estate": "mieszkanie",
  "locations": ["Warszawa"],
  "maxPages": 3,
  "maxListings": 50,
  "fetchDetails": true
}
```

**Search mode, rentals in three cities, filtered:**

```json
{
  "mode": "search",
  "transaction": "wynajem",
  "estate": "mieszkanie",
  "locations": ["Warszawa", "Krakow", "Wroclaw"],
  "minPrice": 2000,
  "maxPrice": 4500,
  "minRooms": 2,
  "maxRooms": 3,
  "sortBy": "PRICE_ASC",
  "maxPages": 2,
  "maxListings": 60,
  "fetchDetails": true
}
```

**Search mode, new developments only (primary market):**

```json
{
  "mode": "search",
  "transaction": "sprzedaz",
  "estate": "mieszkanie",
  "locations": ["Warszawa"],
  "marketType": "primary",
  "maxPages": 2,
  "maxListings": 30,
  "fetchDetails": true
}
```

**URL mode, paste any Otodom search URL:**

```json
{
  "mode": "url",
  "urls": [
    "https://www.otodom.pl/pl/wyniki/sprzedaz/mieszkanie/mazowieckie/warszawa/warszawa/warszawa/mokotow"
  ],
  "minPrice": 400000,
  "maxRooms": 3,
  "maxPages": 5,
  "maxListings": 100,
  "fetchDetails": true
}
```

URL mode is the recommended path for filters not exposed above (district, neighborhood, building type) or for cities outside the built-in lookup table. Open Otodom in your browser, refine your search, then copy the URL. The price, area and room range fields still apply to listings found this way.

**Supported URL shapes:**

| Shape | Example | Result |
|---|---|---|
| Search (city, district, filters, sort, `?page=N`) | `/pl/wyniki/sprzedaz/mieszkanie/mazowieckie/warszawa/warszawa/warszawa` | All listings, walking pages from the page in the URL |
| Paginated search (`?page=2`, walks forward from that page) | `/pl/wyniki/sprzedaz/mieszkanie/mazowieckie/warszawa/warszawa/warszawa?page=2` | Listings from page 2 onward |
| Search with market and rooms query (the site redirects to a canonical path) | `/pl/wyniki/sprzedaz/mieszkanie/cala-polska?ownerTypeSingleSelect=ALL&roomsNumber=%5BTWO%5D&market=PRIMARY` | All matching listings |
| Rent search with price and sort query | `/pl/wyniki/wynajem/mieszkanie/cala-polska?priceMin=1000&priceMax=3000&by=PRICE&direction=ASC` | All listings |
| District with radius | `/pl/wyniki/sprzedaz/mieszkanie/malopolskie/krakow/krakow/krakow/stare-miasto?distanceRadius=5` | All listings |
| New-development search | `/pl/wyniki/sprzedaz/inwestycja/cala-polska` | All listings |
| Single listing (also the short `/pl/oferta/ID...` form) | `/pl/oferta/2-pok-46-38-m2-balkon-umeblowane-do-wejscia-0-prowizji-ID4CBFQ` | One record with full detail |
| Agency profile | `/pl/firmy/biura-nieruchomosci/hampton-home-nieruchomosci-ID3065052` | The first page of the agency's listings (about 6; the site does not serve further pages on this address, a warning shows the agency's total) |

Not supported, skipped with an error line in the log (never a silent zero): new-development detail pages (`/pl/inwestycja/...`), agency directory lists (`/pl/firmy/biura-nieruchomosci/lista/...`), the mobile host `m.otodom.pl`, `/en/` addresses and the old address style (`/sprzedaz/mieszkanie/warszawa/`). If no URL in the list is usable the run fails with an input error.

**Behaviour change:** if you switch to URL mode and leave `urls` untouched (or omit it from an API call), the actor now scrapes the eight example URLs in the table above (one per supported shape, capped by `maxListings`) instead of doing nothing. An explicitly empty list is still rejected. Each returned listing is billed as usual under the existing event; no pricing changed.

**Locations by name or path:** `locations` accepts a city or voivodeship name, or a location path such as `malopolskie/krakow/krakow/krakow/stare-miasto`. A name or path that cannot be resolved is skipped with an error in the log instead of silently searching all of Poland.

#### Run it from your code

Python:

```python
from apify_client import ApifyClient

client = ApifyClient("<YOUR_APIFY_TOKEN>")
run = client.actor("abotapi/otodom-pl-scraper").call(run_input={
    "mode": "search",
    "transaction": "sprzedaz",
    "estate": "mieszkanie",
    "locations": ["Warszawa"],
    "maxListings": 20,
})
for listing in client.dataset(run["defaultDatasetId"]).iterate_items():
    print(listing["title"], listing["priceTotal"], listing["city"])
```

JavaScript:

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

const client = new ApifyClient({ token: '<YOUR_APIFY_TOKEN>' });
const run = await client.actor('abotapi/otodom-pl-scraper').call({
    mode: 'search',
    transaction: 'sprzedaz',
    estate: 'mieszkanie',
    locations: ['Warszawa'],
    maxListings: 20,
});
const { items } = await client.dataset(run.defaultDatasetId).listItems();
```

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

#### Resume and recurring updates

- **Resume** (`resumeFromRunId`) continues one interrupted full-catalogue walk: paste its run or dataset ID and already-collected listings (matched by `id`) are skipped, so this run only saves the new ones.
- **Incremental mode** (`incrementalMode`) is for scheduled runs of the same search. The actor remembers the search itself, no ID to paste, and classifies every row `NEW`, `UPDATED` (with `changedFields`), `UNCHANGED` (suppressed unless `emitUnchanged` is on), `REAPPEARED`, or `EXPIRED` (only after a run that scanned the whole search with no cap or resume, and only with `emitExpired` on). `stateKey` names or shares the stored baseline; leave it empty and one is derived automatically from your search settings. Every output field is compared except `scrapedAt`, the photo fields (`images`, `imageUrl`: image links can change without the ad changing), `isPromoted` (a paid-promotion flag that comes and goes), and anything you list in `ignoreFieldsForChanges` (ignored fields stay in the output but never trigger `UPDATED` or appear in `changedFields`). `dateModified` is Otodom's own modified-at stamp and moves when an advertiser edits or refreshes an ad; add it to `ignoreFieldsForChanges` if you only care about content changes. A pasted URL that starts at `?page=2` or later, or any run stopped by `maxListings`/`maxPages`, never emits `EXPIRED`. `city` and `province` are now filled from the detail page (they used to be empty); on the first incremental run after this change that fill is saved silently as a one-time backfill and is not returned or billed as `UPDATED`. With incremental mode off, output is exactly as before.
- The two combine only when bootstrapping: pass `resumeFromRunId` on the very first `incrementalMode` run to seed its baseline from a prior non-incremental pull.

#### Output views and run summary

The dataset has three views, also linked from the run's **Output** tab: **Listings** (`overview`, the core columns), **Listing details** (`detail`: description, GPS, phone, condition, extras, buildYear, ownership, dateModified) and **Changes (incremental mode)** (`changes`: `changeType`, `changedFields`, `firstSeenAt`, `lastSeenAt`). A **Run summary** (key-value store record `OUTPUT`) lists, per search or URL, the pages loaded, listings found and status. Detail enrichment also adds `images` (up to 30 photo URLs), `condition`, `extras`, `dateModified`, `agencyUrl` and `agencyLicenseNumber` where Otodom shows them.

#### Pricing

Pay per result: a small per-run start charge plus a per-listing charge (the Pricing tab shows the rates for your tier). With **Fetch detail pages** on, each delivered listing whose detail page is read is also charged as one detail-page event; a pasted single-listing URL is itself read from its detail page, so its row carries the detail charge too. Listings whose detail page is refused, and listings incremental mode suppresses as unchanged, are never charged. Incremental mode bills only the rows it returns.

### 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.

**What gets written to the connector:** a condensed, human-readable **summary** of each record, not the full JSON. Each item becomes one entry with a **title** and its key fields flattened to plain text. 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 **"Pipe results into your apps"** input field. (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.

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` (use the filters below) or `url` (paste URLs). |
| `transaction` | string | `sprzedaz` | `sprzedaz` (sale) or `wynajem` (rent). Used to build the search URL in search mode. |
| `estate` | string | `mieszkanie` | Property type: `mieszkanie`, `dom`, `dzialka`, `lokal`, `garaz`, `inwestycja`, `pokoj`, or `haleimagazyny` (halls and warehouses). |
| `locations` | array | none (prefill: `["Warszawa"]`) | Polish city names. 80 cities and all 16 voivodeships are recognized by name; unrecognized names are skipped with an error in the log (and a location path such as `malopolskie/krakow/krakow/krakow/stare-miasto` is accepted). Leave empty for Poland-wide, or use URL mode for districts and smaller cities. |
| `marketType` | string | `all` | `all`, `primary` (rynek pierwotny), or `secondary` (rynek wtórny). |
| `minPrice` | integer | none | Lower bound on total price (PLN). |
| `maxPrice` | integer | none | Upper bound on total price (PLN). |
| `minArea` | integer | none | Lower bound on area (m²). |
| `maxArea` | integer | none | Upper bound on area (m²). |
| `minRooms` | integer | none | Minimum number of rooms, 1 to 10. |
| `maxRooms` | integer | none | Maximum number of rooms, 1 to 10. |
| `buildYear` | integer | none | Only listings built in this year or later (1900-2030). Search mode only. |
| `postedDate` | string | empty (any time) | Only listings posted within the last `1`, `3`, `7` or `14` days. Search mode only. |
| `districts` | array | none | City districts such as `Mokotów` or `Wola`; one search runs per district. Applies to city locations only (ignored, with a log warning, for voivodeships or Poland-wide). Search mode only. |
| `sortBy` | string | `DEFAULT` | `DEFAULT`, `LATEST`, `PRICE_ASC`, `PRICE_DESC`, `AREA_ASC`, or `AREA_DESC`. Search mode only. |
| `urls` | array | eight sample URLs (one per supported shape; also the prefill) | Full otodom.pl URLs (URL mode): searches, single listings, agency profiles. The price, area and room filters above still apply. |
| `maxPages` | integer | `0` | Result pages to walk per search or per URL (about 36 listings per page). `0` walks every page. |
| `maxListings` | integer | `20` | Stop after this many listings across all searches. `0` means no cap. The schema accepts at most 1,000,000. |
| `fetchDetails` | boolean | `true` | Also fetch each listing's detail page for GPS coordinates, full description, agent phone, building year, ownership and the full feature list. Each delivered listing whose detail page is read is charged as one detail-page event on top of the per-result charge. |
| `resumeFromRunId` | string | none | Previous run or dataset ID; already-collected listings (matched by `id`) are skipped. |
| `incrementalMode` | boolean | `false` | Recurring monitoring of the same search: adds `changeType`, `changedFields`, `firstSeenAt`, and `lastSeenAt` to every row. |
| `stateKey` | string | none (auto-derived) | Optional label for the incremental-mode baseline; leave empty to derive one from your search settings. |
| `emitUnchanged` | boolean | `false` | Also output `UNCHANGED` rows in incremental mode (bills extra dataset rows). |
| `emitExpired` | boolean | `false` | Also output `EXPIRED` rows for delisted listings, only after a complete, uncapped, non-resumed scan in incremental mode. |
| `ignoreFieldsForChanges` | string\[] | `[]` | Incremental mode only: field names (dotted path for nested, e.g. `agency.logo`) left out of change detection; they still appear in the output. `scrapedAt` is always excluded. |
| `proxy` | object | Residential (PL) | Connection settings. Residential is optional; datacenter or your own proxy URLs also work. |
| `mcpConnectors` | array | none | Optional: send a summary of each record to apps you authorized under Integrations. |
| `notionParentPageUrl` | string | none | Notion connector only: page under which records are created. |
| `maxNotifyListings` | integer | `50` | Cap on items written to each connector per run. |

### Output Example

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

```json
{
  "id": "00000001",
  "url": "https://www.otodom.pl/pl/oferta/sample-listing-ID00000",
  "title": "Przytulne mieszkanie na Mokotowie, 2 pokoje",
  "transaction": "sprzedaz",
  "estate": "mieszkanie",
  "priceTotal": 750000,
  "priceCurrency": "PLN",
  "pricePerSquareMeter": 15000,
  "rentPrice": null,
  "rentAdditional": null,
  "deposit": null,
  "areaInSquareMeters": 50.0,
  "rooms": 2,
  "floor": "3",
  "buildingFloors": 5,
  "buildYear": 2010,
  "marketType": "secondary",
  "ownership": "full_ownership",
  "buildingType": "block",
  "buildingMaterial": "concrete",
  "heating": "urban",
  "condition": "ready_to_use",
  "extras": ["balcony", "garden"],
  "dateModified": "2026-10-14T21:48:21Z",
  "windowsType": "plastic",
  "city": "Warszawa",
  "province": "mazowieckie",
  "district": "Mokotów",
  "street": "ul. Sample",
  "postalCode": "00-000",
  "latitude": 52.0000,
  "longitude": 21.0000,
  "agencyName": "Sample Agency",
  "advertiserType": "agency",
  "contactName": "Agent Name",
  "phone": "+48000000000",
  "referenceId": "REF-0001",
  "description": "Sprzedam przytulne mieszkanie w spokojnej okolicy, blisko metra i parku.",
  "features": ["meble", "balkon", "winda"],
  "isPromoted": false,
  "isExclusive": false,
  "isPrivateOwner": false,
  "imageCount": 12,
  "imageUrl": "https://img.otodom.pl/api/v1/offers/sample-id/image",
  "dateCreated": "2026-01-01T00:00:00.000Z",
  "investmentEstimatedDelivery": null,
  "scrapedAt": "2026-04-28T00:00:00.000Z"
}
```

### Plan Requirement

The default connection setting is Apify's residential proxy scoped to Poland. It is optional: in our checks Otodom pages also loaded through Apify's standard datacenter proxy (clear a leftover Poland/residential selection in the connection field to use it), so residential is not required. Datacenter access was checked on single fetches only; heavy runs at volume may hit rate limits, in which case switch back to residential. Residential and datacenter proxy usage are billed to your own Apify account.

If your plan has no residential access, the actor says so in the log ("The selected connection is unavailable to this run. Apify free plans do not include residential proxies. Continuing on a backup connection; some pages may still be unavailable.") and continues on a backup connection. If no page can be read at all, the run ends as failed with one of these messages:

- `CONNECTION_REFUSED`: the proxy connection was refused for this account (Apify plans without proxy access need a paid plan), or your own proxy URLs have invalid credentials.
- `NO_RESULTS`: residential proxies are unavailable on this run and the backup connection could not load the requested pages.
- `COULD_NOT_LOAD`: no results page could be loaded; a connection problem, not an empty search. Try again later.

You can also paste your own proxy URLs into the connection field; make sure they reach Poland for stable routing.

### FAQ

#### How much does it cost?

You pay a small per-run start charge plus a per-record charge, with detail-page enrichment billed as its own detail event per read record. The **Pricing** tab shows the rates for your tier. Use **Max listings** to cap the cost of any run.

#### Is it legal to scrape Otodom?

This actor collects only publicly available property listing data. You are responsible for how you use it: follow Otodom's terms and the laws that apply to you, including data protection rules if you store or reuse agent or owner contact details, and get legal advice if you plan commercial redistribution.

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

Yes. Schedule the actor from the **Schedules** tab and turn on **Incremental mode**. Each run then returns only new, updated, reappeared, and (if you also turn on **Emit expired listings**) delisted records for that same search; unchanged ones are not returned or billed unless you turn that on too.

#### Why did my city search return listings from all over Poland?

`locations` recognizes 80 built-in cities and the 16 voivodeships by name. An unrecognized or misspelled name is skipped with an error in the log (if every entry is unrecognized the run fails with an input error). Check the spelling, or switch to URL mode and paste a search URL for that city or district.

#### Why did my run fail instead of returning an empty dataset?

A run fails only in these cases: (1) the input has no usable URLs or locations left after unrecognized entries are rejected (an input error); (2) no results page can be loaded at all, because the proxy connection was refused for your account, because residential access is unavailable on a free plan and the backup connection could not load the page either, or because the requests failed for another connection reason; (3) `resumeFromRunId` points to a run or dataset your account cannot read; (4) `incrementalMode` and `resumeFromRunId` are combined on a search that already has saved incremental state; or (5) the connection settings cannot be set up at all (for example an invalid proxy configuration). Everything else, including filters that match nothing or a single page that could not be read while other pages load, finishes as a successful run with a warning in the log and an empty or partial dataset. If you get zero listings, check your city name and filters first, then try again.

#### Can I use it with AI agents or MCP?

Yes. Call it from any Apify integration or MCP client, and use the connector field to push results into Notion, Linear or Airtable.

### 🔗 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/immoscout24-scraper"><b>ImmobilienScout24.de Scraper</b></a><br>$0.9💰/1K for Gold discount. Extract property listings from immobilienscout24.de...</td><td>🏠 <a href="https://apify.com/abotapi/immowelt-de-scraper"><b>Immowelt.de Scraper</b></a><br>Scrape detailed property listings from Immowelt.de into clean structured data. Extract...</td></tr>
<tr><td>🏠 <a href="https://apify.com/abotapi/seloger-france-scraper"><b>SeLoger Scraper</b></a><br>Scrape SeLoger.com properties for sale and rent. Search by location with price, room and...</td><td>🏠 <a href="https://apify.com/abotapi/cian-ru-scraper"><b>Cian RU</b></a><br>Collect property listings from Cian.ru by search filters or direct URLs. Returns...</td></tr>
<tr><td>🏠 <a href="https://apify.com/abotapi/domclick-scraper"><b>Domclick RU Property Scraper</b></a><br>Extract property listings from domclick.ru, one of Russia’s largest real estate portals...</td><td>🏠 <a href="https://apify.com/abotapi/591-com-tw-scraper"><b>591.com.tw Scraper</b></a><br>Extract property listings from 591房屋交易網 591.com.tw, Taiwan’s largest real estate...</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/otodom-pl-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`):

Search mode = pick transaction + property type + cities + filters below. URL mode = paste any Otodom search URL refined in a browser (recommended for filters not exposed below, like district/neighborhood).

## `transaction` (type: `string`):

Sale (sprzedaż) or rent (wynajem). Used to build the canonical Otodom URL.

## `estate` (type: `string`):

Otodom property type segment. Apartment (mieszkanie), house (dom), plot (działka), commercial (lokal), garage (garaż), investment / new development (inwestycja), room (pokój), halls and warehouses (hale i magazyny).

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

Polish city names. Recognized: Warszawa, Kraków, Wrocław, Gdańsk, Łódź, Poznań, Szczecin, Bydgoszcz, Lublin, Białystok, Katowice, Gdynia, Sopot, Gliwice, Zabrze, Rzeszów, Olsztyn, Toruń, Kielce, Radom, Częstochowa, Bytom, Tychy, Opole, Sosnowiec, plus all 16 voivodeship names. For unknown / smaller cities, leave empty (Poland-wide search) or use URL mode. The cartesian product of property type × city is searched.

## `marketType` (type: `string`):

Filter by primary (rynek pierwotny, new from developer) or secondary (rynek wtórny, resale). Default = both.

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

Lower bound on totalPrice (PLN). Optional.

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

Upper bound on totalPrice (PLN). Optional.

## `minArea` (type: `integer`):

Lower bound on areaInSquareMeters. Optional.

## `maxArea` (type: `integer`):

Upper bound on areaInSquareMeters. Optional.

## `minRooms` (type: `integer`):

Minimum number of rooms (1-10).

## `maxRooms` (type: `integer`):

Maximum number of rooms (1-10).

## `buildYear` (type: `integer`):

Only listings built in this year or later (1900-2030). Search mode only; listings without a build year on Otodom are excluded by the site.

## `postedDate` (type: `string`):

Only listings posted within the last 1, 3, 7 or 14 days. Empty = any time. Search mode only.

## `districts` (type: `array`):

City districts to search, e.g. Mokotów, Wola (one search per district; Search mode only). Applies to city locations such as Warszawa or Kraków, ignored for voivodeships. Use the district name as written on Otodom.

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

Default = newest. Otodom inserts promoted listings ahead of organic order regardless of sort, so sort is best-effort.

## `urls` (type: `array`):

One or more full otodom.pl URLs: a search (city, district, filters, sort, ?page=N), a single listing page (/pl/oferta/...), or an agency profile (/pl/firmy/biura-nieruchomosci/<name>-ID...). Tip: open otodom.pl in your browser, copy the URL from the address bar. Leaving this field untouched scrapes the eight sample URLs listed here. Unsupported pages (new-development detail pages, agency directory lists, m.otodom.pl, /en/ and old-style addresses) are skipped with an error in the log. Search-mode-only fields above (transaction, property type, cities, market type, sort order) are ignored; the price/area/rooms filters above still apply to listings found via search and agency URLs. Pagination starts at the page in the URL and walks forward.

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

How many SERP pages to walk per city × property type combination (or per pasted URL). Each page returns ~36 listings. Leave empty to walk every result page.

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

Stop after this many listings across all searches. 0 = no cap. Values above 1,000,000 are rejected.

## `fetchDetails` (type: `boolean`):

When ON, also fetch each listing's detail page for GPS coordinates, full description, agent phone, building year, ownership type, full feature list. SERP cards already carry id, title, price, area, rooms, agency and the cover image. City, province, latitude/longitude, description and contact details come from the detail page, so they are empty when this is off. Default ON. Each delivered listing whose detail page is read is charged as one detail-page event on top of the per-result charge; listings whose detail page is refused, and listings incremental mode suppresses as unchanged, are not charged.

## `proxy` (type: `object`):

Default = Apify residential with country=PL. Gives clean first-try fetches and the fastest end-to-end wall time.

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

Paste a previous run ID or dataset ID to continue a full-catalogue walk across separate runs. Listings already collected there (matched by id) are skipped, so this run only saves the new ones. Leave empty for a normal fresh run. For ongoing scheduled monitoring of this search, use incrementalMode below instead.

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

Turn on when you run this same search on a schedule (e.g. daily) and only want to know what's new or changed since last time. The actor remembers this search's listings itself, no run ID needed. Every field of a listing is compared (price, area, rooms, title, description, address, contact and agency data, features, image count and the rest) except scrapedAt, images, imageUrl, isPromoted and any field you list in the ignore setting at the end of this section; ignored fields stay in the output but never trigger UPDATED. dateModified is the site's own modified-at stamp (it moves when an ad is edited or refreshed); list it to ignore refresh-only changes. Adds changeType (NEW/UPDATED/UNCHANGED/REAPPEARED/EXPIRED), changedFields, firstSeenAt, and lastSeenAt to every row. Off by default so existing scheduled runs are unaffected. See stateKey, emitUnchanged, and emitExpired below.

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

Optional label for the incrementalMode baseline. Leave empty to auto-derive one from your search settings (mode/transaction/estate/locations/urls/marketType/price/area/rooms ranges/sort) — a different filter setup automatically gets its own baseline, never sharing one by accident. Set this only if you want multiple differently-configured runs to intentionally share one baseline.

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

When incrementalMode is on, also output listings that are UNCHANGED since the last run (changeType=UNCHANGED, changedFields=\[]). Off by default: unchanged listings are silently skipped, so you only see and pay for what's new or different. Turning this on bills extra dataset-item rows for listings that haven't changed.

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

When incrementalMode is on, also output a row for previously-tracked listings that are no longer found (changeType=EXPIRED), so you can detect delistings. Only emitted after a run that scans the full tracked search with no page/item cap and no resumeFromRunId — a partial run never marks a still-live listing as expired. Off by default. Turning this on bills extra dataset-item rows.

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

Incremental mode only. Field names to leave out when deciding whether a listing changed (they still appear in the output). Use a dotted path for a nested field, for example agency.name. Fields excluded by default: scrapedAt, images, imageUrl, isPromoted. Example: dateModified, agency.name. Leave empty to compare every other field.

## `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",
  "transaction": "sprzedaz",
  "estate": "mieszkanie",
  "locations": [
    "Warszawa"
  ],
  "marketType": "all",
  "postedDate": "",
  "sortBy": "DEFAULT",
  "urls": [
    "https://www.otodom.pl/pl/wyniki/sprzedaz/mieszkanie/mazowieckie/warszawa/warszawa/warszawa",
    "https://www.otodom.pl/pl/wyniki/sprzedaz/mieszkanie/mazowieckie/warszawa/warszawa/warszawa?page=2",
    "https://www.otodom.pl/pl/wyniki/wynajem/mieszkanie/cala-polska?priceMin=1000&priceMax=3000&by=PRICE&direction=ASC",
    "https://www.otodom.pl/pl/wyniki/sprzedaz/mieszkanie/cala-polska?ownerTypeSingleSelect=ALL&roomsNumber=%5BTWO%5D&market=PRIMARY",
    "https://www.otodom.pl/pl/wyniki/sprzedaz/mieszkanie/malopolskie/krakow/krakow/krakow/stare-miasto?distanceRadius=5",
    "https://www.otodom.pl/pl/wyniki/sprzedaz/inwestycja/cala-polska",
    "https://www.otodom.pl/pl/oferta/2-pok-46-38-m2-balkon-umeblowane-do-wejscia-0-prowizji-ID4CBFQ",
    "https://www.otodom.pl/pl/firmy/biura-nieruchomosci/hampton-home-nieruchomosci-ID3065052"
  ],
  "maxPages": 0,
  "maxListings": 20,
  "fetchDetails": true,
  "proxy": {
    "useApifyProxy": true,
    "apifyProxyGroups": [
      "RESIDENTIAL"
    ],
    "apifyProxyCountry": "PL"
  },
  "incrementalMode": false,
  "emitUnchanged": false,
  "emitExpired": false,
  "ignoreFieldsForChanges": [],
  "maxNotifyListings": 50
}
```

# Actor output Schema

## `overview` (type: `string`):

No description

## `detail` (type: `string`):

No description

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

No description

## `summary` (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 = {
    "mode": "search",
    "locations": [
        "Warszawa"
    ],
    "urls": [
        "https://www.otodom.pl/pl/wyniki/sprzedaz/mieszkanie/mazowieckie/warszawa/warszawa/warszawa",
        "https://www.otodom.pl/pl/wyniki/sprzedaz/mieszkanie/mazowieckie/warszawa/warszawa/warszawa?page=2",
        "https://www.otodom.pl/pl/wyniki/wynajem/mieszkanie/cala-polska?priceMin=1000&priceMax=3000&by=PRICE&direction=ASC",
        "https://www.otodom.pl/pl/wyniki/sprzedaz/mieszkanie/cala-polska?ownerTypeSingleSelect=ALL&roomsNumber=%5BTWO%5D&market=PRIMARY",
        "https://www.otodom.pl/pl/wyniki/sprzedaz/mieszkanie/malopolskie/krakow/krakow/krakow/stare-miasto?distanceRadius=5",
        "https://www.otodom.pl/pl/wyniki/sprzedaz/inwestycja/cala-polska",
        "https://www.otodom.pl/pl/oferta/2-pok-46-38-m2-balkon-umeblowane-do-wejscia-0-prowizji-ID4CBFQ",
        "https://www.otodom.pl/pl/firmy/biura-nieruchomosci/hampton-home-nieruchomosci-ID3065052"
    ],
    "proxy": {
        "useApifyProxy": true,
        "apifyProxyGroups": [
            "RESIDENTIAL"
        ],
        "apifyProxyCountry": "PL"
    }
};

// Run the Actor and wait for it to finish
const run = await client.actor("abotapi/otodom-pl-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": ["Warszawa"],
    "urls": [
        "https://www.otodom.pl/pl/wyniki/sprzedaz/mieszkanie/mazowieckie/warszawa/warszawa/warszawa",
        "https://www.otodom.pl/pl/wyniki/sprzedaz/mieszkanie/mazowieckie/warszawa/warszawa/warszawa?page=2",
        "https://www.otodom.pl/pl/wyniki/wynajem/mieszkanie/cala-polska?priceMin=1000&priceMax=3000&by=PRICE&direction=ASC",
        "https://www.otodom.pl/pl/wyniki/sprzedaz/mieszkanie/cala-polska?ownerTypeSingleSelect=ALL&roomsNumber=%5BTWO%5D&market=PRIMARY",
        "https://www.otodom.pl/pl/wyniki/sprzedaz/mieszkanie/malopolskie/krakow/krakow/krakow/stare-miasto?distanceRadius=5",
        "https://www.otodom.pl/pl/wyniki/sprzedaz/inwestycja/cala-polska",
        "https://www.otodom.pl/pl/oferta/2-pok-46-38-m2-balkon-umeblowane-do-wejscia-0-prowizji-ID4CBFQ",
        "https://www.otodom.pl/pl/firmy/biura-nieruchomosci/hampton-home-nieruchomosci-ID3065052",
    ],
    "proxy": {
        "useApifyProxy": True,
        "apifyProxyGroups": ["RESIDENTIAL"],
        "apifyProxyCountry": "PL",
    },
}

# Run the Actor and wait for it to finish
run = client.actor("abotapi/otodom-pl-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": [
    "Warszawa"
  ],
  "urls": [
    "https://www.otodom.pl/pl/wyniki/sprzedaz/mieszkanie/mazowieckie/warszawa/warszawa/warszawa",
    "https://www.otodom.pl/pl/wyniki/sprzedaz/mieszkanie/mazowieckie/warszawa/warszawa/warszawa?page=2",
    "https://www.otodom.pl/pl/wyniki/wynajem/mieszkanie/cala-polska?priceMin=1000&priceMax=3000&by=PRICE&direction=ASC",
    "https://www.otodom.pl/pl/wyniki/sprzedaz/mieszkanie/cala-polska?ownerTypeSingleSelect=ALL&roomsNumber=%5BTWO%5D&market=PRIMARY",
    "https://www.otodom.pl/pl/wyniki/sprzedaz/mieszkanie/malopolskie/krakow/krakow/krakow/stare-miasto?distanceRadius=5",
    "https://www.otodom.pl/pl/wyniki/sprzedaz/inwestycja/cala-polska",
    "https://www.otodom.pl/pl/oferta/2-pok-46-38-m2-balkon-umeblowane-do-wejscia-0-prowizji-ID4CBFQ",
    "https://www.otodom.pl/pl/firmy/biura-nieruchomosci/hampton-home-nieruchomosci-ID3065052"
  ],
  "proxy": {
    "useApifyProxy": true,
    "apifyProxyGroups": [
      "RESIDENTIAL"
    ],
    "apifyProxyCountry": "PL"
  }
}' |
apify call abotapi/otodom-pl-scraper --silent --output-dataset

```

## MCP server setup

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