# Otodom Scraper - Poland Real Estate Listings, Prices & m² (`scrapewise/otodom-scraper`) Actor

Scrape Otodom.pl without login: flats, houses, plots and commercial property for sale or rent in Poland, with price, price per m², area, rooms, floor, city, district, agency or developer, optional full listing page. US$ 1.50 per 1,000.

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

## Pricing

from $1.70 / 1,000 listing delivereds

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: Poland real estate listings, prices and price per m²

Scrape [Otodom.pl](https://www.otodom.pl/), Poland's largest real estate portal, **without an account or
cookies**. Pick a city or voivodeship (Warszawa, Kraków, Gdańsk, Wrocław, mazowieckie...), sale or rent and a property
type, or paste any Otodom search link, and get every listing with **price, price per m², monthly fee (czynsz), area,
rooms, floor, street, district, city, voivodeship, advertiser type (private owner, agency or developer), agency or
developer name, photos and dates**. Turn on the full listing page to add the **complete description, all
characteristics (build year, building type, heating, ownership, condition, lift, balcony, parking...) and the
approximate map position**.

Use Otodom's own filters: **price, area, rooms, primary or secondary market, private owners only, added in the last N
days, sort by newest or price**. Big searches follow the pagination to the end (72 listings per page), so you can pull
thousands of listings per city.

Built for property investors and flippers, agencies watching competitors and new stock, developers pricing new
projects, relocation and rental platforms, banks and valuers building price-per-m² models, and researchers tracking
the Polish housing market.

**US$ 1.50 per 1,000 listings. Full listing page +US$ 1.00 per 1,000, only when you ask for it. No start fee, no
monthly fee. Error rows, duplicates and listings outside your filters are free.**

### At a glance

- **Price per 1,000 listings, Free plan:** US$ 1.50
- **Successful runs, last 30 days:** New (30 of 30 pages in our tests)
- **Otodom filters (price, area, rooms, market, private owners, last N days, sort):** Yes, all as input fields
- **City or voivodeship by name, no link needed:** Yes (43 cities, 16 voivodeships)
- **Full listing page (description, characteristics, map position):** Optional, +US$ 1.00 / 1,000
- **Resumes after a platform restart without charging twice:** Yes

### What you can do with it

- **Price-per-m² map of a city.** `locations: ["Warszawa"]`, `maxResultsPerSearch: 5000`: every flat for sale with
  price, price per m², area, rooms and district.
- **Daily feed of new listings.** `postedWithinDays: 1`, `sortBy: "newest"`, schedule it every morning.
- **Owner-direct deals (no commission).** `ownerType: "private"` keeps only private owners.
- **Rental market study.** `transaction: "rent"` gives monthly rent plus the extra monthly fee (czynsz).
- **Developer and new-build tracking.** `market: "primary"`, or `propertyType: "investment"` for the project cards.
- **Reuse a search you built on Otodom.** Paste the link from your browser; all its filters are kept.
- **Feed a spreadsheet, BI tool or valuation model.** Export CSV, Excel or JSON, or call it through the Apify API.

### Input

| Field | What it does |
|---|---|
| `locations` | Cities or voivodeships, one per line (Warszawa, Kraków, Gdańsk, mazowieckie...), or an Otodom location id. Empty = all of Poland. |
| `transaction` | `sale` (default) or `rent`. |
| `propertyType` | `flat` (default), `studio`, `house`, `plot`, `commercial`, `warehouse`, `garage`, `room`, `investment`. |
| `startUrls` | Otodom search links (`/pl/wyniki/...`, keep their own filters) and listing links (`/pl/oferta/...`). |
| `maxResultsPerSearch` | Listings per location or search link. Default 100, up to 20,000. |
| `maxItems` | Hard cap on charged listings for the whole run. |
| `priceMin`, `priceMax` | Total price in PLN (monthly rent for rentals). |
| `areaMin`, `areaMax` | Floor area in m². |
| `rooms` | `1`, `2`, `3`, `4`, `5`, `6+`. |
| `market` | `all`, `primary` (new from developer), `secondary` (resale). |
| `ownerType` | `all`, `private`, `agency`, `developer`. |
| `postedWithinDays` | Only listings added in the last N days. |
| `sortBy` | `relevance`, `newest`, `priceLow`, `priceHigh`. |
| `excludeKeywords` | Skip listings with these words in the title (free). |
| `includeDevelopmentProjects` | Keep the developer project cards Otodom mixes into results (skipped by default, free). |
| `includeDetails` | Add the full listing page (charged separately, only when read). |

Example: 2 and 3-room resale flats in Kraków from private owners, 40 to 70 m², newest first, with the full page.

```json
{
  "locations": ["Kraków"],
  "transaction": "sale",
  "propertyType": "flat",
  "rooms": ["2", "3"],
  "areaMin": 40,
  "areaMax": 70,
  "market": "secondary",
  "ownerType": "private",
  "sortBy": "newest",
  "includeDetails": true,
  "maxResultsPerSearch": 500
}
```

An empty input runs a small example (20 flats for sale in Warszawa), so the Actor never fails on a blank form.

### Output

One real row from a local test run on 2026-09-29 (Gdańsk, with the full listing page; photo list and description
shortened):

```json
{
  "type": "listing",
  "listingId": "68440135",
  "url": "https://www.otodom.pl/pl/oferta/2-pok-i-zaspa-mlyniec-i-piekny-widok-ID4Dap9",
  "title": "2 pok. I Zaspa Młyniec I Piękny widok",
  "transaction": "sale",
  "propertyType": "flat",
  "price": 649000.0,
  "currency": "PLN",
  "priceHidden": false,
  "pricePerM2": 13464.73,
  "additionalMonthlyFee": 600.0,
  "areaM2": 48.2,
  "plotAreaM2": null,
  "rooms": 2,
  "floor": 10,
  "street": "ul. Startowa",
  "district": "Zaspa-Młyniec",
  "city": "Gdańsk",
  "province": "pomorskie",
  "locationId": "pomorskie/gdansk/gdansk/gdansk/zaspa--mlyniec",
  "advertiserType": "agency",
  "agencyName": "DOMO Nieruchomości",
  "agencyType": "agency",
  "isPromoted": false,
  "tags": ["separate_kitchen", "top_floor"],
  "imageUrl": "https://ireland.apollo.olxcdn.com/v1/files/.../image;s=1280x1024;q=80",
  "totalImages": 15,
  "createdAt": "2026-09-30 01:10:51",
  "firstPublishedAt": "2026-09-20T18:34:45Z",
  "detailsLoaded": true,
  "description": "Szukasz funkcjonalnych dwóch pokoi na Zaspie? Świetnie trafiłeś! ...",
  "characteristics": {
    "price": "649 000 zł", "rent": "600 zł", "price_per_m": "13 464,73 zł/m²", "m": "48,2 m²",
    "market": "secondary", "building_type": "block", "floor_no": "floor_10",
    "construction_status": "to_renovation", "building_material": "concrete_plate",
    "building_ownership": "full_ownership", "heating": "urban", "rooms_num": "2", "build_year": "1970",
    "building_floors_num": "10"
  },
  "attributes": {"extras_types": ["lift", "separate_kitchen"], "security_types": "entryphone", "lift": "y"},
  "buildYear": 1970,
  "buildingFloors": 10,
  "market": "secondary",
  "latitude": 54.396,
  "longitude": 18.597,
  "mapRadiusMeters": 0,
  "source": "location:Gdańsk",
  "scrapedAt": "2026-09-30T02:48:15Z",
  "errorCode": null,
  "error": null
}
```

- `price` is the total price (sale) or the monthly rent (rent); `additionalMonthlyFee` is the extra monthly fee
  (czynsz) when the listing shows one. `priceHidden` is true when the advertiser hides the price.
- `advertiserType` is `private`, `agency` or `developer`. Agency and developer names are returned; **private owners are
  never named**.
- Without `includeDetails` the row has everything from the results page (the fields above up to `firstPublishedAt`,
  plus a short description and the first photos).

The run also saves `SEARCH_SUMMARY` in its key-value store: for each location or link, the search URL, how many
listings Otodom has for it, pages read and rows delivered (free).

#### Error rows (never charged)

| errorCode | When |
|---|---|
| `NO_RESULTS` | Otodom has no listings for the search with your filters. |
| `NOT_FOUND` | Unknown location, a search link that does not exist, or a listing that expired. |
| `INVALID_URL` | The link is not an Otodom search or listing link. |
| `BLOCKED` | Otodom refused every attempt for that search. |
| `INVALID_INPUT` | A field has a value the Actor cannot use (the message says which). |
| `UNEXPECTED` | Anything else; the rest of the run continues. |

### Pricing

Pay per event, no start fee:

| Event | Price per 1,000 (Free plan) | Charged for |
|---|---|---|
| Listing delivered | **US$ 1.50** | each unique listing |
| Full listing page delivered | **US$ 1.00** | each listing whose full page was read (`includeDetails`, or a listing link) |

Examples: 1,000 listings cost US$ 1.50; with the full page, US$ 2.50. Error rows, duplicates, skipped project cards
and filtered-out listings are free, and `maxItems` is a hard stop.

### How it works and limits

- It reads the same public pages the Otodom website shows to any visitor. Refused requests are retried. No login.
- Results come 72 per page in Otodom's own order; the Actor stops at the last page Otodom has.
- The built-in names cover the 43 largest cities and the 16 voivodeships. For a smaller town or a district, paste the
  Otodom search link (or its location id).
- In our tests: 200 listings in 21 s, 30 of 30 result pages in a row (3,000 Warsaw flats in 54 s); with the full
  page, 100 listings in about 70 s.
- **No personal data.** The Actor never returns phone numbers, e-mail addresses or the names of private owners or
  individual agents. Phone numbers and e-mails written inside titles and descriptions are replaced by
  `[contact removed]`. Map positions are rounded to about 100 m.

### FAQ

**Do I need an Otodom account?** No. Everything comes from public pages.

**Can I get the seller's phone number?** No. Otodom hides it behind a click and it is often a private person's number;
this Actor does not collect it.

**How many listings can I get?** As many as Otodom shows for the search (over 150,000 flats for sale in all of Poland).
Split very large jobs by city or price range.

**What if the run is restarted by the platform?** Listings already in the dataset are not charged again: the Actor
reads its own dataset when it starts and skips what was delivered.

### Changelog

- **0.1 (2026-09-29):** first version: cities and voivodeships by name, Otodom search and listing links, all Otodom
  filters, optional full listing page, resume after restart.

Independent tool, not affiliated with Otodom or OLX Group.

# Actor input Schema

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

Polish cities or voivodeships, one per line (for example: Warszawa, Kraków, Gdańsk, Wrocław, mazowieckie). The 43 largest cities and all 16 voivodeships are built in, with or without Polish letters (Warsaw and Cracow work too). Any other place: paste its Otodom location id (e.g. mazowieckie/piaseczynski/piaseczno/piaseczno) or an Otodom search link below. Empty = all of Poland.

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

Listings for sale (sprzedaż) or for rent (wynajem).

## `propertyType` (type: `string`):

Otodom property category.

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

Otodom search links with any filters you set on the website (https://www.otodom.pl/pl/wyniki/...) and single listing links (https://www.otodom.pl/pl/oferta/...). Search links keep their own filters and ignore the filter fields below.

## `maxResultsPerSearch` (type: `integer`):

How many listings to take from each location or search link. Empty = 100. The Actor reads 72 listings per page and follows the pagination to the end.

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

Hard cap on charged listings for the whole run, across all locations and links. Empty or 0 = no cap besides the per-search limit.

## `priceMin` (type: `integer`):

Minimum total price in PLN (monthly rent for rentals).

## `priceMax` (type: `integer`):

Maximum total price in PLN (monthly rent for rentals).

## `areaMin` (type: `integer`):

Minimum floor area in square metres.

## `areaMax` (type: `integer`):

Maximum floor area in square metres.

## `rooms` (type: `array`):

Number of rooms (flats and houses). Empty = any.

## `market` (type: `string`):

Primary market (new builds sold by developers) or secondary market (resale).

## `ownerType` (type: `string`):

Otodom's own advertiser filter. 'Private owners only' gives offers without an agency commission.

## `postedWithinDays` (type: `integer`):

Only listings added in the last N days (for example 1 for a daily feed, 7 for a weekly one). Empty = any date.

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

Order of the results, as on Otodom.

## `excludeKeywords` (type: `array`):

Words, one per line (for example: licytacja, udział). Listings with any of them in the title or short description are skipped and not charged.

## `includeDevelopmentProjects` (type: `boolean`):

Otodom mixes developer project cards (a whole new development with a 'price from' per m², no unit price or area) into the results. They are skipped by default and not charged; tick this to keep them. Searching the 'New developments' property type always returns them.

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

Also open each listing page and add the full description, all characteristics (build year, heating, ownership, lift, balcony, parking...), approximate map position and every photo. Charged as a separate event, only when the page was read.

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

Apify Proxy is the default and works for Otodom.

## Actor input object example

```json
{
  "locations": [
    "Warszawa"
  ],
  "transaction": "sale",
  "propertyType": "flat",
  "market": "all",
  "ownerType": "all",
  "sortBy": "relevance",
  "includeDevelopmentProjects": false,
  "includeDetails": false,
  "proxyConfiguration": {
    "useApifyProxy": true
  }
}
```

# Actor output Schema

## `results` (type: `string`):

No description

## `resultsCsv` (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 = {
    "locations": [
        "Warszawa"
    ],
    "proxyConfiguration": {
        "useApifyProxy": true
    }
};

// Run the Actor and wait for it to finish
const run = await client.actor("scrapewise/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 = {
    "locations": ["Warszawa"],
    "proxyConfiguration": { "useApifyProxy": True },
}

# Run the Actor and wait for it to finish
run = client.actor("scrapewise/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 '{
  "locations": [
    "Warszawa"
  ],
  "proxyConfiguration": {
    "useApifyProxy": true
  }
}' |
apify call scrapewise/otodom-scraper --silent --output-dataset

```

## MCP server setup

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