# Otodom Scraper - Polish Property Listings & Price Drops (`mambo_melon/otodom-scraper`) Actor

Scrape Otodom.pl listings from any search URL: price, price per m², area, rooms, floor, district, agency or private owner, first published date. Flags price drops, tracks price changes between runs and can add GPS, description and building details.

- **URL**: https://apify.com/mambo\_melon/otodom-scraper.md
- **Developed by:** [Joran Morgan](https://apify.com/mambo_melon) (community)
- **Categories:** Real estate, Lead generation, Automation
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $2.00 / 1,000 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

## Otodom Scraper - Polish Property Listings & Price Drops

Scrape listings from Otodom.pl, Poland's largest property portal: flats, houses, plots, rooms and commercial property, for sale or rent. Set up any search on otodom.pl, paste the URL, and get every result as a clean row with price, price per m², area, rooms, floor, district, who is selling (developer, agency or private owner) and when the listing first went up.

Two things I added because I wanted them for market tracking. First, price drops: Otodom shows the lowest price of the last 30 days for discounted listings, and the scraper turns that into `lowestPrice30d` and `priceDropPct`. Second, it remembers what it saw, so on a schedule you can ask for only new listings and listings whose price changed since your last run, with the old price next to the new one.

### Example

Input:

```json
{
  "startUrls": [{ "url": "https://www.otodom.pl/pl/wyniki/sprzedaz/mieszkanie/mazowieckie/warszawa/warszawa/warszawa" }],
  "maxItems": 300
}
```

One of the 300 rows from a real run (Sep 29 2026), a listing priced 8% below its 30-day low:

```json
{
  "id": 67457296,
  "url": "https://www.otodom.pl/pl/oferta/2-pokojowe-mieszkanie-45m2-loggia-bezposrednio-ID4z2IU",
  "title": "2-pokojowe mieszkanie 45m2 + loggia Bezpośrednio",
  "transaction": "sale",
  "propertyType": "flat",
  "price": 1009908,
  "currency": "PLN",
  "pricePerSqm": 21987.98,
  "lowestPrice30d": 1097727,
  "priceDropPct": 8,
  "previousPrice": null,
  "priceChange": null,
  "isNew": true,
  "areaSqm": 45.93,
  "rooms": 2,
  "floor": 7,
  "province": "mazowieckie",
  "city": "Warszawa",
  "district": "Bielany",
  "subdistrict": "Chomiczówka",
  "street": "Conrada",
  "isPrivateOwner": false,
  "advertiserName": "Marvipol Development",
  "advertiserType": "developer",
  "developmentTitle": "Conrada 30",
  "tags": ["PARKING_SPOT"],
  "imageCount": 8,
  "firstPublishedAt": "2025-11-18T13:57:23Z",
  "bumpedAt": "2026-09-29T15:34:31+02:00"
}
```

With `includeDetails` on, each row also gets data from the listing page. From a Kraków rental run:

```json
{
  "title": "2 pok., 49m2, przy Galerii Bonarka, ul. Sucha ENG",
  "price": 2600,
  "monthlyFee": 460,
  "latitude": 50.021736,
  "longitude": 19.94083,
  "buildYear": 2018,
  "buildingFloors": 2,
  "heating": "gas",
  "description": "..."
}
```

Every row has the same fields; missing values are `null`. Field names are in English, values like `heating: "gas"` or `market: "primary"` come from Otodom's own codes.

### Input

| Field | What it does | Default |
|---|---|---|
| `startUrls` | Otodom search result URLs. Any filters you set on the site (city, sale/rent, type, price, area, rooms, market) are kept | required |
| `maxItems` | Cap on listings across all URLs | 500 |
| `includeDetails` | Open each listing for GPS, description, build year, building floors, market, ownership, heating, features | off |
| `onlyNewOrChanged` | Only listings you haven't received before, or whose price changed since your last run | off |

### Good to know

- Speed: a run of 300 Warsaw listings took about 25 seconds; 40 Kraków rentals with details took about 11 seconds.
- `priceDropPct` is only filled when Otodom publishes the 30-day lowest price (mostly developers' discounted units). `priceChange` comes from your own previous runs and works for every listing.
- Development projects appear as `propertyType: "development"` rows without a single price; the individual units are separate rows.
- Private sellers are marked with `isPrivateOwner: true`; their names are not collected.

### Use cases

Tracking asking prices and price per m² by district, spotting discounted units from developers, alerting on new listings that match a search (schedule it and connect Slack, email or Google Sheets), building datasets for valuation models, or monitoring a competitor agency's portfolio.

### FAQ

**Can I scrape all of Poland?**
Yes, use a search URL without a city, or several city URLs. For very large pulls, splitting by city or price range keeps each run short and easy to re-run.

**What does it cost?**
See the Pricing tab: a price per listing, plus a separate small charge per listing when `includeDetails` is on.

**The site changed and something broke.**
Open an issue. I run a daily test on this Actor, so breakages usually get noticed and fixed quickly.

### Notes

Only public listing pages are read, no login. Otodom is a trademark of its owner; this Actor isn't affiliated with it.

# Actor input Schema

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

Open otodom.pl, set any filters (city, sale/rent, flat/house/plot, price, area, rooms...), then copy the results page URL here. You can add several.

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

Maximum number of listings to return across all URLs.

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

Also open every listing to add GPS coordinates, full description, market (primary/secondary), build year, building floors, ownership, heating and features. Slower and charged as an extra event per listing.

## `onlyNewOrChanged` (type: `boolean`):

For scheduled runs: return only listings you haven't received before, or ones whose price changed since your last run.

## Actor input object example

```json
{
  "startUrls": [
    {
      "url": "https://www.otodom.pl/pl/wyniki/sprzedaz/mieszkanie/mazowieckie/warszawa/warszawa/warszawa"
    }
  ],
  "maxItems": 500,
  "includeDetails": false,
  "onlyNewOrChanged": false
}
```

# Actor output Schema

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

No description

## `drops` (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.otodom.pl/pl/wyniki/sprzedaz/mieszkanie/mazowieckie/warszawa/warszawa/warszawa"
        }
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("mambo_melon/otodom-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 = { "startUrls": [{ "url": "https://www.otodom.pl/pl/wyniki/sprzedaz/mieszkanie/mazowieckie/warszawa/warszawa/warszawa" }] }

# Run the Actor and wait for it to finish
run = client.actor("mambo_melon/otodom-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 '{
  "startUrls": [
    {
      "url": "https://www.otodom.pl/pl/wyniki/sprzedaz/mieszkanie/mazowieckie/warszawa/warszawa/warszawa"
    }
  ]
}' |
apify call mambo_melon/otodom-scraper --silent --output-dataset

```

## MCP server setup

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