Otodom Scraper avatar

Otodom Scraper

Pricing

$1.50 / 1,000 listings

Go to Apify Store
Otodom Scraper

Otodom Scraper

Otodom.pl real estate listings as clean JSON: price, area, rooms, floor, location, agency or private-owner flag. Search by URL or transaction/type/location. No login, no persondata.

Pricing

$1.50 / 1,000 listings

Rating

0.0

(0)

Developer

Rowfeed

Rowfeed

Maintained by Community

Actor stats

0

Bookmarked

2

Total users

1

Monthly active users

3 days ago

Last modified

Share

Pull Otodom.pl real estate listings as clean JSON: price, price per m², area, rooms, floor, city, district, street, agency or private-owner flag, no login and no browser. Built for property analysts and proptech tools tracking the Polish housing market, investors screening listings by price/area/location, market researchers watching supply and pricing trends across cities, and AI agents and dashboards that need a structured "what's for sale/rent in <city>" answer.

Plain HTTPS GETs against Otodom's own public search-results page (otodom.pl/pl/wyniki/...), the same server-rendered page a browser loads, parsed from its embedded JSON - no API key, no cookies, no headless browser.

What you get

  • Every listing as one row - id, URL, title, sale/rent, property type, price, price per m², area, rooms, floor, city, district, street, agency name (or a private-owner flag - never a private seller's name), creation date, image URL.
  • Any search, two ways - drop in one or more full Otodom search URLs (pagination handled automatically), or let the Actor build one from transaction + estateType + location (+ optional price range).
  • A cost cap that actually caps - maxListings stops the run once that many rows are billed, across however many search URLs you listed.

No personal data, ever

Otodom's search-results JSON carries an advertOwner.name field with the seller's or agent's own personal name for every listing, private or agency. This Actor never reads that field - it is not mapped into any output column. Only agency.name (the business name of a real-estate agency, when one is attached to the listing) is ever output, and is_private_owner flags a listing with no agency instead of naming the individual behind it.

Sample row

A real row from a default run (https://www.otodom.pl/pl/wyniki/sprzedaz/mieszkanie/cala-polska):

{
"id": 67913444,
"url": "https://www.otodom.pl/pl/oferta/gotowe-do-odbioru-3-pokoje-miejsce-w-hali-garazowej-za-50-ID4AXo8",
"title": "Gotowe do odbioru - 3 pokoje + miejsce w hali garażowej za 50%",
"transaction_type": "SELL",
"estate_type": "FLAT",
"listing_kind": "standard",
"price": 539550.0,
"currency": "PLN",
"price_per_m2": 9900.0,
"area_m2": 54.5,
"rooms": 3,
"floor": 0,
"city": "Osielsko",
"district": null,
"street": "ul. Dzikiej Gęsi",
"latitude": null,
"longitude": null,
"agency_name": null,
"is_private_owner": true,
"market": null,
"build_year": null,
"created_at": "2026-04-16T12:50:17Z",
"image_url": "https://ireland.apollo.olxcdn.com/v1/files/.../image;s=1280x1024;q=80",
"rank_on_page": 1,
"page": 1,
"scraped_at": "2026-09-16T08:24:11+00:00"
}

district is populated when Otodom's own reverse geocoding resolves one for the listing (common for big cities, e.g. "Śródmieście" for central Warsaw addresses) and null otherwise, as in this rural example. latitude, longitude, market (primary/secondary) and build_year are always null: Otodom's search-results JSON does not include them - only the individual listing's own detail page would, and this Actor does not fetch detail pages (see Details below).

estate_type is Otodom's own value, verbatim - observed values are FLAT, HOUSE, TERRAIN, COMMERCIAL (matching the estateType filter's mieszkanie/dom/dzialka/lokal) and INVESTMENT (a developer's multi-unit project listed as one search result - commonly ~1/3 of rows for mieszkanie/dom searches; its price/area describe the project's range, not a single unit).

listing_kind is "standard" for a normal ranked result, or "recommended" for a "similar ad" slot Otodom's results page injects mid-list, re-showing an ad that already appears elsewhere on the same page under a decorated id. Otodom sends an impossible placeholder date (1999-02-29, which never existed) and no images for these slots; this Actor never emits that fake date - created_at and image_url are null on a "recommended" row instead, and listing_kind says why. These are still real listings (title, price, agency all populated), so they are kept rather than dropped.

The typed columns above are followed by Otodom's own extra raw fields, kept for completeness: province, is_promoted, is_exclusive_offer, source, tags, short_description, rooms_raw (the un-mapped ONE..TEN/MORE enum), floor_raw (the un-mapped GROUND..ABOVE_TENTH enum) and total_possible_images.

Filters

InputDefaultWhat it does
searchUrls[] (empty)Full Otodom search-results URLs. Each is paginated automatically. Empty by default, so a default run uses the Transaction/Estate type/Location builder below. If you fill this in, it wins over the builder fields below - even if you also changed one of them - and a warning is logged saying so.
transaction"sprzedaz"Used only when searchUrls is empty: "sprzedaz" (sale) or "wynajem" (rent).
estateType"mieszkanie"Used only when searchUrls is empty: "mieszkanie" (flat), "dom" (house), "dzialka" (plot) or "lokal" (commercial unit).
location"cala-polska"Used only when searchUrls is empty: "cala-polska" (whole country) or an Otodom location path, e.g. "mazowieckie/warszawa/warszawa/warszawa" (voivodeship/county/commune/city) or ".../srodmiescie" (+ district).
maxListings200Stop after this many rows total, across all search URLs.
priceMin / priceMax-Builder path only - applied as priceMin/priceMax query params on the URL built from transaction/estateType/location. Has no effect when searchUrls is set: put the price range directly in the URL instead (e.g. ?priceMax=800000).

The default run (all fields left as-is) builds https://www.otodom.pl/pl/wyniki/sprzedaz/mieszkanie/cala-polska from the builder defaults and returns a full page of results.

Example searchUrls (paste into the field to use the URL path instead of the builder):

  • https://www.otodom.pl/pl/wyniki/sprzedaz/mieszkanie/cala-polska (all flats for sale, whole country)
  • https://www.otodom.pl/pl/wyniki/wynajem/mieszkanie/mazowieckie/warszawa/warszawa/warszawa (flats for rent, Warsaw)

Otodom's own URL grammar (for hand-built searchUrls): /pl/wyniki/<sprzedaz|wynajem>/<mieszkanie|dom|dzialka|lokal>/<location>?priceMin=<int>&priceMax=<int>. An unrecognized location segment (e.g. .../nope/xyz) returns HTTP 404, which this Actor turns into one uncharged error row (error: "not_found") instead of failing the run; a URL that returns HTTP 200 but isn't actually an Otodom results page (no searchAds in its embedded JSON) likewise becomes one uncharged error row (error: "other"), never a silent zero-row success.

Pricing

Pay per event, no subscription: $1.50 per 1,000 listings ($0.0015 per row) - undercuts the current Store leader's $2/1,000. A default run (2 search URLs, maxListings: 200) costs at most $0.30. An invalid search URL (bad location, bad filters) produces one uncharged error row and the run continues with the next URL; only zero rows plus at least one failed request marks the whole run failed.

Use it from your tools

  • API and SDKs - call it via the Apify API or the official Python/JavaScript clients: one call to start the run, one to fetch the results as JSON or CSV.
  • Schedules - run it daily inside Apify and pipe freshly-listed properties into Google Sheets, a webhook or your own database.
  • n8n, Make and Zapier - trigger runs and feed extracted listings into a workflow through Apify's integration for each.
  • AI agents and MCP - eligible for agentic use via Apify's MCP server with pay-per-event pricing, so an agent can call it mid-task and pay only for the listings it actually pulls.

Details

  • Source: https://www.otodom.pl/pl/wyniki/..., Otodom's own public search-results page, parsed from the __NEXT_DATA__ JSON it embeds server-side (props.pageProps.data.searchAds). No authentication, no proxies, no browser.
  • Pagination: follows Otodom's own pagination.totalPages, requesting up to 72 listings per page (Otodom's own maximum), until the budget (maxListings) or the last page is reached.
  • Search-list only: this Actor reads the search-results page, not each listing's own detail page - faster and far less likely to be blocked (no per-listing requests to fail or rate-limit), at the cost of a few fields (coordinates, market, build year) that only the detail page carries. Documented as null, never guessed.
  • Reliability: 429 and 5xx responses are retried with exponential backoff (5 tries); 404 and 401/403 are never retried; a 200 response without the expected embedded JSON counts as a failure, not a silent empty success.
  • Run stats: the STATS record in the run's key-value store holds row/error counts and per-category request-error counts.
  • Politeness: 0.5 s between requests, 30 s timeout, Accept-Language: pl,en, a User-Agent that names the Actor.
  • Not affiliated with Otodom / OLX Group. Built against Otodom's own public search page; no login, no scraping of member-only content.