# Otodom & OLX Listing Deal Checker – Notarial Deed Prices (`kris_fili/otodom-listing-deal-checker`) Actor

Check Polish apartment listings (Otodom, OLX, Morizon, Gratka scrapers) against real sale prices from notarial deeds: fair value, % above or below, deal verdict.

- **URL**: https://apify.com/kris_fili/otodom-listing-deal-checker.md
- **Developed by:** [Krzysztof Filipiak](https://apify.com/kris_fili) (community)
- **Categories:** Real estate, Integrations, AI
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $20.00 / 1,000 listing checks

This Actor is paid per event. You are not charged for the Apify platform usage, but only a fixed price for specific events.

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 & OLX Listing Deal Checker – asking prices vs notarial deed prices

**Is this listing a bargain or overpriced?** Feed the apartment listings you scraped from **Otodom, OLX, Morizon, Gratka, Domiporta** (or any other source) and get, for every listing, the value of the apartment according to **what buyers actually paid in notarial deeds nearby** – plus how many percent the asking price is above or below it and a clear verdict.

The values come from Poland's official **Real Estate Price Register (Rejestr Cen Nieruchomości, RCN)** – the register of notarial deeds kept by Polish counties and published by GUGiK. No other listing scraper gives you this: portals only show asking prices.

![Median apartment price per m² from notarial deeds in Polish cities – refreshed weekly](https://api.apify.com/v2/key-value-stores/kris_fili~promotor-publiczne/records/mapa-ceny-en.png)

### What you get for each listing

- **deed value** of the apartment (PLN) and **price per m²** from comparable deeds, with a **range**
- **asking vs deed value** in % (e.g. +7.4% = asking price 7.4% above what similar apartments sold for)
- **typical premium** of the other listings from the same city in your input, and the listing's position against it
- a **verdict**: `great_deal`, `good_deal`, `typical`, `expensive` or `very_expensive` – and whether it is reliable
- **confidence** (high / medium / low), number of comparable sales and the search radius
- the **3 most similar sales** (`similarSales`: date, price, area, price per m², floor, street, distance)

Plus a **report** with the best deals and a table per city (Output tab → *Best deals*), and a JSON summary.

### How to use it with a scraper

1. Run any listings scraper from Apify Store – for example an Otodom, OLX or Morizon scraper.
2. Open this Actor and pick that run's dataset in **Listings dataset**. Start.
3. Open **Best deals** in the Output tab.

**Automatically after every scrape:** on the scraper's **Integrations** tab click *Add integration*, choose this Actor – each finished scrape is then checked without any copying. Prices, areas, rooms, floors, coordinates, cities and streets are recognised automatically from the usual field names (`price`, `area`, `rooms`, `floor`, `market`, `latitude`, `longitude`, `city`, `district`, `street`, `url`…). Unusual names can be mapped in `fieldMapping`, e.g. `{"price": "pricing.total", "area": "details.surface"}`.

You can also paste listings directly into `listings` (JSON).

### Example

Input (pasted listings – real run from October 2026):

```json
{
  "listings": [
    { "title": "2 pokoje, Kazimierz", "price": 1050000, "area": 52, "rooms": 2, "floor": 2, "market": "SECONDARY",
      "latitude": 50.051, "longitude": 19.9447, "city": "Kraków" },
    { "title": "3 pokoje, Mokotów", "price": 1150000, "area": 65, "rooms": 3, "floor": 4, "market": "SECONDARY",
      "latitude": 52.1935, "longitude": 21.0235, "city": "Warszawa" },
    { "title": "2 pokoje, Wrzeszcz", "price": 720000, "area": 48, "rooms": 2, "floor": 1, "market": "SECONDARY",
      "latitude": 54.38, "longitude": 18.604, "city": "Gdańsk" }
  ]
}
```

Results:

| Listing | Asking PLN/m² | Deed value PLN/m² | Asking vs deeds | Comparable sales |
|---|---|---|---|---|
| Kraków, Kazimierz · 52 m² · 1,050,000 PLN | 20,192 | 18,800 | +7.4% | 361 within 1 km |
| Warszawa, Mokotów · 65 m² · 1,150,000 PLN | 17,692 | 17,063 | +3.7% | 262 within 1 km |
| Gdańsk, Wrzeszcz · 48 m² · 720,000 PLN | 15,000 | 13,716 | +9.4% | 299 within 1 km |

One record (shortened):

```json
{
  "recordType": "listing",
  "listing": { "title": "2 pokoje, Kazimierz", "price": 1050000, "areaM2": 52, "rooms": 2, "city": "Kraków", "url": "https://…" },
  "askingPricePerM2Pln": 20192,
  "deedValuePln": 978000,
  "deedPricePerM2Pln": 18800,
  "deedValueRangePln": { "low": 793000, "high": 1160000 },
  "priceVsDeedValuePct": 7.4,
  "verdict": "typical",
  "verdictBasis": "fewer than 12 comparable listings in the input - fixed bands on the raw asking-vs-deed difference",
  "confidence": "medium",
  "comparablesUsed": 361,
  "searchRadiusM": 1000,
  "marketUsed": "secondary",
  "similarSales": [
    { "date": "2026-06-10", "priceTotalPln": 900000, "areaM2": 59.1, "pricePerM2Pln": 15228.43, "street": "Józefa", "distanceM": 149 }
  ]
}
```

With 12 or more listings from the same city in one run, each record also gets `typicalPremiumPct` (the median asking premium of those listings) and `relativeToTypicalPct`, and the verdict is based on them.

### How the verdict works

Asking prices are almost always above final sale prices (negotiation, and the register shows sales with a delay). So a listing is judged against the **typical premium of other listings from the same city** in your input (median of at least 12 listings):

| Listing vs typical premium | Verdict |
|---|---|
| 15 points or more below | `great_deal` |
| 7–15 points below | `good_deal` |
| within ±7 points | `typical` |
| 7–15 points above | `expensive` |
| 15 points or more above | `very_expensive` |

With fewer listings from a city, fixed bands on the raw difference are used (≤ −10% great deal, ≤ 0% good deal, < +15% typical, < +25% expensive). Verdicts based on few sales or only a city name are marked `verdictReliable: false` and left out of *Best deals*.

### How the deed value is calculated

The same method as in [Poland Apartment Valuation](https://apify.com/kris_fili/poland-apartment-valuation): apartment sales from notarial deeds within 1 km (2.5 km or 6 km when there are too few), from the last 24 months by default, market sales of full ownership only. Each sale is adjusted to today's price level with the local quarterly trend and weighted by distance, floor area, rooms, floor and recency; the value is the weighted median price per m² × the listing's area. Resale listings are compared with resale deeds, new-build listings with primary-market deeds (both markets when there are too few).

The deed value is an automated estimate from comparable sales, not a formal appraisal (*operat szacunkowy*): it does not see the condition, view or standard of the apartment.

### Who uses it

- **Investors and flippers** – screen hundreds of listings a day and look only at the real bargains.
- **Real estate agents** – show sellers how their asking price compares with what was actually paid nearby.
- **Proptech and data teams** – enrich listing datasets with an official, sourced value per listing.
- **Lenders and analysts** – monitor how far asking prices run ahead of transaction prices, city by city.

### Pricing (pay per event)

| Event | Price |
|---|---|
| Listing check (only when a value is produced) | $0.02 |

Skipped listings – rentals, houses, plots, listings without a price, area or location, or with no comparable sales nearby – are **free** and listed with the reason. 1,000 checked listings cost $20.

### Data sources and privacy

- Notarial deeds: Rejestr Cen Nieruchomości, GUGiK – official bulk export (monthly), optionally topped up with the newest deeds from the live register (`includeNewestDeeds`).
- The register has no names of buyers or sellers; deed numbers and notaries are not used.
- Your listings are processed only in your run and are not stored anywhere else.

### Related Actors

- [Poland Apartment Valuation (AVM)](https://apify.com/kris_fili/poland-apartment-valuation) – a detailed valuation of one address with 10 comparable sales.
- [Poland Real Estate Transaction Prices](https://apify.com/kris_fili/poland-real-estate-transaction-prices) – area statistics and the full list of deeds.
- [Poland Housing Price Gap](https://apify.com/kris_fili/poland-housing-price-gap) – developers' asking prices vs deed prices.
- [SeLoger & Leboncoin Listing Deal Checker](https://apify.com/kris_fili/seloger-leboncoin-deal-checker) – the same for France, with DVF sale prices.

***

#### Po polsku – czy ogłoszenie to okazja? Porównanie cen ofertowych z cenami z aktów notarialnych

Wrzucasz ogłoszenia mieszkań z **Otodom, OLX, Morizon, Gratka** (np. wyniki scrapera z Apify), a agent przy każdym ogłoszeniu podaje **wartość mieszkania według cen transakcyjnych z aktów notarialnych** z okolicy (Rejestr Cen Nieruchomości), o ile procent cena ofertowa jest wyższa lub niższa i **werdykt: okazja, typowa cena albo za drogo** – na tle innych ofert z tego samego miasta. Dostajesz też raport z najlepszymi okazjami. Opłata 0,02 USD za sprawdzone ogłoszenie; pominięte (najem, domy, brak danych) są bezpłatne.

# Actor input Schema

## `datasetId` (type: `string`):

Pick the dataset of a listings scraper run – Otodom, OLX, Morizon, Gratka, Domiporta or any scraper whose items have a price, floor area and coordinates or city/street. When this Actor runs as an integration after a scraper, the dataset is passed automatically. / Zbiór danych z ogłoszeniami.

## `listings` (type: `array`):

Used only when no dataset is selected. Each item needs a price (PLN), floor area and coordinates (latitude/longitude) or a city and street. Field names are recognised automatically (price, area, rooms, floor, market, latitude, longitude, city, district, street, url).

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

Stop after this many listings from the dataset (1–20,000). Each checked listing is charged; skipped ones (rentals, houses, missing data) are free.

## `monthsBack` (type: `integer`):

How many months of sales to compare with (12–48). Older sales are adjusted to today's prices with the local trend.

## `includeComparables` (type: `boolean`):

Date, price, area and distance of the most similar deeds (no extra charge).

## `includeSkipped` (type: `boolean`):

Rentals, houses, listings without price or area… are written to the dataset as 'skipped' (free).

## `includeNewestDeeds` (type: `boolean`):

The monthly bulk export of the register is used by default. This also asks the live register for deeds from the last weeks – slower for large areas.

## `fieldMapping` (type: `object`):

Only if your scraper uses unusual field names. Dotted paths are allowed, e.g. {"price": "pricing.total", "area": "details.surface", "lat": "geo.lat", "lon": "geo.lng"}. Keys: price, area, rooms, floor, lat, lon, city, district, street, market, url, id, title, type.

## Actor input object example

```json
{
  "listings": [
    {
      "title": "2 pokoje, Kazimierz",
      "price": 1050000,
      "area": 52,
      "rooms": 2,
      "floor": 2,
      "market": "SECONDARY",
      "latitude": 50.051,
      "longitude": 19.9447,
      "city": "Kraków",
      "district": "Kazimierz",
      "street": "Józefa",
      "url": "https://example.com/listing-1"
    },
    {
      "title": "3 pokoje, Mokotów",
      "price": 1150000,
      "area": 65,
      "rooms": 3,
      "floor": 4,
      "market": "SECONDARY",
      "latitude": 52.1935,
      "longitude": 21.0235,
      "city": "Warszawa",
      "district": "Mokotów",
      "street": "Puławska",
      "url": "https://example.com/listing-2"
    },
    {
      "title": "2 pokoje, Wrzeszcz",
      "price": 720000,
      "area": 48,
      "rooms": 2,
      "floor": 1,
      "market": "SECONDARY",
      "latitude": 54.38,
      "longitude": 18.604,
      "city": "Gdańsk",
      "district": "Wrzeszcz",
      "url": "https://example.com/listing-3"
    }
  ],
  "maxListings": 1000,
  "monthsBack": 24,
  "includeComparables": true,
  "includeSkipped": true,
  "includeNewestDeeds": false,
  "fieldMapping": {}
}
```

# Actor output Schema

## `report` (type: `string`):

No description

## `results` (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 = {
    "listings": [
        {
            "title": "2 pokoje, Kazimierz",
            "price": 1050000,
            "area": 52,
            "rooms": 2,
            "floor": 2,
            "market": "SECONDARY",
            "latitude": 50.051,
            "longitude": 19.9447,
            "city": "Kraków",
            "district": "Kazimierz",
            "street": "Józefa",
            "url": "https://example.com/listing-1"
        },
        {
            "title": "3 pokoje, Mokotów",
            "price": 1150000,
            "area": 65,
            "rooms": 3,
            "floor": 4,
            "market": "SECONDARY",
            "latitude": 52.1935,
            "longitude": 21.0235,
            "city": "Warszawa",
            "district": "Mokotów",
            "street": "Puławska",
            "url": "https://example.com/listing-2"
        },
        {
            "title": "2 pokoje, Wrzeszcz",
            "price": 720000,
            "area": 48,
            "rooms": 2,
            "floor": 1,
            "market": "SECONDARY",
            "latitude": 54.38,
            "longitude": 18.604,
            "city": "Gdańsk",
            "district": "Wrzeszcz",
            "url": "https://example.com/listing-3"
        }
    ],
    "fieldMapping": {}
};

// Run the Actor and wait for it to finish
const run = await client.actor("kris_fili/otodom-listing-deal-checker").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 = {
    "listings": [
        {
            "title": "2 pokoje, Kazimierz",
            "price": 1050000,
            "area": 52,
            "rooms": 2,
            "floor": 2,
            "market": "SECONDARY",
            "latitude": 50.051,
            "longitude": 19.9447,
            "city": "Kraków",
            "district": "Kazimierz",
            "street": "Józefa",
            "url": "https://example.com/listing-1",
        },
        {
            "title": "3 pokoje, Mokotów",
            "price": 1150000,
            "area": 65,
            "rooms": 3,
            "floor": 4,
            "market": "SECONDARY",
            "latitude": 52.1935,
            "longitude": 21.0235,
            "city": "Warszawa",
            "district": "Mokotów",
            "street": "Puławska",
            "url": "https://example.com/listing-2",
        },
        {
            "title": "2 pokoje, Wrzeszcz",
            "price": 720000,
            "area": 48,
            "rooms": 2,
            "floor": 1,
            "market": "SECONDARY",
            "latitude": 54.38,
            "longitude": 18.604,
            "city": "Gdańsk",
            "district": "Wrzeszcz",
            "url": "https://example.com/listing-3",
        },
    ],
    "fieldMapping": {},
}

# Run the Actor and wait for it to finish
run = client.actor("kris_fili/otodom-listing-deal-checker").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 '{
  "listings": [
    {
      "title": "2 pokoje, Kazimierz",
      "price": 1050000,
      "area": 52,
      "rooms": 2,
      "floor": 2,
      "market": "SECONDARY",
      "latitude": 50.051,
      "longitude": 19.9447,
      "city": "Kraków",
      "district": "Kazimierz",
      "street": "Józefa",
      "url": "https://example.com/listing-1"
    },
    {
      "title": "3 pokoje, Mokotów",
      "price": 1150000,
      "area": 65,
      "rooms": 3,
      "floor": 4,
      "market": "SECONDARY",
      "latitude": 52.1935,
      "longitude": 21.0235,
      "city": "Warszawa",
      "district": "Mokotów",
      "street": "Puławska",
      "url": "https://example.com/listing-2"
    },
    {
      "title": "2 pokoje, Wrzeszcz",
      "price": 720000,
      "area": 48,
      "rooms": 2,
      "floor": 1,
      "market": "SECONDARY",
      "latitude": 54.38,
      "longitude": 18.604,
      "city": "Gdańsk",
      "district": "Wrzeszcz",
      "url": "https://example.com/listing-3"
    }
  ],
  "fieldMapping": {}
}' |
apify call kris_fili/otodom-listing-deal-checker --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,kris_fili/otodom-listing-deal-checker"
        }
    }
}
```

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/Ddm7Tulm09GowDh69/builds/bGONQfJIFfjaxibEH/openapi.json
