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 ageo_pathGET /api/otodom/property— full detail for one listing idGET /api/otodom/development— investment/development detail with per-unit pricingGET /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.
Run this endpoint
Endpoint
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.
string
Required
sprzedaz (sale) or wynajem (rent).
sprzedaz
string
Required
Estate slug, e.g. mieszkanie, kawalerka, dom, pokoj, dzialka, mieszkanie,rynek-pierwotny.
mieszkanie,rynek-pierwotny
string
Required
2 to 6 slash-separated segments from the locations endpoint.
mazowieckie/warszawa/warszawa/warszawa
integer
Optional
Minimum price.
10
integer
Optional
Maximum price.
10
array
Optional
Room counts: ONE, TWO, THREE, FOUR, FIVE, SIX_OR_MORE.
["example"]
string
Optional
DEVELOPER, PRIVATE or AGENCY.
example
integer
Optional
Items per page, up to 72.
5
integer
Optional
Page number.
1
boolean
Optional
Only true is supported. Omit it for listings both with and without a discount.
true
Request Examples
<?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
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();
}
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));
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);
}
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
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()
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}")
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());
}
}
}
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))
}
#!/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"
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}");
}
}
}
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();
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
{
"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.
HTTP INVALID_PARAMETERS
The geo path or estate slug was rejected.
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.