Otodom.pl Scraper - Property Listings, Prices & Rentals
Pricing
from $0.80 / 1,000 results
Otodom.pl Scraper - Property Listings, Prices & Rentals
Scrape Otodom.pl properties for sale and rent, including apartments, houses, plots, commercial spaces, garages, rooms and new developments. Build searches by city, transaction, property type and filters, or paste Otodom URLs directly.
Pricing
from $0.80 / 1,000 results
Rating
5.0
(1)
Developer
Abot API
Maintained by CommunityActor stats
2
Bookmarked
32
Total users
5
Monthly active users
a day ago
Last modified
Categories
Share
Otodom Scraper: Polish Property Listings, Prices, GPS & Agent Contacts
Otodom Scraper turns Otodom into a clean data feed of Polish property listings. Get price, area, rooms, full address, GPS coordinates, description, agent contact, building year, ownership, market type and the full feature list for apartments, houses, plots, commercial units, garages, new developments and rooms, for sale or for rent. Build a search from transaction, property type, city and filters, or paste any Otodom search URL, then export to JSON, CSV or Excel, or pull the results straight into your app through the API.
Why This Scraper?
- Two ways to search. Build a query from transaction, property type, cities and filters, or paste any Otodom search URL refined in your browser. No need to construct search URLs by hand.
- 80 cities plus all 16 voivodeships recognized by name. Warszawa, Kraków, Wrocław, Gdańsk, Łódź, Poznań, Szczecin, Sopot and more, built in. For a city or district that is not recognized, drop a URL into URL mode instead.
- 30+ fields per listing. SERP fields (price, area, rooms, agency, photo) plus optional detail-page enrichment (city and province, GPS coordinates, full description, agent phone, building year, ownership, full feature list).
- Detail depth on demand. Switch
fetchDetailson or off per run: SERP-only fields are always included, detail-page fields are added when you need them. - Forward pagination with cost caps. Walks every result page per search by default, with per-run and per-search limits so a multi-city search returns balanced results instead of exhausting the budget on the first city.
- Resume across runs. Continue an interrupted full-catalogue walk from a previous run or dataset ID without re-scraping listings you already collected.
- Incremental monitoring for scheduled runs. Turn on incremental mode for a recurring search and every row is flagged
NEW,UPDATED,UNCHANGED,REAPPEAREDorEXPIREDagainst what you collected last time.
Use Cases
- Real estate market research: track asking prices, price per square meter and inventory across cities, districts and property types over time.
- Property investment analysis: pull GPS-tagged listings with build year, ownership type and agent contact to compare deals across multiple cities.
- Lead generation for agents and brokers: filter by market type and advertiser type to find private-seller and secondary-market listings worth a call.
- Rental market tracking: compare rent, deposit and additional fees across cities, room counts and property types.
- Proptech and data products: feed structured Polish listings into a search app, price index or valuation model.
Data You Get
Sample shape, values are illustrative placeholders, not from a live listing.
| Field | Example |
|---|---|
id | "00000001" |
url | "https://www.otodom.pl/pl/oferta/sample-listing-ID00000" |
title | "Przytulne mieszkanie na Mokotowie, 2 pokoje" |
transaction | "sprzedaz" |
estate | "mieszkanie" |
priceTotal / priceCurrency | 750000 / "PLN" |
pricePerSquareMeter | 15000 |
areaInSquareMeters | 50.0 |
rooms | 2 |
floor / buildingFloors | "3" / 5 |
buildYear | 2010 |
marketType | "secondary" |
ownership | "full_ownership" |
buildingType | "block" |
city / province / district | "Warszawa" / "mazowieckie" / "Mokotów" |
latitude / longitude | 52.0000 / 21.0000 |
agencyName / advertiserType | "Sample Agency" / "agency" |
phone | "+48000000000" |
features | ["meble", "balkon", "winda"] |
imageUrl | "https://img.otodom.pl/api/v1/offers/sample-id/image" |
scrapedAt | "2026-04-28T00:00:00.000Z" |
How to Use
- Pick a mode:
search(build a query from transaction, property type, cities and filters) orurl(paste one or more Otodom search URLs). - Fill in the fields for that mode, and turn Fetch detail pages on if you need GPS coordinates, the full description, agent phone, building year, ownership or the feature list.
- Set Max pages and Max listings to control run size and cost, then click Start.
- Download the dataset as JSON, CSV or Excel, or read it through the API.
Search mode, basic Warsaw apartments for sale:
{"mode": "search","transaction": "sprzedaz","estate": "mieszkanie","locations": ["Warszawa"],"maxPages": 3,"maxListings": 50,"fetchDetails": true}
Search mode, rentals in three cities, filtered:
{"mode": "search","transaction": "wynajem","estate": "mieszkanie","locations": ["Warszawa", "Krakow", "Wroclaw"],"minPrice": 2000,"maxPrice": 4500,"minRooms": 2,"maxRooms": 3,"sortBy": "PRICE_ASC","maxPages": 2,"maxListings": 60,"fetchDetails": true}
Search mode, new developments only (primary market):
{"mode": "search","transaction": "sprzedaz","estate": "mieszkanie","locations": ["Warszawa"],"marketType": "primary","maxPages": 2,"maxListings": 30,"fetchDetails": true}
URL mode, paste any Otodom search URL:
{"mode": "url","urls": ["https://www.otodom.pl/pl/wyniki/sprzedaz/mieszkanie/mazowieckie/warszawa/warszawa/warszawa/mokotow"],"minPrice": 400000,"maxRooms": 3,"maxPages": 5,"maxListings": 100,"fetchDetails": true}
URL mode is the recommended path for filters not exposed above (district, neighborhood, building type) or for cities outside the built-in lookup table. Open Otodom in your browser, refine your search, then copy the URL. The price, area and room range fields still apply to listings found this way.
Supported URL shapes:
| Shape | Example | Result |
|---|---|---|
Search (city, district, filters, sort, ?page=N) | /pl/wyniki/sprzedaz/mieszkanie/mazowieckie/warszawa/warszawa/warszawa | All listings, walking pages from the page in the URL |
Paginated search (?page=2, walks forward from that page) | /pl/wyniki/sprzedaz/mieszkanie/mazowieckie/warszawa/warszawa/warszawa?page=2 | Listings from page 2 onward |
| Search with market and rooms query (the site redirects to a canonical path) | /pl/wyniki/sprzedaz/mieszkanie/cala-polska?ownerTypeSingleSelect=ALL&roomsNumber=%5BTWO%5D&market=PRIMARY | All matching listings |
| Rent search with price and sort query | /pl/wyniki/wynajem/mieszkanie/cala-polska?priceMin=1000&priceMax=3000&by=PRICE&direction=ASC | All listings |
| District with radius | /pl/wyniki/sprzedaz/mieszkanie/malopolskie/krakow/krakow/krakow/stare-miasto?distanceRadius=5 | All listings |
| New-development search | /pl/wyniki/sprzedaz/inwestycja/cala-polska | All listings |
Single listing (also the short /pl/oferta/ID... form) | /pl/oferta/2-pok-46-38-m2-balkon-umeblowane-do-wejscia-0-prowizji-ID4CBFQ | One record with full detail |
| Agency profile | /pl/firmy/biura-nieruchomosci/hampton-home-nieruchomosci-ID3065052 | The first page of the agency's listings (about 6; the site does not serve further pages on this address, a warning shows the agency's total) |
Not supported, skipped with an error line in the log (never a silent zero): new-development detail pages (/pl/inwestycja/...), agency directory lists (/pl/firmy/biura-nieruchomosci/lista/...), the mobile host m.otodom.pl, /en/ addresses and the old address style (/sprzedaz/mieszkanie/warszawa/). If no URL in the list is usable the run fails with an input error.
Behaviour change: if you switch to URL mode and leave urls untouched (or omit it from an API call), the actor now scrapes the eight example URLs in the table above (one per supported shape, capped by maxListings) instead of doing nothing. An explicitly empty list is still rejected. Each returned listing is billed as usual under the existing event; no pricing changed.
Locations by name or path: locations accepts a city or voivodeship name, or a location path such as malopolskie/krakow/krakow/krakow/stare-miasto. A name or path that cannot be resolved is skipped with an error in the log instead of silently searching all of Poland.
Run it from your code
Python:
from apify_client import ApifyClientclient = ApifyClient("<YOUR_APIFY_TOKEN>")run = client.actor("abotapi/otodom-pl-scraper").call(run_input={"mode": "search","transaction": "sprzedaz","estate": "mieszkanie","locations": ["Warszawa"],"maxListings": 20,})for listing in client.dataset(run["defaultDatasetId"]).iterate_items():print(listing["title"], listing["priceTotal"], listing["city"])
JavaScript:
import { ApifyClient } from 'apify-client';const client = new ApifyClient({ token: '<YOUR_APIFY_TOKEN>' });const run = await client.actor('abotapi/otodom-pl-scraper').call({mode: 'search',transaction: 'sprzedaz',estate: 'mieszkanie',locations: ['Warszawa'],maxListings: 20,});const { items } = await client.dataset(run.defaultDatasetId).listItems();
Or connect it to Make, Zapier, n8n, Google Sheets or webhooks from the Integrations tab.
Resume and recurring updates
- Resume (
resumeFromRunId) continues one interrupted full-catalogue walk: paste its run or dataset ID and already-collected listings (matched byid) are skipped, so this run only saves the new ones. - Incremental mode (
incrementalMode) is for scheduled runs of the same search. The actor remembers the search itself, no ID to paste, and classifies every rowNEW,UPDATED(withchangedFields),UNCHANGED(suppressed unlessemitUnchangedis on),REAPPEARED, orEXPIRED(only after a run that scanned the whole search with no cap or resume, and only withemitExpiredon).stateKeynames or shares the stored baseline; leave it empty and one is derived automatically from your search settings. Every output field is compared exceptscrapedAt, the photo fields (images,imageUrl: image links can change without the ad changing),isPromoted(a paid-promotion flag that comes and goes), and anything you list inignoreFieldsForChanges(ignored fields stay in the output but never triggerUPDATEDor appear inchangedFields).dateModifiedis Otodom's own modified-at stamp and moves when an advertiser edits or refreshes an ad; add it toignoreFieldsForChangesif you only care about content changes. A pasted URL that starts at?page=2or later, or any run stopped bymaxListings/maxPages, never emitsEXPIRED.cityandprovinceare now filled from the detail page (they used to be empty); on the first incremental run after this change that fill is saved silently as a one-time backfill and is not returned or billed asUPDATED. With incremental mode off, output is exactly as before. - The two combine only when bootstrapping: pass
resumeFromRunIdon the very firstincrementalModerun to seed its baseline from a prior non-incremental pull.
Output views and run summary
The dataset has three views, also linked from the run's Output tab: Listings (overview, the core columns), Listing details (detail: description, GPS, phone, condition, extras, buildYear, ownership, dateModified) and Changes (incremental mode) (changes: changeType, changedFields, firstSeenAt, lastSeenAt). A Run summary (key-value store record OUTPUT) lists, per search or URL, the pages loaded, listings found and status. Detail enrichment also adds images (up to 30 photo URLs), condition, extras, dateModified, agencyUrl and agencyLicenseNumber where Otodom shows them.
Pricing
Pay per result: $0.08 per run start plus $0.0015 per listing at the FREE tier (the Pricing tab shows the rates for your tier). Detail-page enrichment is included in the listing, and incremental mode bills only the rows it returns.
Send results into your apps (MCP connectors)
Optionally pipe the scraped results into the apps you already use, via Model Context Protocol (MCP) connectors. This is an extra delivery step after the scrape, the Apify dataset is never changed.
What gets written to the connector: a condensed, human-readable summary of each record, not the full JSON. Each item becomes one entry with a title and its key fields flattened to plain text. The complete record always stays in the Apify dataset.
- Authorize a connector once under Apify → Settings → Integrations (Notion, Linear, Airtable, or Apify).
- Select it in the "Pipe results into your apps" input field. (If the picker is empty, you have not authorized a connector yet.)
- For Notion, also set
notionParentPageUrlto the page where items should be created.
The connection is mediated by Apify's MCP proxy, so this actor never sees your third-party credentials. Leave the field empty to skip.
Input Parameters
| Parameter | Type | Default | Description |
|---|---|---|---|
mode | string | search | search (use the filters below) or url (paste URLs). |
transaction | string | sprzedaz | sprzedaz (sale) or wynajem (rent). Used to build the search URL in search mode. |
estate | string | mieszkanie | Property type: mieszkanie, dom, dzialka, lokal, garaz, inwestycja, pokoj, or haleimagazyny (halls and warehouses). |
locations | array | none (prefill: ["Warszawa"]) | Polish city names. 80 cities and all 16 voivodeships are recognized by name; unrecognized names are skipped with an error in the log (and a location path such as malopolskie/krakow/krakow/krakow/stare-miasto is accepted). Leave empty for Poland-wide, or use URL mode for districts and smaller cities. |
marketType | string | all | all, primary (rynek pierwotny), or secondary (rynek wtórny). |
minPrice | integer | none | Lower bound on total price (PLN). |
maxPrice | integer | none | Upper bound on total price (PLN). |
minArea | integer | none | Lower bound on area (m²). |
maxArea | integer | none | Upper bound on area (m²). |
minRooms | integer | none | Minimum number of rooms, 1 to 10. |
maxRooms | integer | none | Maximum number of rooms, 1 to 10. |
buildYear | integer | none | Only listings built in this year or later (1900-2030). Search mode only. |
postedDate | string | empty (any time) | Only listings posted within the last 1, 3, 7 or 14 days. Search mode only. |
districts | array | none | City districts such as Mokotów or Wola; one search runs per district. Applies to city locations only (ignored, with a log warning, for voivodeships or Poland-wide). Search mode only. |
sortBy | string | DEFAULT | DEFAULT, LATEST, PRICE_ASC, PRICE_DESC, AREA_ASC, or AREA_DESC. Search mode only. |
urls | array | eight sample URLs (one per supported shape; also the prefill) | Full otodom.pl URLs (URL mode): searches, single listings, agency profiles. The price, area and room filters above still apply. |
maxPages | integer | 0 | Result pages to walk per search or per URL (about 36 listings per page). 0 walks every page. |
maxListings | integer | 20 | Stop after this many listings across all searches. 0 means no cap. The schema accepts at most 1,000,000. |
fetchDetails | boolean | true | Also fetch each listing's detail page for GPS coordinates, full description, agent phone, building year, ownership and the full feature list. |
resumeFromRunId | string | none | Previous run or dataset ID; already-collected listings (matched by id) are skipped. |
incrementalMode | boolean | false | Recurring monitoring of the same search: adds changeType, changedFields, firstSeenAt, and lastSeenAt to every row. |
stateKey | string | none (auto-derived) | Optional label for the incremental-mode baseline; leave empty to derive one from your search settings. |
emitUnchanged | boolean | false | Also output UNCHANGED rows in incremental mode (bills extra dataset rows). |
emitExpired | boolean | false | Also output EXPIRED rows for delisted listings, only after a complete, uncapped, non-resumed scan in incremental mode. |
ignoreFieldsForChanges | string[] | [] | Incremental mode only: field names (dotted path for nested, e.g. agency.logo) left out of change detection; they still appear in the output. scrapedAt is always excluded. |
proxy | object | Residential (PL) | Connection settings. Residential is optional; datacenter or your own proxy URLs also work. |
mcpConnectors | array | none | Optional: send a summary of each record to apps you authorized under Integrations. |
notionParentPageUrl | string | none | Notion connector only: page under which records are created. |
maxNotifyListings | integer | 50 | Cap on items written to each connector per run. |
Output Example
Sample shape, values are illustrative placeholders, not from a live listing.
{"id": "00000001","url": "https://www.otodom.pl/pl/oferta/sample-listing-ID00000","title": "Przytulne mieszkanie na Mokotowie, 2 pokoje","transaction": "sprzedaz","estate": "mieszkanie","priceTotal": 750000,"priceCurrency": "PLN","pricePerSquareMeter": 15000,"rentPrice": null,"rentAdditional": null,"deposit": null,"areaInSquareMeters": 50.0,"rooms": 2,"floor": "3","buildingFloors": 5,"buildYear": 2010,"marketType": "secondary","ownership": "full_ownership","buildingType": "block","buildingMaterial": "concrete","heating": "urban","condition": "ready_to_use","extras": ["balcony", "garden"],"dateModified": "2026-10-14T21:48:21Z","windowsType": "plastic","city": "Warszawa","province": "mazowieckie","district": "Mokotów","street": "ul. Sample","postalCode": "00-000","latitude": 52.0000,"longitude": 21.0000,"agencyName": "Sample Agency","advertiserType": "agency","contactName": "Agent Name","phone": "+48000000000","referenceId": "REF-0001","description": "Sprzedam przytulne mieszkanie w spokojnej okolicy, blisko metra i parku.","features": ["meble", "balkon", "winda"],"isPromoted": false,"isExclusive": false,"isPrivateOwner": false,"imageCount": 12,"imageUrl": "https://img.otodom.pl/api/v1/offers/sample-id/image","dateCreated": "2026-01-01T00:00:00.000Z","investmentEstimatedDelivery": null,"scrapedAt": "2026-04-28T00:00:00.000Z"}
Plan Requirement
The default connection setting is Apify's residential proxy scoped to Poland. It is optional: in our checks Otodom pages also loaded through Apify's standard datacenter proxy (clear a leftover Poland/residential selection in the connection field to use it), so residential is not required. Datacenter access was checked on single fetches only; heavy runs at volume may hit rate limits, in which case switch back to residential. Residential and datacenter proxy usage are billed to your own Apify account.
If your plan has no residential access, the actor says so in the log ("The selected connection is unavailable to this run. Apify free plans do not include residential proxies. Continuing on a backup connection; some pages may still be unavailable.") and continues on a backup connection. If no page can be read at all, the run ends as failed with one of these messages:
CONNECTION_REFUSED: the proxy connection was refused for this account (Apify plans without proxy access need a paid plan), or your own proxy URLs have invalid credentials.NO_RESULTS: residential proxies are unavailable on this run and the backup connection could not load the requested pages.COULD_NOT_LOAD: no results page could be loaded; a connection problem, not an empty search. Try again later.
You can also paste your own proxy URLs into the connection field; make sure they reach Poland for stable routing.
FAQ
How much does it cost?
You pay $0.08 per run start plus $0.0015 per record returned at the FREE tier, with detail-page enrichment included in that same record. The Pricing tab shows the rates for your tier. Use Max listings to cap the cost of any run.
Is it legal to scrape Otodom?
This actor collects only publicly available property listing data. You are responsible for how you use it: follow Otodom's terms and the laws that apply to you, including data protection rules if you store or reuse agent or owner contact details, and get legal advice if you plan commercial redistribution.
Can I get only new or changed listings on a schedule?
Yes. Schedule the actor from the Schedules tab and turn on Incremental mode. Each run then returns only new, updated, reappeared, and (if you also turn on Emit expired listings) delisted records for that same search; unchanged ones are not returned or billed unless you turn that on too.
Why did my city search return listings from all over Poland?
locations recognizes 80 built-in cities and the 16 voivodeships by name. An unrecognized or misspelled name is skipped with an error in the log (if every entry is unrecognized the run fails with an input error). Check the spelling, or switch to URL mode and paste a search URL for that city or district.
Why did my run fail instead of returning an empty dataset?
A run fails only in these cases: (1) the input has no usable URLs or locations left after unrecognized entries are rejected (an input error); (2) no results page can be loaded at all, because the proxy connection was refused for your account, because residential access is unavailable on a free plan and the backup connection could not load the page either, or because the requests failed for another connection reason; (3) resumeFromRunId points to a run or dataset your account cannot read; (4) incrementalMode and resumeFromRunId are combined on a search that already has saved incremental state; or (5) the connection settings cannot be set up at all (for example an invalid proxy configuration). Everything else, including filters that match nothing or a single page that could not be read while other pages load, finishes as a successful run with a warning in the log and an empty or partial dataset. If you get zero listings, check your city name and filters first, then try again.
Can I use it with AI agents or MCP?
Yes. Call it from any Apify integration or MCP client, and use the connector field to push results into Notion, Linear or Airtable.
🔗 Want more real estate data?
Pair this actor with these related scrapers from the same team:
| 🏠 ImmobilienScout24.de Scraper $0.9💰/1K for Gold discount. Extract property listings from immobilienscout24.de... | 🏠 Immowelt.de Scraper Scrape detailed property listings from Immowelt.de into clean structured data. Extract... |
| 🏠 SeLoger Scraper Scrape SeLoger.com properties for sale and rent. Search by location with price, room and... | 🏠 Cian RU Collect property listings from Cian.ru by search filters or direct URLs. Returns... |
| 🏠 Domclick RU Property Scraper Extract property listings from domclick.ru, one of Russia’s largest real estate portals... | 🏠 591.com.tw Scraper Extract property listings from 591房屋交易網 591.com.tw, Taiwan’s largest real estate... |
💬 Support & custom scrapers
- 🐞 Found a bug or a missing field? Open a ticket on the Issues tab. We usually reply within hours.
- 🛠️ Need another site, extra fields or a private build? Email contact@abotapi.com or message Telegram @abotapi.
- ⭐ Enjoying it? A quick review on the actor page helps other users find it.