# Otodom Scraper (`automation-lab/otodom-property-scraper`) Actor

Export public Otodom Poland sale and rental listings with prices, property attributes, photos, locations, and advertiser metadata.

- **URL**: https://apify.com/automation-lab/otodom-property-scraper.md
- **Developed by:** [Stas Persiianenko](https://apify.com/automation-lab) (community)
- **Categories:** Real estate
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

Pay per event

This Actor is paid per event. You are not charged for the Apify platform usage, but only a fixed price for specific events.
Since this Actor supports Apify Store discounts, the price gets lower the higher subscription plan you have.

Learn more: https://docs.apify.com/platform/actors/running/actors-in-store#pay-per-event

## What's an Apify Actor?

Actors are web data automations that power AI and operations. They run on the Apify platform to scrape websites, process data, connect APIs, and automate workflows.
In Batch mode, an Actor accepts a well-defined JSON input, performs an action which can take anything from a few seconds to a few hours,
and optionally produces a well-defined JSON output, datasets with results, or files in key-value store.
In Standby mode, an Actor provides a web server which can be used as a website, API, or an MCP server.
Actors are written with capital "A".

## How to integrate an Actor?

If asked about integration, you help developers integrate Actors into their projects.
You adapt to their stack and deliver integrations that are safe, well-documented, and production-ready.
The best way to integrate Actors is as follows.

- **AI agents and MCP clients** — the [Apify MCP server](https://docs.apify.com/integrations/mcp.md) at `https://mcp.apify.com` (remote, streamable HTTP, OAuth on first use).
- **Agentic workflows and local Actor development** — [Agent Skills](https://apify.com/.well-known/agent-skills/index.json) with the [Apify CLI](https://docs.apify.com/cli/docs.md): `npm install -g apify-cli`, then `apify login`.
- **JavaScript/TypeScript projects** — the official [JS/TS client](https://docs.apify.com/api/client/js/docs.md): `npm install apify-client`.
- **Python projects** — the official [Python client](https://docs.apify.com/api/client/python/docs.md): `pip install apify-client`.
- **Any other language** — the [REST API](https://docs.apify.com/api/v2.md).

For usage examples, see the [API](#api) section below.

For more details, see Apify documentation as [Markdown index](https://docs.apify.com/llms.txt) and [Markdown full-text](https://docs.apify.com/llms-full.txt).

# README

## Otodom Scraper

Export public Otodom Poland sale and rental listings as structured JSON, CSV, Excel, XML, or RSS data.
The Actor follows real Otodom search URLs, preserves their filters, paginates results, and returns prices, property attributes, photos, location details, and advertiser metadata.
Use it for one-time research or schedule repeated snapshots for Polish property market monitoring.

The default search mode is efficient: it reads the structured data already embedded in Otodom result pages.
Optional detail enrichment visits each offer page to add full descriptions, more attributes, coordinates, images, and available public advertiser contacts.
No Otodom login is required.

### What can you do with this Otodom scraper?

- Export current apartments, houses, plots, commercial properties, rooms, garages, and developments covered by an Otodom search URL.
- Compare sale or rental inventory between cities, districts, property types, and saved filter combinations.
- Build recurring snapshots for new-listing, removed-listing, and price-change analysis in your own database.
- Collect PLN prices, price per square metre, area, room count, floor, market type, and location hierarchy.
- Preserve property photo URLs without downloading large image files.
- Capture public agency, developer, owner classification, and available assigned-agent metadata.
- Enrich selected listings with complete descriptions and detailed property attributes.
- Feed Otodom data into spreadsheets, BI tools, warehouses, CRM workflows, or Apify integrations.

### Who is it for?

**Property investors and analysts** can compare asking prices, price per square metre, and available inventory across Polish markets.

**Real estate agencies** can monitor competing listings, neighbourhood supply, and public advertiser information.

**Developers and researchers** can use the Actor as an unofficial Otodom data export rather than maintaining page parsing and retry logic.

**Relocation and housing teams** can create filtered rental datasets for cities such as Lublin, Warsaw, Gdańsk, or Katowice.

**Data teams** can schedule repeatable runs and join records by stable Otodom IDs in an external history table.

### Why use this Actor?

1. **Search URL fidelity** — copy a real filtered Otodom results URL and the Actor keeps those source filters.
2. **Typed records** — output is normalized into stable fields instead of raw HTML.
3. **Two depth levels** — use fast search snapshots or enable offer-page enrichment only when the additional detail is valuable.
4. **Bounded collection** — `maxItems` stops pagination and avoids unnecessary source requests.
5. **Economical delivery** — a browser-like direct HTTP route avoids browser runtime and proxy transfer while bounded retries handle transient failures.
6. **Integration-ready output** — the default dataset works with Apify API clients, webhooks, schedules, exports, and MCP.
7. **Transparent partial enrichment** — if one optional detail page fails, the useful search record is retained instead of disappearing.

### What Otodom property data is extracted?

| Field | Meaning |
| --- | --- |
| `propertyId` | Stable Otodom listing or development identifier |
| `url` | Canonical public offer URL |
| `title` | Listing headline |
| `transaction` | Sale or rental classification |
| `propertyType` | Flat, house, land, investment, or other source category |
| `market` | Primary, secondary, or another available market value |
| `price` | Advertised amount and currency |
| `rentPrice` | Rent or maintenance amount when exposed |
| `pricePerSquareMeter` | PLN/m² or other source currency/m² value |
| `areaSquareMeters` | Interior floor area |
| `terrainAreaSquareMeters` | Plot area when relevant |
| `rooms` | Room count classification |
| `floor` | Floor classification |
| `city`, `province` | Locality and Polish voivodeship |
| `locationPath` | Ordered source location hierarchy |
| `latitude`, `longitude` | Approximate public coordinates when available |
| `advertiserType` | Agency, developer, or private-owner classification |
| `agency` | Public agency or developer metadata |
| `contact` | Available public advertiser or assigned-agent metadata |
| `description` | Search summary, or full plain text when enriched |
| `attributes` | Build year, heating, equipment, market, and other source attributes |
| `features` | Property tags and feature labels |
| `imageUrls` | Public property photo URLs |
| `createdAt`, `modifiedAt` | Source timestamps when available |
| `sourceSearchUrl` | Input or paginated search URL that produced the record |
| `scrapedAt` | Timestamp when this Actor produced the record |

Otodom does not expose every field for every listing.
Missing source values are returned as `null`, empty arrays, or empty objects rather than invented values.

### Getting started

1. Open an Otodom search in your browser.
2. Apply the location, transaction, property type, price, room, or other filters you need.
3. Copy the resulting `https://www.otodom.pl/pl/wyniki/...` URL.
4. Add it to **Otodom search or offer URLs**.
5. Choose a `maxItems` limit.
6. Leave detail enrichment off for a fast inventory snapshot, or enable it for richer offer data.
7. Click **Start**.
8. Open the **Property listings** dataset view or export it in your preferred format.

The prefilled Lublin apartment search is a working example and can be run without editing any field.

### Input parameters

#### `startUrls`

An array of public Otodom Poland URLs.
Supported paths are:

- Search results: `https://www.otodom.pl/pl/wyniki/...`
- Individual offers: `https://www.otodom.pl/pl/oferta/...`

You can mix search and offer URLs in one run.
Duplicate properties are emitted once by stable `propertyId`.
Only HTTPS Otodom URLs are accepted; unrelated domains and unsupported paths fail validation.
If omitted, the Actor uses a nationwide apartments-for-sale search.

#### `maxItems`

Maximum unique records saved across all start URLs.
Allowed range: 1–1,000.
Default: 20.
The Actor stops fetching new result pages after reaching the limit.

#### `includeDetails`

When `false`, records come from structured search-page data and already include the main price, property, image, location, and advertiser fields.

When `true`, each search record is enriched from its public offer page with full descriptions, additional attributes, coordinates, image variants, and available public contact metadata.
Offer URLs supplied directly are always parsed as detail pages.

#### `maxConcurrency`

Maximum simultaneous offer-page enrichment requests.
Allowed range: 1–10.
Default: 3.
Lower values are gentler and often more reliable for long jobs.
This setting does not increase search-page pagination concurrency.

### Example input: Lublin apartments for sale

```json
{
  "startUrls": [
    {
      "url": "https://www.otodom.pl/pl/wyniki/sprzedaz/mieszkanie/lubelskie/lublin/lublin/lublin"
    }
  ],
  "maxItems": 25,
  "includeDetails": false,
  "maxConcurrency": 3
}
```

For rental data, use a real Otodom URL containing `/wyniki/wynajem/`.
For another city or property type, build the search on Otodom and paste its URL unchanged.

### Example output

```json
{
  "propertyId": "12345678",
  "url": "https://www.otodom.pl/pl/oferta/bright-three-room-apartment-IDexample",
  "title": "Bright three-room apartment with balcony",
  "transaction": "SELL",
  "propertyType": "FLAT",
  "market": "secondary",
  "price": { "value": 650000, "currency": "PLN" },
  "rentPrice": { "value": 620, "currency": "PLN" },
  "pricePerSquareMeter": { "value": 12500, "currency": "PLN" },
  "areaSquareMeters": 52,
  "terrainAreaSquareMeters": null,
  "rooms": "THREE",
  "floor": "SECOND",
  "city": "Lublin",
  "province": "lubelskie",
  "locationPath": ["lubelskie", "Lublin", "Śródmieście"],
  "latitude": 51.2465,
  "longitude": 22.5684,
  "advertiserType": "AGENCY",
  "isPrivateOwner": false,
  "agency": { "name": "Sample Property Agency", "type": "AGENCY" },
  "contact": { "name": "Sample Agent", "phones": ["+48123456789"] },
  "description": "Three-room apartment near public transport with a balcony.",
  "attributes": { "build_year": "2020", "heating": "urban" },
  "features": ["BALCONY", "PARKING_SPOT"],
  "imageUrls": ["https://ireland.apollo.olxcdn.com/v1/files/sample/image"],
  "createdAt": "2025-01-15T12:00:00Z",
  "modifiedAt": "2025-01-16T09:30:00Z",
  "sourceSearchUrl": "https://www.otodom.pl/pl/wyniki/sprzedaz/mieszkanie/lubelskie/lublin/lublin/lublin",
  "scrapedAt": "2025-01-16T10:00:00Z"
}
```

The example is anonymized.
Actual records contain current public source values.

### How much does it cost to scrape Otodom properties?

The Actor uses pay-per-event pricing:

- one `start` event per run;
- one `item` event for each unique property record written to the dataset.

At the FREE-tier price of **$0.005 per run plus $0.0023 per property**, approximate Actor charges are:

| Result volume | Approximate Actor charge |
| ---: | ---: |
| 10 properties | $0.0280 |
| 100 properties | $0.2350 |
| 1,000 properties | $2.3050 |

Paid Apify plans receive the decreasing item prices configured for their tier.
Optional detail enrichment has no separate charge event, but it can take longer and use more source requests.
Your final total can also include Apify platform usage according to your account and plan.
Always check the live pricing shown on the Actor page before starting a large run.

### Recurring Polish property market monitoring

The Actor returns current snapshots; it does not silently retain a cross-run property history or send built-in alerts.
Use Apify schedules and your own stable-ID store to create monitoring:

1. Save a filtered Otodom URL for the market segment.
2. Schedule the Actor daily, weekly, or monthly.
3. Export each dataset to a database, object store, or spreadsheet.
4. Join snapshots on `propertyId`.
5. Compare `price.value`, `modifiedAt`, availability, and newly appearing IDs.
6. Trigger your own notification or dashboard refresh when the comparison meets your rules.

This separation keeps every run reproducible and avoids mixing old records into the current run dataset.

### Spreadsheet and data-pipeline exports

From the dataset page, export results as JSON, CSV, Excel, XML, RSS, or HTML.
For repeat automation, use a dataset API URL or an Apify integration.

Useful patterns include:

- send a completed dataset to Google Sheets;
- load daily snapshots into BigQuery, Snowflake, PostgreSQL, or S3;
- call a webhook after each run;
- connect Make, Zapier, n8n, or another automation tool;
- ingest records into a property intelligence or lead-review workflow.

Nested fields such as `price`, `agency`, `contact`, and `attributes` are objects.
Flatten them in your destination if a spreadsheet workflow requires scalar columns.

### Run through the Apify API with cURL

```bash
curl -X POST \
  "https://api.apify.com/v2/acts/automation-lab~otodom-property-scraper/runs?token=$APIFY_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "startUrls": [{
      "url": "https://www.otodom.pl/pl/wyniki/wynajem/mieszkanie/lubelskie/lublin/lublin/lublin"
    }],
    "maxItems": 20,
    "includeDetails": false
  }'
```

Poll the returned run ID until it reaches a terminal status, then read its default dataset.
Keep your Apify token in an environment variable or secret manager.

### Run with JavaScript

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

const client = new ApifyClient({ token: process.env.APIFY_TOKEN });

const run = await client.actor('automation-lab/otodom-property-scraper').call({
  startUrls: [{
    url: 'https://www.otodom.pl/pl/wyniki/sprzedaz/mieszkanie/cala-polska',
  }],
  maxItems: 50,
  includeDetails: false,
});

const { items } = await client.dataset(run.defaultDatasetId).listItems();
console.log(`Collected ${items.length} Otodom properties`);
```

For asynchronous systems, use `.start()` instead of `.call()` and attach a webhook or poll the run.

### Run with Python

```python
import os
from apify_client import ApifyClient

client = ApifyClient(os.environ['APIFY_TOKEN'])

run = client.actor('automation-lab/otodom-property-scraper').call(run_input={
    'startUrls': [{
        'url': 'https://www.otodom.pl/pl/wyniki/sprzedaz/mieszkanie/cala-polska',
    }],
    'maxItems': 50,
    'includeDetails': False,
})

items = client.dataset(run['defaultDatasetId']).list_items().items
print(f'Collected {len(items)} Otodom properties')
```

Use the stable `propertyId` field when upserting records into your destination.

### Use with Apify MCP

Add the Actor to Claude Code:

```bash
claude mcp add --transport http apify \
  "https://mcp.apify.com?tools=automation-lab/otodom-property-scraper"
```

#### Claude Desktop setup

Add this HTTP server to the `mcpServers` object in Claude Desktop configuration:

```json
{
  "mcpServers": {
    "apify": {
      "type": "http",
      "url": "https://mcp.apify.com?tools=automation-lab/otodom-property-scraper"
    }
  }
}
```

#### Cursor setup

Open **Cursor Settings → MCP → Add new global MCP server** and use the same `apify` HTTP configuration above.
Restart the MCP connection after saving it.

#### VS Code setup

Add the same server object under `servers.apify` in your workspace or user MCP configuration, with `type` set to `http` and the Actor-specific URL shown above.
Start the server from the VS Code MCP servers view.

Example prompts to ask through MCP:

- “Using the Apify MCP Otodom tool, collect 20 current apartments for sale in Lublin from this filtered URL and summarize median PLN per square metre.”
- “Run the Otodom property scraper through MCP for five enriched rental listings and compare public amenities.”
- “Use MCP to start the Otodom property scraper with my saved search URL and return the dataset link.”

The Actor only supports workflows represented by its input schema; an MCP prompt does not add unsupported filtering or alert features.

### Reliability and retries

The Actor uses a lightweight direct request with a browser-like HTTP stack.
If Otodom returns a temporary block, rate limit, network failure, or server error, it makes a bounded fresh direct request after backoff.
The working route needs neither a browser nor proxy transfer, which keeps normal and enriched runs economical.

Search pages are fetched sequentially.
Detail pages use the bounded `maxConcurrency` value.
Transient failures are retried twice.
Deterministic invalid URLs and missing offers are not retried indefinitely.

When optional detail enrichment fails for one offer, the Actor logs a warning and preserves the useful search-page record.
A challenged or structurally unexpected search page is treated as an error, not as a successful empty result.

### Limits and source behavior

- The Actor supports public Polish Otodom search and offer pages only.
- Otodom may change fields, categories, URL structures, or availability without notice.
- Search ordering can change while long pagination jobs are running.
- Promoted listings can appear alongside ordinary results.
- Duplicate IDs are removed within one run.
- Phone numbers, coordinates, descriptions, and agency details are returned only when publicly exposed by the source.
- Photo URLs are collected, but the Actor does not download or archive image binaries.
- `maxItems` is capped at 1,000 per run to keep jobs bounded.
- Built-in historical storage, change alerts, valuation models, and owner identity inference are not provided.

For large recurring jobs, split work by meaningful Otodom search filters rather than repeatedly requesting one extremely broad search.

### Troubleshooting

#### Why did the Actor return fewer records than `maxItems`?

The search may contain fewer unique public listings, the supplied page may be near the end of pagination, or several start URLs may overlap.
Check the original Otodom URL in a browser and inspect run warnings.

#### Why is a field `null`?

Otodom does not expose every attribute for every property.
Enable `includeDetails` for additional offer-page fields, but do not expect private or absent source data to appear.

#### Why did my input fail immediately?

Only HTTPS URLs on `otodom.pl` or `www.otodom.pl` are accepted.
Use a `/pl/wyniki/` search URL or `/pl/oferta/` property URL copied from Otodom.

#### Why is an enriched record missing some detail fields?

The source can remove an offer between search discovery and the detail request, or a detail request can fail after retries.
The Actor intentionally preserves the valid search record and logs the enrichment warning.

#### How should I detect price changes?

Schedule repeat runs, store each snapshot externally, and compare `price.value` by stable `propertyId`.
The Actor does not compare past runs automatically.

### Responsible use and legality

This Actor extracts publicly available property information.
You are responsible for complying with Otodom terms, applicable law, database rights, privacy rules, and the rules governing your downstream use.

Collect only what you need.
Do not use public contact details for spam, harassment, unlawful profiling, or discriminatory housing decisions.
Protect exported personal data, set appropriate retention periods, and honour valid deletion or access requests where applicable.
The Actor does not bypass logins or access private account data.

### Related Actors by automation-lab

- [OLX Poland Classifieds Scraper](https://apify.com/automation-lab/olx-poland-classifieds-scraper) for broader Polish classified listings.
- [Otomoto Scraper](https://apify.com/automation-lab/otomoto-scraper) for Polish vehicle marketplace records.

These are separate sources and datasets.
They are not merged into Otodom results automatically.

### FAQ

#### Does this provide an official Otodom API?

No.
It provides an Actor and dataset API over public Otodom pages.
It is not affiliated with or endorsed by Otodom.

#### Can I paste a filtered Otodom URL?

Yes.
That is the recommended way to specify locations, transactions, property types, prices, rooms, and other source filters.

#### Can I scrape one property?

Yes.
Pass its public `/pl/oferta/` URL in `startUrls`.
An individual offer is parsed as a detailed record.

#### Does it support Otodom mieszkania and rentals?

Yes.
Use a real apartment search URL for sale (`sprzedaz`) or rent (`wynajem`).
The same input pattern works for supported Otodom locations and property categories.

#### Does it download photos?

No.
It returns public photo URLs, avoiding unnecessary storage and transfer.

#### Can I schedule it?

Yes.
Use an Apify schedule, then store or compare datasets in your own monitoring workflow.

#### Is every public contact guaranteed?

No.
Contacts appear only when the current public source record exposes them.

#### What happens when Otodom blocks a request?

The Actor retries transient failures with fresh direct HTTP requests and bounded backoff.
If retries are exhausted, the run fails rather than claiming that a challenged page contains zero listings.

# Actor input Schema

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

One or more public Otodom Poland search-result URLs (/pl/wyniki/...) or individual property offer URLs (/pl/oferta/...). Filters already present in search URLs are preserved.

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

Maximum number of unique property records saved across all start URLs. The Actor stops pagination as soon as this limit is reached.

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

Fetch each offer page for full descriptions, additional attributes, coordinates, images, and available public advertiser contacts. Leave disabled for faster market snapshots because search records already include prices, core attributes, photos, and agency metadata.

## `maxConcurrency` (type: `integer`):

Maximum parallel offer-page requests when detail enrichment is enabled. Conservative values reduce source blocking and transient request failures.

## Actor input object example

```json
{
  "startUrls": [
    {
      "url": "https://www.otodom.pl/pl/wyniki/sprzedaz/mieszkanie/lubelskie/lublin/lublin/lublin"
    }
  ],
  "maxItems": 20,
  "includeDetails": false,
  "maxConcurrency": 3
}
```

# Actor output Schema

## `dataset` (type: `string`):

Normalized Otodom property records in the overview view.

# 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/lubelskie/lublin/lublin/lublin"
        }
    ],
    "maxItems": 20,
    "includeDetails": false,
    "maxConcurrency": 3
};

// Run the Actor and wait for it to finish
const run = await client.actor("automation-lab/otodom-property-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/lubelskie/lublin/lublin/lublin" }],
    "maxItems": 20,
    "includeDetails": False,
    "maxConcurrency": 3,
}

# Run the Actor and wait for it to finish
run = client.actor("automation-lab/otodom-property-scraper").call(run_input=run_input)

# Fetch and print Actor results from the run's dataset (if there are any)
print("💾 Check your data here: https://console.apify.com/storage/datasets/" + run["defaultDatasetId"])
for item in client.dataset(run["defaultDatasetId"]).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/lubelskie/lublin/lublin/lublin"
    }
  ],
  "maxItems": 20,
  "includeDetails": false,
  "maxConcurrency": 3
}' |
apify call automation-lab/otodom-property-scraper --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "command": "npx",
            "args": [
                "mcp-remote",
                "https://mcp.apify.com/?tools=automation-lab/otodom-property-scraper",
                "--header",
                "Authorization: Bearer <YOUR_API_TOKEN>"
            ]
        }
    }
}

```

## OpenAPI specification

Download the OpenAPI definition: https://api.apify.com/v2/acts/ceXeKSL8IxbyU6B5R/builds/8RLMwjcJRIZqVeWLv/openapi.json
