# Otodom Price Stats (`lowlanddata/otodom-price-stats`) Actor

One run samples Otodom and delivers a price-per-square-meter table by Polish city: median, quartiles, median price and surface per city, with the sample size attached. Asking prices, honestly labeled. Computed from listings that never contained personal data.

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

## Pricing

from $17.91 / 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?

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 Price Stats — PLN/m² by city, in one run

What does a square meter cost in Poland right now? This actor answers with data instead of listings. One run samples **Otodom.pl** — Poland's largest property portal — newest listings first, groups them by city and delivers one statistics row per city: median asking price per m² in złoty, the 25th and 75th percentile around it, the median price and the median surface. The "ile kosztuje metr" number, as a dataset.

It is built on the [Otodom Scraper](https://apify.com/lowlanddata/otodom-scraper) and inherits its guarantee: the aggregates are computed from already privacy-clean listing data. Owner and agent identity is never collected anywhere in the chain — there is structurally nothing personal in your dataset.

### Quick start (30 seconds)

1. Click **Start**. The default input samples flats for sale across all of Poland and returns a PLN/m² row for every city with enough listings.
2. When the run finishes — typically under a minute — open the dataset's **Overview** tab for a ready-made city table, or **Export** it as CSV/Excel/JSON.

Optional knobs: sale or rent, property type, a location path to zoom into one region, a price band, and the sampling depth.

### What you can build with it

- **Compare cities before investing.** One country-wide run puts Kraków, Warszawa and ten other cities side by side — median PLN/m², quartiles and sample size in one table. A real default-style run returned 12 city rows from 296 listings: Kraków at 15,796 PLN/m² (n=68), Warszawa at 16,660 (n=27), Kielce at 10,801 (n=13).
- **Track the market as a series.** Schedule the same input weekly and each dataset is one dated snapshot — a PLN/m² time series per city with no scraping pipeline to maintain.
- **Put a number in an article.** Analysts and journalists get a citable, dated figure with its sample size attached, computed the same way every run.
- **Benchmark your own listings.** Agencies and sellers see where an asking price sits against the city's p25–p75 band.
- **Plan a relocation.** Run once for sale, once for rent, and compare what a move to Gdańsk or Wrocław actually costs per square meter.

### What you get

Each city is one dataset row:

```json
{
  "location": "cala-polska",
  "transaction": "sale",
  "propertyType": "flat",
  "city": "Warszawa",
  "sampleSize": 42,
  "medianPricePerM2Pln": 15900,
  "p25PricePerM2Pln": 12800,
  "p75PricePerM2Pln": 18400,
  "medianPricePln": 780000,
  "medianAreaM2": 52,
  "currency": "PLN"
}
```

Field notes, so you know exactly what you are buying:

- `medianPricePerM2Pln` is the median **asking** price per m² — what sellers want, not what buyers paid. Otodom publishes no transaction prices; nobody scraping the site has them.
- `sampleSize` is in every row on purpose: a median over 68 listings is worth more than one over 13. Trust wide samples more.
- `p25PricePerM2Pln` / `p75PricePerM2Pln` frame the middle half of the market — the spread matters as much as the median.
- `medianPricePln` and `medianAreaM2` are medians of the same city sample; for rent runs the price is monthly złoty.
- Rows are sorted by sample size, largest first, and a city only gets a row once at least 5 sampled listings carry a price per m².

### How much does it cost?

$1.99 per 1,000 rows delivered, pay-as-you-go — no subscription, no charge for empty or failed runs. Since a run typically delivers 10–30 city rows, **a full country snapshot costs about $0.02–0.06**. A weekly schedule for a year lands around $1–3 total.

The price is all-inclusive — platform usage is covered, with no separate compute or proxy charges. Datacenter proxies are sufficient. Runs finish in under a minute at the default sampling depth.

Free-plan runs are capped at 25 rows — more than a typical run produces, so the free plan shows you the real product.

### Not technical? Let your AI assistant set it up

Copy this into ChatGPT, Claude or any AI assistant, fill in the one line, and follow the conversation:

```text
Help me set up the "Otodom Price Stats" actor on Apify
(https://apify.com/lowlanddata/otodom-price-stats). Guide me one step at a time.

What I want to know: [E.G. "median flat price per m² in the big Polish cities,
updated weekly"]

Guide me to:
1. Propose my input values: transaction sale or rent, propertyType, location
   (an Otodom URL path like "malopolskie" or the default "cala-polska"),
   an optional priceMinPln/priceMaxPln band, and samplePages (more pages =
   steadier medians).
2. Create a free Apify account (apify.com), open the actor page, paste the
   values into the Input form, and start a run.
3. Set up a weekly Schedule in the Apify Console with the same input, so each
   dataset becomes one dated snapshot of PLN/m² by city.
4. Show me how to export the city table as CSV/Excel, or read it from the API
   if I code.
5. If the results are what I wanted, remind me at the end to leave a quick
   rating on the actor page, and to report anything broken or missing on its
   Issues tab.
```

### Input

| Field                | Description                                                                                     |
| -------------------- | ----------------------------------------------------------------------------------------------- |
| `transaction`        | `sale` (default) or `rent`.                                                                     |
| `propertyType`       | `flat` (default), `house`, `room`, `plot`, `garage`, `office`, `commercial`.                    |
| `location`           | Otodom URL location path: `cala-polska` (default, country-wide), `mazowieckie`, or a full path. |
| `priceMinPln`        | Only sample listings priced at least this many złoty (per month for rentals).                   |
| `priceMaxPln`        | Only sample listings priced at most this many złoty (per month for rentals).                    |
| `samplePages`        | Result pages sampled, 2–40 (default 14, ~36 listings each). More pages = steadier medians.      |
| `maxItems`           | Upper bound on city rows delivered (default 100; free plans cap at 25).                         |
| `proxyConfiguration` | Proxy settings; keep Apify proxy enabled.                                                       |

No field is required — the default input produces a country-wide flats-for-sale city table. The run's status message reports the outcome in plain words, e.g. "12 city rows from 296 sampled listings".

### Use it from your code

Runs finish in well under the sync-call limit, so one HTTP call returns the finished city table:

```bash
curl "https://api.apify.com/v2/acts/lowlanddata~otodom-price-stats/run-sync-get-dataset-items?token=<YOUR_API_TOKEN>" \
  -X POST -H "Content-Type: application/json" \
  -d '{"transaction": "sale", "propertyType": "flat", "location": "cala-polska"}'
```

Node.js:

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

const client = new ApifyClient({ token: '<YOUR_API_TOKEN>' });
const run = await client.actor('lowlanddata/otodom-price-stats').call({
  location: 'malopolskie',
  samplePages: 20,
});
const { items } = await client.dataset(run.defaultDatasetId).listItems();
```

Python:

```python
from apify_client import ApifyClient

client = ApifyClient("<YOUR_API_TOKEN>")
run = client.actor("lowlanddata/otodom-price-stats").call(
    run_input={"transaction": "rent", "location": "cala-polska"})
rows = client.dataset(run["defaultDatasetId"]).list_items().items
```

Schedules, webhooks and the Make/Zapier/n8n integrations all work out of the box — this is a standard Apify actor.

### Use it with AI agents (MCP)

Claude, Cursor and other MCP-capable agents can run this actor as a tool through Apify's hosted MCP server — the agent asks the market question, runs the sampling and reads the city table itself.

Claude Code:

```bash
claude mcp add apify --transport http "https://mcp.apify.com?actors=lowlanddata/otodom-price-stats"
```

Cursor or Claude Desktop (add a custom connector / MCP server with this URL):

```text
https://mcp.apify.com?actors=lowlanddata/otodom-price-stats
```

Sign in with your Apify account when prompted — runs are billed to it. Setup details per client: [Apify MCP docs](https://docs.apify.com/platform/integrations/mcp).

Prompts that work once connected:

- "What is the median price per square meter for flats in Poland's big cities right now?"
- "Compare rent per m² between the cities of małopolskie, sampled from Otodom."
- "Run the Otodom price stats weekly input and tell me which city moved most since the numbers I pasted."

### Is it legal to scrape Otodom?

This actor sits one step further from the question than most scrapers: it delivers **aggregates**, not listings. The medians are computed from the same fixed whitelist of fields as the parent Otodom Scraper — prices, areas, cities — and owner names, agent contacts, street addresses and coordinates are never collected at any point in the chain. What the GDPR and Poland's UODO regulate is personal data; a table of city medians contains none by construction.

Requests are paced, the sampling load on the site is a handful of search pages, and no anti-bot protection is bypassed.

### Is there an Otodom API for price statistics?

No. Otodom publishes no public API, and the market reports that exist are periodic PDFs, not data you can call. This actor is the practical alternative: a fresh, dated PLN/m² table per city through one HTTP call, on a schedule, or as an MCP tool for AI agents.

### Does Otodom block scrapers?

Otodom serves its search pages openly to ordinary requests, and this actor stays inside that welcome: it reads a limited number of search pages, paced, over standard datacenter proxies. The occasional temporary block resolves on retry — and because a run is small, retrying costs seconds, not minutes.

### How do I track PLN/m² over time?

Create a weekly **Schedule** in the Apify Console with the exact same input every time — same transaction, property type, location and `samplePages`. Each run's dataset is then one dated, comparable snapshot: same sampling depth, same median method. Export the datasets (or read them from the API) and you have a per-city PLN/m² time series with `sampleSize` attached to every point. Keep the input stable — changing `samplePages` or the location mid-series changes what the medians are measuring.

### FAQ

**How much is a square meter of a flat in Warsaw?** Run the default input and read the `Warszawa` row — median asking PLN/m² with quartiles, computed from the newest listings at run time. In a recent country-wide sample it was 16,660 PLN/m².

**Ile kosztuje metr kwadratowy mieszkania? / What does a square meter cost in Poland?** That question is this actor's output: one row per city with the median asking price per m² in złoty, refreshed on every run.

**Are these transaction prices or asking prices?** Asking prices. Otodom lists what sellers ask; actual sale prices are not public there. Asking medians still track the market well — just read them as the seller side of the negotiation.

**How is the median calculated?** Per city, over every sampled listing that carries a price per m², using nearest-rank quantiles — a method that behaves well on city-sized samples and never invents values between listings.

**Why is my city not in the results?** A city needs at least 5 sampled listings with a price per m² to earn a row — fewer than that and a median would be noise. Raise `samplePages` or point `location` at the city's region so more of its listings enter the sample.

**Can I get rent per square meter instead?** Yes — set `transaction: "rent"`. The same table comes back in monthly złoty: median rent per m², median rent, median surface.

**How many cities does one run cover?** Whatever the sample supports — a default country-wide run typically yields 10–30 rows. Bigger `samplePages` values surface more mid-size cities; a regional `location` trades breadth for depth.

**Can I trust a row with a small sample?** Trust it less — that is why `sampleSize` is in every row. An n=68 median is a solid market signal; an n=6 median is an indication. For steadier small-city numbers, raise `samplePages` or narrow the location.

**Why do the numbers differ slightly between two runs?** Each run samples the newest listings, so the sample shifts as the market does. That is the point for tracking — and why comparisons should use the same input at the same depth.

**Does country-wide sampling favor big cities?** Yes — cities with more listings fill the newest-first sample faster, so Warszawa and Kraków get wide samples while small cities may miss the 5-listing bar. Zoom `location` into a region to give its cities more weight.

**Can I limit the stats to one region or city?** Yes — `location` takes any Otodom URL path: `pomorskie` for a voivodeship or a full city path. The stats then describe only that slice.

**Can I filter by price band?** Yes — `priceMinPln` / `priceMaxPln` restrict the sampled listings, so you can compute, say, the m² price of flats under 600,000 zł only.

**Does it cover houses, plots or commercial property?** Yes — `propertyType` takes `house`, `room`, `plot`, `garage`, `office` and `commercial` besides flats. Note that fewer plot and commercial listings publish a per-m² price, so samples run thinner there.

**How do I check Polish property prices weekly?** Schedule this actor with a fixed input — each dataset is one dated snapshot, and the series is comparable because the method never changes. See "How do I track PLN/m² over time?" above.

**Can I export the table to Excel or CSV?** Yes — every dataset exports as CSV, Excel, JSON or XML from the Apify Console or API. The Overview tab already shows it as a clean city table.

**How much does one run cost?** Rows are billed at $1.99 per 1,000, so a typical 10–30-row run costs $0.02–0.06, all-inclusive.

**Is this GDPR-safe?** Yes, by construction: the output is city-level aggregates computed from already-whitelisted listing data. No owner or agent identity exists anywhere in the pipeline, so none can leak into your dataset.

**Can I get the underlying listings too?** Not from this actor — it delivers the answer, not the rows behind it. For the raw listings with prices, areas and price per m², use the parent [Otodom Scraper](https://apify.com/lowlanddata/otodom-scraper).

**Can ChatGPT or Claude use this actor?** MCP-capable agents (Claude, Cursor and others) can run it directly through Apify's hosted MCP server and read the city table themselves. ChatGPT can guide you through the Console setup using the prompt above.

### Related scrapers

Same privacy-clean output, same maintainer:

- [Otodom Scraper](https://apify.com/lowlanddata/otodom-scraper) — the parent: the individual Otodom listings behind these medians, as structured JSON.
- [Subito Real Estate Scraper](https://apify.com/lowlanddata/subito-real-estate-scraper) — Italian property listings with computed price per m².
- [Immoweb Scraper](https://apify.com/lowlanddata/immoweb-scraper) — Belgium's largest real-estate portal.

### Troubleshooting

The actor fails fast with the reason in the run's status message:

- **"No city reached 5 listings with a price per m2 — the sample is too thin. Raise samplePages or widen the location."** — the input was too narrow (a tight price band, a small location, or few sample pages). Do what the message says.
- **A block reported on the first request** — temporary; retry in a few minutes, a fresh proxy session usually lands cleanly.
- **Fewer rows than expected** — either the free-plan 25-row cap, or the honest answer: only that many cities cleared the 5-listing bar. Raise `samplePages` to bring more cities in.
- **A city's median looks off** — check its `sampleSize` first; thin samples swing. Re-run with more `samplePages` before concluding the market moved.

### Support

Found an issue, or want a statistic this actor should compute? Open an issue on the actor's **Issues tab** — reports get fixed, this actor is actively maintained.

Working well for you? A **rating on this page** takes ten seconds and helps other buyers find it — it is also the clearest signal of what to build next.

# Actor input Schema

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

Search properties for sale (sprzedaz) or for rent (wynajem).

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

What kind of property to search.

## `location` (type: `string`):

Otodom location path as it appears in the site's URL: "cala-polska" for all of Poland (default), a voivodeship like "mazowieckie", or a full path like "mazowieckie/warszawa/warszawa/warszawa".

## `priceMinPln` (type: `integer`):

Only include listings priced at least this many złoty. For rentals this is złoty per month.

## `priceMaxPln` (type: `integer`):

Only include listings priced at most this many złoty. For rentals this is złoty per month.

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

Upper bound on city rows delivered. Free-plan runs are capped at 25.

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

Proxy settings. Keep Apify proxy enabled.

## `samplePages` (type: `integer`):

Result pages sampled (2-40, ~36 listings each). More pages = steadier medians per city.

## Actor input object example

```json
{
  "transaction": "sale",
  "propertyType": "flat",
  "location": "cala-polska",
  "maxItems": 25,
  "proxyConfiguration": {
    "useApifyProxy": true
  },
  "samplePages": 14
}
```

# Actor output Schema

## `rows` (type: `string`):

One price-per-m2 statistics row per city.

# 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 = {
    "transaction": "sale",
    "propertyType": "flat",
    "location": "cala-polska",
    "maxItems": 25,
    "proxyConfiguration": {
        "useApifyProxy": true
    },
    "samplePages": 14
};

// Run the Actor and wait for it to finish
const run = await client.actor("lowlanddata/otodom-price-stats").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 = {
    "transaction": "sale",
    "propertyType": "flat",
    "location": "cala-polska",
    "maxItems": 25,
    "proxyConfiguration": { "useApifyProxy": True },
    "samplePages": 14,
}

# Run the Actor and wait for it to finish
run = client.actor("lowlanddata/otodom-price-stats").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 '{
  "transaction": "sale",
  "propertyType": "flat",
  "location": "cala-polska",
  "maxItems": 25,
  "proxyConfiguration": {
    "useApifyProxy": true
  },
  "samplePages": 14
}' |
apify call lowlanddata/otodom-price-stats --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,lowlanddata/otodom-price-stats"
        }
    }
}

```

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/MQjPvDYDp8ZnM2HTw/builds/6oQm5gECIkWypJc4i/openapi.json
