# /idealista/properties/search

`POST /api/idealista/properties/search`

Price: 10 credits

Search Idealista property listings in Portugal (idealista.pt) or Italy (idealista.it), for sale or to rent, across homes, land, garages, offices, buildings, storage rooms, commercial units and new developments. Narrows by dwelling sub-type, room and bathroom count, condition, energy band, storey, amenities, price range and floor area, and orders by price, price per m², area, storey, publication date or price cut. Returns listing cards with title, price with any recorded discount, price per m², area, rooms, storey, amenities, photos, the advertiser description and the selling agency.

## How to use it

Portugal and Italy only — set `domain` to pt (idealista.pt, the default) or it (idealista.it). Spain is not covered. `location` on pt is a plain accent-folded municipality name ('Vila Nova de Gaia'); on it the slug pairs comune and provincia ('milano-milano', 'forli-forli-cesena'), and a single bare word is doubled for you, which is right for most provincial capitals but not where the province differs. Widen to the whole district or province with the '-distrito' (pt) or '-provincia' (it) suffix — usually two to three times the listings. `rooms` and `home_types` are country-specific and prefixed pt_/it_; passing a value from the wrong country is rejected rather than ignored. Note the two room systems count different things: pt_t2 is two BEDROOMS, it_bilocale is two ROOMS in total, so it_bilocale is the smaller home. Filters intersect, so stacking several narrows fast and an over-constrained query returns 412 rather than an empty list. `property_type` homes covers flats and houses together; narrow with `home_types`. Renting land (pt) and buying rooms (it) are the only operation/property-type pairs idealista does not publish and are rejected. Some features are tied to one operation and are rejected with a message naming which; the amenity, bathroom, condition, energy and storey filters belong to the homes feed, so pairing them with another `property_type` answers 412. One query reaches at most 1800 listings however many match, so to cover a large city split it by `zone`, price band or `home_types` rather than raising `count`.

## Parameters

- `access-token` (string, required)

## Request body

- `timeout` (integer) — Max scrapping execution timeout (in seconds) (default: 300; min: 20; max: 1500)
- `domain` (string) — Country site to search: pt = idealista.pt, it = idealista.it (default: "pt"; one of: "pt", "it")
- `location` (string, required) — Municipality or city. On pt a plain accent-folded name resolves ('Vila Nova de Gaia'); on it the slug is '{comune}-{provincia}' ('milano-milano', 'forli-forli-cesena') and a single bare name is doubled automatically. Append '-distrito' (pt) or '-provincia' (it) to widen to the whole district/province (examples: "Lisboa", "Vila Nova de Gaia", "lisboa-distrito", "milano-milano", "milano-provincia"; minLength: 1)
- `zone` (string, nullable) — Optional parish or neighbourhood inside the location (examples: "alcantara", "citta-studi-lambrate")
- `operation` (string) — Buy or rent (default: "buy"; one of: "buy", "rent")
- `property_type` (string) — Kind of property to search (default: "homes"; one of: "homes", "land", "garages", "offices", "buildings", "storage_rooms", "commercial", "new_developments", "rooms")
- `home_types` (array, nullable) — Dwelling sub-types; only meaningful when property_type is homes. Values are country-specific and prefixed pt_/it_ - they must match the selected domain (one of: "pt_apartamentos", "pt_apenas_apartamentos", "pt_moradias", "pt_moradias_em_banda", "pt_moradias_geminadas", "pt_moradias_independentes", "pt_moradias_terreas", "pt_duplex", "pt_lofts", "pt_aguas_furtadas", "pt_casas_rusticas", "pt_quintas", "pt_solares_ou_palacetes", "it_appartamenti", "it_solo_appartamenti", "it_attici", "it_loft_open_space", "it_mansarde", "it_case_indipendenti", "it_ville", "it_ville_indipendenti", "it_villette_a_schiera", "it_rustici", "it_casali_o_cascine", "it_appartamenti_due_livelli")
- `rooms` (array, nullable) — Room-count buckets. Country-specific and prefixed pt_/it_: pt_* is the Portuguese T-typology counting BEDROOMS (pt_t2 = two bedrooms), it_* is the Italian 'locali' counting TOTAL rooms (it_bilocale = two rooms, i.e. one bedroom). They must match the selected domain (one of: "pt_t0", "pt_t1", "pt_t2", "pt_t3", "pt_t4_or_more", "it_monolocale", "it_bilocale", "it_trilocale", "it_quadrilocale", "it_5_locali_or_more")
- `bathrooms` (array, nullable) — Number of bathrooms (one of: "one", "two", "three_or_more")
- `condition` (array, nullable) — Property condition (one of: "new_construction", "good", "to_renovate")
- `energy_rating` (array, nullable) — Energy certificate band (one of: "high", "medium", "low")
- `floor` (array, nullable) — Storey position in the building (one of: "top", "intermediate", "ground")
- `features` (array, nullable) — Amenities and listing attributes to require. Not every feature exists on every country site, and some exist only for one operation; a value the selected domain or operation does not publish is rejected with a message naming what is allowed rather than silently ignored. Idealista applies these filters to the homes feed, so most other property_type values refuse them upstream (one of: "elevator", "pool", "garden", "private_garden", "terrace", "balcony", "terrace_or_balcony", "parking", "storage_room", "air_conditioning", "built_in_wardrobes", "accessible", "luxury", "sea_view", "floor_plan", "virtual_tour", "bank_owned", "tenanted", "judicial_auction", "pets_allowed", "furnished")
- `min_price` (integer, nullable) — Minimum price in euros (min: 0)
- `max_price` (integer, nullable) — Maximum price in euros (min: 0)
- `min_size` (integer, nullable) — Minimum floor area in m² (min: 0)
- `max_size` (integer, nullable) — Maximum floor area in m² (min: 0)
- `sort` (string) — Result ordering (default: "relevance"; one of: "relevance", "price_asc", "price_desc", "newest", "oldest", "price_drop", "price_per_m2_asc", "price_per_m2_desc", "size_asc", "size_desc", "floor_asc", "floor_desc")
- `count` (integer, required) — Max number of results to return (min: 1; max: 1800)

