Skip to content
Scrappa Get API key
Otodom API 1 credit/request

Otodom Search API Documentation

GET https://scrappa.co/api/otodom/search?transaction=sprzedaz&estate_slug=mieszkanie%2Crynek-pierwotny&geo_path=mazowieckie%2Fwarszawa%2Fwarszawa%2Fwarszawa&limit=5

Endpoint documentation for searching Otodom property listings as JSON

How to call the Otodom search endpoint, which filters it accepts, and what each listing field means.

Scrappa's GET /api/otodom/search endpoint is the Otodom Search API documentation page for developers who need Polish property listings as structured JSON instead of building their own scraper.

Endpoint reference for Otodom search

Use this endpoint to search apartments, houses, rooms, plots and commercial premises for sale or rent anywhere in Poland. The request takes a transaction, an estate_slug, a geo_path from the Otodom locations endpoint, and optional price, area, room-count, amenity, owner-type, sort and distance filters, plus page and limit.

What the Otodom search response returns

Each listing returns its full field set: id, slug, title, href, estate, transaction, prices (totalPrice, rentPrice, pricePerSquareMeter, priceFromPerSquareMeter), areaInSquareMeters, roomsNumber, floorNumber, owner and agency flags, publication timestamps, tags, location, development grouping (developmentId, developmentTitle, developmentUrl) and image URLs. The response also carries pagination (total_items, total_pages, current_page, items_per_page), the resolved location chain and result_count for filter read-back.

How this differs from an Otodom scraper build

Otodom publishes no public API and serves filtered search only from server-rendered page state; its GraphQL endpoint accepts no filters and answers with the entire national corpus. This endpoint performs that translation for you, so your application never has to maintain a browser, a proxy pool or a state parser.

Otodom search API use cases

Proptech products can run repeatable supply feeds for Polish cities, counties, districts and residential quarters. Market analysts can compare asking prices and availability across the whole country by combining the search endpoint with the count endpoint. Alerting tools can poll the same filtered search on a schedule and trigger on newly published listings.

For cross-portal coverage, query Otodom beside other real estate APIs and deduplicate by address, coordinates, listing URL and price.

When to pair this endpoint with the other Otodom endpoints

Use GET /api/otodom/locations first to resolve a place name into the geo_path this endpoint requires. Follow a result id into GET /api/otodom/property for the full description, characteristics and agency data. Use GET /api/otodom/development when an item carries a developmentUrl, and GET /api/otodom/count when you need a total without paging through results.

Related Otodom endpoints

  • GET /api/otodom/locations — resolve a place name to a geo_path
  • GET /api/otodom/property — full detail for one listing id
  • GET /api/otodom/development — investment/development detail with per-unit pricing
  • GET /api/otodom/count — facet totals for a filter set

Implementation playbook

What teams build with this endpoint

Supply Monitoring by City

Poll the same city filter on a schedule and diff `id` sets to detect newly published listings.

Rental Market Analysis

Combine price, area and room filters across regions to compare asking rents without scraping pages.

Cross-Portal Deduplication

Match Otodom listings against other portals by address, coordinates and price.

Investment Screening

Filter on `estate_slug=mieszkanie,rynek-pierwotny` to find new-build units, then pull per-unit pricing from the development endpoint.

Otodom Search API Documentation 1 credit/request

Endpoint