## Response

### 200 — Successful Response

- `@type` (string) (default: "IdealistaPropertyCard")
- `id` (string, required)
- `url` (string, nullable)
- `listing_title` (string, nullable)
- `price` (number, nullable)
- `previous_price` (number, nullable)
- `price_drop_percent` (integer, nullable)
- `price_per_m2` (number, nullable)
- `surface` (integer, nullable)
- `room_count` (integer, nullable)
- `photo_count` (integer, nullable)
- `floor` (string, nullable)
- `has_elevator` (boolean, nullable)
- `has_parking` (boolean, nullable)
- `is_professional` (boolean, nullable)
- `highlight` (string, nullable)
- `description` (string, nullable)
- `tags` (array) (default: [])
- `features` (array) (default: [])
- `images` (array) (default: [])
- `agency` (object, nullable)
  - `@type` (string) (default: "IdealistaCardAgency")
  - `name` (string, nullable)
  - `url` (string, nullable)
  - `image` (string, nullable)

## Errors

### 422 — Validation Error

A filter value does not exist on the selected country domain, is tied to the other operation, or a required field is missing

What to do: Check the fields against this schema. A URN with the wrong prefix is the most common cause.

- `detail` (array)
  - `loc` (array, required)
  - `msg` (string, required)
  - `type` (string, required)
  - `input` (any)
  - `ctx` (object)

### 408

The request ran past its time limit

What to do: Raise `timeout` in the request body, up to the maximum this endpoint documents. Lowering `count` or turning off the `with_*` flags also helps, because less work finishes sooner.

### 412

No listings matched, or the location, zone or filter combination does not exist upstream — the commonest case is a filter that only the homes feed accepts

What to do: Retrying will not help: either the entity does not exist, or the input points at a different one.

### 429

Too many requests: a rate limit or a usage window is exhausted

What to do: When the response carries an X-Retry-After header, wait that many seconds and retry: the same number is in the body as `detail.retry_after`, and the limit clears once that window passes. The message in the body names the limit that was hit.

### 500

Something broke on our side

What to do: Retrying will not help. If it keeps happening, send us the X-Request-ID from the response headers.

### 529

Rate limit reached, or the endpoint is overloaded

What to do: Wait at least 30 seconds, then retry.

## Response envelope

Success: Array of objects (may be empty if no results)

Error: Error may coexist with partial results if it occurs mid-execution. Check X-Error header and status code.

Every response carries these headers:

- `X-Error` — Error message text (present only on error)
- `X-Request-ID` — Unique request identifier
- `X-Execution-Time` — Execution time in seconds
- `X-Result-Count` — How many records the body carries. 0 means an empty result, which is a normal answer and not by itself an error. A non-zero count can come back together with X-Error when the failure happened partway through — read this header and X-Error independently.
- `X-Total-Available-Results` — How many records exist for this query, when the endpoint can say. On a `dry_run` request this is the answer and the body is empty. It saturates: the endpoint's documented maximum means 'at least that many', any smaller number is exact.
- `X-Warning` — Present when the request body carried keys this endpoint does not document. They were ignored, so any filter you meant to apply through them did not apply. Check the spelling against this schema and retry.
- `X-Retry-After` — Seconds to wait before retrying. Present only on 429.