Request preview
GET
https://scrappa.co/api/otodom/search?transaction=sprzedaz&estate_slug=mieszkanie%2Crynek-pierwotny&geo_path=mazowieckie%2Fwarszawa%2Fwarszawa%2Fwarszawa&limit=5
Auth header
x-api-key
Cost
1 credit/request
transaction = sprzedaz estate_slug = mieszkanie,rynek-pierwotny geo_path = mazowieckie/warszawa/warszawa/warszawa
Response preview
200 OK
{
    "success": true,
    "data": {
        "items": [
            {
                "id": "64629218",
                "slug": "mieszkanie-3-pokojowe-warszawa-ID4nb0K",
                "title": "Mieszkanie 3 pokojowe, Warszawa",
                "estate": "FLAT",
                "transaction": "SELL",
                "totalPrice": {
                    "value": 899000,
                    "currency": "PLN"
                },
...

Parameters

Start with the required fields, then add optional filters only when your use case needs them.

Runnable path

3 required parameters needed before sending a request.

7 optional filters available.

transaction string Required

sprzedaz (sale) or wynajem (rent).

Example value sprzedaz
estate_slug string Required

Estate slug, e.g. mieszkanie, kawalerka, dom, pokoj, dzialka, mieszkanie,rynek-pierwotny.

Example value mieszkanie,rynek-pierwotny
geo_path string Required

2 to 6 slash-separated segments from the locations endpoint.

Example value mazowieckie/warszawa/warszawa/warszawa
price_min integer Optional

Minimum price.

Example value 10
price_max integer Optional

Maximum price.

Example value 10
rooms_number[] array Optional

Room counts: ONE, TWO, THREE, FOUR, FIVE, SIX_OR_MORE.

Example value ["example"]
owner_type string Optional

DEVELOPER, PRIVATE or AGENCY.

Example value example
limit integer Optional

Items per page, up to 72.

Example value 5
page integer Optional

Page number.

Example value 1
has_discount boolean Optional

Only true is supported. Omit it for listings both with and without a discount.

Example value true

Request Examples

PHP
<?php

$curl = curl_init();

curl_setopt_array($curl, [
    CURLOPT_URL => "https://scrappa.co/api/otodom/search?transaction=sprzedaz&estate_slug=mieszkanie%2Crynek-pierwotny&geo_path=mazowieckie%2Fwarszawa%2Fwarszawa%2Fwarszawa&limit=5",
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_ENCODING => "",
    CURLOPT_MAXREDIRS => 10,
    CURLOPT_TIMEOUT => 30,
    CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
    CURLOPT_CUSTOMREQUEST => "GET",
    CURLOPT_HTTPHEADER => [
        "x-api-key: YOUR_API_KEY_HERE"
    ],
]);

$response = curl_exec($curl);
$err = curl_error($curl);

curl_close($curl);

if ($err) {
    echo "cURL Error #:" . $err;
} else {
    echo $response;
}
PHP
<?php

use Illuminate\Support\Facades\Http;

$response = Http::timeout(30)
    ->withHeaders(['x-api-key' => 'YOUR_API_KEY_HERE'])
    ->get('https://scrappa.co/api/otodom/search?transaction=sprzedaz&estate_slug=mieszkanie%2Crynek-pierwotny&geo_path=mazowieckie%2Fwarszawa%2Fwarszawa%2Fwarszawa&limit=5');

if ($response->successful()) {
    echo $response->body();
} else {
    echo "Error: " . $response->status();
}
JavaScript
const options = {
    method: 'GET',
    headers: {
        'x-api-key': 'YOUR_API_KEY_HERE'
    }
};

fetch('https://scrappa.co/api/otodom/search?transaction=sprzedaz&estate_slug=mieszkanie%2Crynek-pierwotny&geo_path=mazowieckie%2Fwarszawa%2Fwarszawa%2Fwarszawa&limit=5', options)
    .then(response => {
        if (!response.ok) {
            throw new Error(`HTTP error! status: ${response.status}`);
        }
        return response.text();
    })
    .then(data => console.log(data))
    .catch(error => console.error('Error:', error));
JavaScript
const axios = require('axios');

const options = {
    method: 'GET',
    url: 'https://scrappa.co/api/otodom/search?transaction=sprzedaz&estate_slug=mieszkanie%2Crynek-pierwotny&geo_path=mazowieckie%2Fwarszawa%2Fwarszawa%2Fwarszawa&limit=5',
    headers: {
        x-api-key: 'YOUR_API_KEY_HERE',
    }
};

try {
    const response = await axios(options);
    console.log(response.data);
} catch (error) {
    console.error('Error:', error.message);
}
Ruby
require 'net/http'
require 'uri'

uri = URI.parse("https://scrappa.co/api/otodom/search?transaction=sprzedaz&estate_slug=mieszkanie%2Crynek-pierwotny&geo_path=mazowieckie%2Fwarszawa%2Fwarszawa%2Fwarszawa&limit=5")
http = Net::HTTP.new(uri.host, uri.port)
http.use_ssl = uri.scheme == 'https'

request = Net::HTTP::Get.new(uri.request_uri)
request['x-api-key'] = 'YOUR_API_KEY_HERE'

begin
    response = http.request(request)
    puts response.body
rescue => e
    puts "Error: #{e.message}"
end
Python
import http.client
import json

conn = http.client.HTTPSConnection("scrappa.co")

headers = {
    'x-api-key': 'YOUR_API_KEY_HERE',
}

try:
    conn.request("GET", "/api/otodom/search?transaction=sprzedaz&estate_slug=mieszkanie%2Crynek-pierwotny&geo_path=mazowieckie%2Fwarszawa%2Fwarszawa%2Fwarszawa&limit=5", headers=headers)
    res = conn.getresponse()
    data = res.read()
    print(data.decode("utf-8"))
except Exception as e:
    print(f"Error: {e}")
finally:
    conn.close()
Python
import requests

headers = {
    'x-api-key': 'YOUR_API_KEY_HERE',
}

try:
    response = requests.get('https://scrappa.co/api/otodom/search?transaction=sprzedaz&estate_slug=mieszkanie%2Crynek-pierwotny&geo_path=mazowieckie%2Fwarszawa%2Fwarszawa%2Fwarszawa&limit=5', headers=headers)
    response.raise_for_status()
    print(response.text)
except requests.exceptions.RequestException as e:
    print(f"Error: {e}")
Java
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.Response;
import java.io.IOException;

public class ApiExample {
    public static void main(String[] args) {
        OkHttpClient client = new OkHttpClient();

        Request request = new Request.Builder()
            .url("https://scrappa.co/api/otodom/search?transaction=sprzedaz&estate_slug=mieszkanie%2Crynek-pierwotny&geo_path=mazowieckie%2Fwarszawa%2Fwarszawa%2Fwarszawa&limit=5")
        .addHeader("x-api-key", "YOUR_API_KEY_HERE")
            .build();

        try (Response response = client.newCall(request).execute()) {
            if (response.isSuccessful()) {
                System.out.println(response.body().string());
            } else {
                System.out.println("Error: " + response.code());
            }
        } catch (IOException e) {
            System.out.println("Error: " + e.getMessage());
        }
    }
}
Go
package main

import (
    "fmt"
    "net/http"
    "io/ioutil"
)

func main() {
    client := &http.Client{}
    req, err := http.NewRequest("GET", "https://scrappa.co/api/otodom/search?transaction=sprzedaz&estate_slug=mieszkanie%2Crynek-pierwotny&geo_path=mazowieckie%2Fwarszawa%2Fwarszawa%2Fwarszawa&limit=5", nil)
    if err != nil {
        fmt.Println("Error creating request:", err)
        return
    }
    req.Header.Set("x-api-key", "YOUR_API_KEY_HERE")

    resp, err := client.Do(req)
    if err != nil {
        fmt.Println("Error making request:", err)
        return
    }
    defer resp.Body.Close()

    body, err := ioutil.ReadAll(resp.Body)
    if err != nil {
        fmt.Println("Error reading response:", err)
        return
    }

    fmt.Println(string(body))
}
Terminal
#!/bin/bash

curl -X GET \
    -H "x-api-key: YOUR_API_KEY_HERE" \
    "https://scrappa.co/api/otodom/search?transaction=sprzedaz&estate_slug=mieszkanie%2Crynek-pierwotny&geo_path=mazowieckie%2Fwarszawa%2Fwarszawa%2Fwarszawa&limit=5"
C#
using System;
using System.Net.Http;
using System.Threading.Tasks;

class Program
{
    static async Task Main()
    {
        using var client = new HttpClient();
        client.DefaultRequestHeaders.Add("x-api-key", "YOUR_API_KEY_HERE");

        try
        {
            var response = await client.SendAsync(new HttpRequestMessage(HttpMethod.Get, "https://scrappa.co/api/otodom/search?transaction=sprzedaz&estate_slug=mieszkanie%2Crynek-pierwotny&geo_path=mazowieckie%2Fwarszawa%2Fwarszawa%2Fwarszawa&limit=5"));
            var content = await response.Content.ReadAsStringAsync();
            Console.WriteLine(content);
        }
        catch (Exception ex)
        {
            Console.WriteLine($"Error: {ex.Message}");
        }
    }
}
TypeScript
import axios from 'axios';

async function run(): Promise<void> {
    try {
        const response = await axios({
            method: 'GET',
            url: 'https://scrappa.co/api/otodom/search?transaction=sprzedaz&estate_slug=mieszkanie%2Crynek-pierwotny&geo_path=mazowieckie%2Fwarszawa%2Fwarszawa%2Fwarszawa&limit=5',
            headers: {
        'x-api-key': 'YOUR_API_KEY_HERE',
            },
        });

        console.log(response.data);
    } catch (error) {
        console.error('Error:', error);
    }
}

void run();
RUST
use reqwest::Client;

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    let client = Client::new();

    let response = client
        .get("https://scrappa.co/api/otodom/search?transaction=sprzedaz&estate_slug=mieszkanie%2Crynek-pierwotny&geo_path=mazowieckie%2Fwarszawa%2Fwarszawa%2Fwarszawa&limit=5")
        .header("x-api-key", "YOUR_API_KEY_HERE")
        .send()
        .await?;

    println!("{}", response.text().await?);

    Ok(())
}

Response Schema

Example response fields are illustrative; inspect the JSON before integrating.

Example response fields

Scan these fields before integrating.

success data
JSON Response
200 OK
{
    "success": true,
    "data": {
        "items": [
            {
                "id": "64629218",
                "slug": "mieszkanie-3-pokojowe-warszawa-ID4nb0K",
                "title": "Mieszkanie 3 pokojowe, Warszawa",
                "estate": "FLAT",
                "transaction": "SELL",
                "totalPrice": {
                    "value": 899000,
                    "currency": "PLN"
                },
                "areaInSquareMeters": 68.5,
                "roomsNumber": "THREE",
                "developmentId": null,
                "images": [
                    "https://cdn.otodom.pl/asset/example/image;s=655x491;q=80"
                ]
            }
        ],
        "pagination": {
            "total_items": 20226,
            "total_pages": 281,
            "current_page": 1,
            "items_per_page": 72
        },
        "result_count": 20226
    }
}

Errors

Handle these documented responses before retrying or showing customer-facing failures.

INVALID_PARAMETERS

HTTP INVALID_PARAMETERS

The geo path or estate slug was rejected.

UPSTREAM_ERROR

HTTP UPSTREAM_ERROR

Otodom could not be reached. This response is not billed.

Generate Code with AI

Copy a ready-made prompt with all the endpoint details, parameters, and example responses. Paste it into ChatGPT, Claude, or any AI assistant to instantly generate working code.

Otodom API FAQ

How do I find a geo_path?

Call the Otodom locations endpoint with a place name and use the id of the match you want.

Why is my result count much larger than I expected?

An unrecognized estate slug silently returns the nationwide feed. Every supported slug is listed in the endpoint reference.

Is a request billed when it fails?

No. Failed requests return an error object and are never charged.

Why can I request page 99999?

Otodom clamps a page past the end rather than erroring. The response reports page_clamped with the page actually served.

Are image files downloaded?

No. Only image URLs are returned, which is cheaper and lets you choose any resolution.

Try It Live

Test this endpoint in our interactive playground with real data.