# idealista-api > API published on RapidAPI. Exposes real-estate listing search, property detail, location autocomplete, reverse geocoding, new-development promotion units and listing statistics for Spain, Portugal and Italy. All responses are JSON; stable fields are documented, but payloads may include extra fields not listed here. This file is designed so another LLM can integrate with the API without additional documentation. It contains: auth, cache conventions, endpoint list, response schemas, error codes and `curl` examples. The camelCase `operationId`s serve as method names when generating SDKs (`client.searchListings(...)`, `client.getPropertyDetail(...)`, etc.). A human-oriented integration cookbook with copy-paste recipes (locations, rentals, new developments, filters, large-scale crawling) is served at **`GET /integration-guide.html`**, and the full OpenAPI 3.1.0 specification at **`GET /openapi.json`**. > **Search filters**: not listed here. They are dynamic and depend on the `operation`+`propertyType`+`locationIds` combo. To discover which filters apply in each context, call **`getSearchFilters`** (`GET /v1/search/filters`) first — it returns blocks with `key`/`dataType`/labels ready to render UI or build queries. Search endpoints accept any recognized filter and silently ignore those that don't apply. ## Authentication Consumers call the RapidAPI URL (`https://.p.rapidapi.com`) with the `x-rapidapi-key` header. RapidAPI forwards the request to this API with the required internal headers. ### Headers | Header | When | Notes | |--------|------|-------| | `x-rapidapi-proxy-secret` | Required on `/v1/*` | Injected automatically by RapidAPI. | | `X-No-Cache: true` | Optional on any `/v1/*` endpoint | Skips the cache read; writes the fresh result. | | `Cache-Control: no-cache` | Optional | Alias of the above. | ## Error codes | Status | Cause | Body | |--------|-------|------| | `400` | Required parameter missing or incompatible | `{"detail": "..."}` | | `401` | Missing or invalid `x-rapidapi-proxy-secret` | `{"error":"unauthorized","message":"invalid proxy secret"}` | | `410` | Listing deactivated (only `getPropertyDetail` / `getPromotionDetail`) | `{"detail": {"error":"ad_deactivated","message":"Ad no longer active","ad": {...}}}` | | `502` | Upstream auth/protocol/generic error | `{"error":"upstream_auth\|upstream_protocol\|upstream_error","message":"..."}` | | `503` | Circuit breaker open (IP flagged / 407) | `{"error":"upstream_unavailable","message":"..."}` | ## Domain concepts - **`locationIds`** — Idealista's hierarchical location tag, e.g. `"0-EU-ES-28"` (Madrid province), `"0-EU-ES-28-07-001-079"` (Madrid city). Obtain them with `listSpanishRegions`, `autocompleteLocations`, `reverseGeocodeLocation`, `resolveShortUris` or `listLocationChildren`. Passed in brackets: `locationIds=[0-EU-ES-28]`. - **`propertyType`** — `homes`, `newDevelopments`, `offices`, `premises`, `garages`, `storageRooms`, `lands`, `buildings`. - **`operation`** — `sale` | `rent`. `lands` and `buildings` only accept `sale`. - **`propertyCode` / `adid`** — listing ID; interchangeable between `searchListings` (`propertyCode`) and `getPropertyDetail` / `getPromotionDetail` (`adid`). - **~2,000-result depth limit** — a single search query only yields distinct listings for roughly its first ~2,000 results; deeper pages repeat listings already returned. `total`/`totalPages` remain accurate. To exhaustively crawl a location whose `total` exceeds ~2,000, re-issue the query against its children (`listLocationChildren`) recursively until every sub-query is under the limit (a ~1,800 threshold leaves a safe margin). Full recipe with a runnable script at `GET /integration-guide.html`. ## Endpoints ### `searchListings` — listing search `GET /v1/search` **Required query params** | Param | Type | Notes | |-------|------|-------| | `operation` | `sale` \| `rent` | Commercial operation | | `propertyType` | string | `homes`, `newDevelopments`, `offices`, `premises`, `garages`, `storageRooms`, `lands`, `buildings` | | `locationIds` **or** `parentPromotionId` | string | One of the two. `locationIds` format `[0-EU-ES-28]`. `parentPromotionId` lists a promotion's units | **Optional query params — context** | Param | Type | Default | Notes | |-------|------|---------|-------| | `country` | string 2 | `es` | `es` \| `pt` \| `it` | | `locale` | string | — | `es`, `en`, ... | **Pagination and ordering** | Param | Type | Default | Values | |-------|------|---------|--------| | `numPage` | int ≥ 1 | `1` | — | | `maxItems` | int 1–40 | `40` | — | | `order` | string | — | `publicationDate`, `price`, `priceDown`, `size`, `distance`, `weigh` | | `sort` | enum | — | `asc` \| `desc` | **Search filters** Dynamic per context. Call **`getSearchFilters(operation, propertyType, locationIds)`** first to get the applicable filters with their `dataType` and labels. Any unrecognized extra param is ignored without error. **Response 200** ```jsonc { "total": 28774, "totalPages": 720, "actualPage": 1, "itemsPerPage": 40, "elementList": [ { "propertyCode": "110715434", "price": 1400000, "propertyType": "flat", "operation": "sale", "address": "calle de Isabel la Católica", "size": 180, "rooms": 4, "bathrooms": 3, "latitude": 40.417, "longitude": -3.703, "thumbnail": "https://...", "url": "...", "hasVideo": false, "has3DTour": false, "multimedia": { "...": "..." }, "contactInfo": { "...": "..." } } ], "summary": ["..."], "searchTitle": "..." } ``` **Cache**: 5 min (`CACHE_TTL_SEARCH_SECONDS`). **Example** ```bash curl 'https://idealista-real-estate.p.rapidapi.com/v1/search?operation=sale&propertyType=homes&locationIds=%5B0-EU-ES-28%5D&priceTo=500000&maxItems=20' \ -H 'x-rapidapi-key: YOUR_RAPIDAPI_KEY' \ -H 'x-rapidapi-host: idealista-real-estate.p.rapidapi.com' ``` ### `getSearchFilters` — dynamic filters per context `GET /v1/search/filters` Lists the filters that apply to an operation+propertyType+location combo, with `dataType` and UI metadata. Useful for building adaptive UIs. > **Spain only.** Upstream does not publish this endpoint for `pt` or `it` — both return `502 upstream_error`. Filters themselves still work on `/v1/search` with `country=pt|it`; only the discovery catalog is missing. **Query params** | Param | Req. | Default | |-------|:----:|---------| | `operation` (`sale`\|`rent`) | ✅ | — | | `propertyType` | ✅ | — | | `locationIds` | ❌ | — | | `country` | ❌ | `es` | | `locale` | ❌ | `es` | **Response 200 (shape)** ```jsonc { "blocks": [ { "key": "rentalUsages", "text": "Uso", "settingsUI": { "component": "vertical" }, "filters": [ { "key": "longTermRental", "dataType": "boolean", "settingsUI": {"text":"Larga duración","component":"check"} }, { "key": "seasonalRental", "dataType": "boolean", "settingsUI": {"text":"Temporada","component":"check"} } ] }, { "key": "price", "filters": [{"key":"minPrice"},{"key":"maxPrice"}] }, { "key": "rooms", "filters": [{"key":"rooms0"},{"key":"rooms1"}] }, { "key": "features", "filters": [{"key":"petsAllowed"},{"key":"elevator"},{"key":"swimmingPool"}] } ] } ``` Observed blocks (`rent+homes` Madrid): `propertyType`, `price`, `rentalUsages`, `size`, `homeType`, `otherSubTypologies`, `rooms`, `bathrooms`, `state`, `furnished`, `features`, `floor`, `energyCertificate`, `multimedia`, `ad_type`, `sinceDate`, `showRuledOuts`. **Cache**: 7 days. ### Per-vertical shortcut endpoints Syntactic sugar over `searchListings` with `propertyType` preset. Identical response schema, 5-min cache TTL. All accept `locationIds` (required unless noted), `numPage`, `maxItems`, `order`, `sort`, `country`, `locale` and the `X-No-Cache` header. For the filters applicable to each vertical call **`getSearchFilters(operation, propertyType)`**; any recognized filter can be passed as a query param even if it's not in the public OpenAPI. | `operationId` | Path | `operation` | Preset / note | |---------------|------|-------------|---------------| | `listOffices` | `GET /v1/offices` | `sale`\|`rent` (req.) | `propertyType=offices` | | `listPremises` | `GET /v1/premises` | `sale`\|`rent` (req.) | `propertyType=premises` | | `listGarages` | `GET /v1/garages` | `sale`\|`rent` (req.) | `propertyType=garages` | | `listStorageRooms` | `GET /v1/storage-rooms` | `sale`\|`rent` (req.) | `propertyType=storageRooms` | | `listLands` | `GET /v1/lands` | fixed `sale` | `propertyType=lands` | | `listBuildings` | `GET /v1/buildings` | fixed `sale` | `propertyType=buildings` | | `listNewHomes` | `GET /v1/new-homes` | `sale` (default) \| `rent` | `propertyType=newDevelopments`. Extra flags: `finished`, `newDevelopmentInProject` | | `listRentHomes` | `GET /v1/rent-homes` | fixed `rent` | `propertyType=homes` | > `listNewHomes` returns **promotions** (one item per development), not individual homes. For the individual units inside a specific promotion use `listPromotionUnits`. > > `listUsedHomes` was retired: the `newDevelopment=false` preset has no effect (Idealista's mobile API discards that filter). For second-hand homes use `searchListings` with `operation=sale&propertyType=homes` and drop items with `newDevelopment=true` client-side. ### `getPropertyDetail` / `getPromotionDetail` — listing or promotion detail `GET /v1/property/{ad_id}` · `GET /v1/promotion/{promotion_id}` Same handler, same response schema, same cache key. Two `operationId`s so the SDK expresses intent: individual home (`getPropertyDetail`) vs new-development promotion (`getPromotionDetail`). The `propertyType` field in the response confirms which it is (`homes`/`flat`/`penthouse`/`chalet`/... vs `newDevelopments`). **Path params** | Param | Type | Notes | |-------|------|-------| | `ad_id` / `promotion_id` | int | `propertyCode` returned by `searchListings`, `listNewHomes`, `listRentHomes`, `listPromotionUnits`, etc. | **Query params** | Param | Default | Notes | |-------|---------|-------| | `country` | `es` | 2 chars | | `locale` | — | — | | `quality` | `high` | `high` (1500px) \| `low` (850px) | **Response 200** ```jsonc { "adid": 105103860, "price": 352000, "operation": "sale", "propertyType": "newDevelopments", "state": "...", "ubication": { "title": "...", "latitude": 40.38, "longitude": -3.67, "locationId": "0-EU-ES-28-07-001-079", "locationName": "Los Ángeles, Madrid", "hasHiddenAddress": false }, "country": "es", "propertyComment": "...", "multimedia": { "...": "..." }, "contactInfo": { "...": "..." }, "moreCharacteristics": { "...": "..." }, "energyCertification": { "...": "..." }, "has360VHS": false, "link": "https://...", "detailWebLink": "https://..." } ``` **Response 410** — deactivated listing: ```json { "detail": { "error": "ad_deactivated", "message": "Ad no longer active", "ad": { "adid": 105103860, "operation": "sale", "propertyType": "flat", "locationId": "0-EU-ES-28", "locationText": "Madrid", "suggestedTitle": "...", "lastDeactivationDate": 1776886524407 } } } ``` **Cache**: 30 min. ### `getPropertyStats` — listing analytics `GET /v1/property/{ad_id}/stats` Listing visibility metrics. **Query params**: `country` (default `es`), `language` (default `es`). **Response 200** ```jsonc { "views": { "value": 67599, "text": "67.599 visitas" }, "favorites": { "value": 1101, "text": "1.101 veces guardado como favorito" }, "contactMails": { "value": 254, "text": "254 contactos por email" }, "sentToFriend": { "value": 20, "text": "20 envíos a amigos" } } ``` **Cache**: 30 min. Use `X-No-Cache: true` if you need live values. ### `listSpanishRegions` — static map of Spanish regions and provinces `GET /v1/locations/regions` Static map of Spain's 17 autonomous communities plus Ceuta and Melilla, each with its provinces' `locationId`s directly usable in search. **No upstream request** — ideal seed for region selectors or nationwide crawls. No parameters. **Response 200** ```jsonc { "regions": [ { "code": "CT", "name": "Cataluña", "type": "comunidad", // "comunidad" | "ciudad_autonoma" "provinces": [ { "locationId": "0-EU-ES-08", "name": "Barcelona" }, { "locationId": "0-EU-ES-17", "name": "Girona" }, { "locationId": "0-EU-ES-25", "name": "Lleida" }, { "locationId": "0-EU-ES-43", "name": "Tarragona" } ] } ] } ``` 19 entries, 52 provinces total (50 + Ceuta + Melilla). **Cache**: constant response; sends `Cache-Control: public, max-age=2592000` (30 days). ### `autocompleteLocations` — free-text location search `GET /v1/locations/autocomplete` Mixes official locations (with a `locationId` usable in `searchListings`) and POIs (streets, metro stations — no `locationId`). **Query params** | Param | Req. | Default | |-------|:----:|---------| | `query` (≥ 2 chars) | ✅ | — | | `operation` | ❌ | — | | `propertyType` | ❌ | — | | `locale` | ❌ | — | | `country` | ❌ | `es` | **Response 200** ```jsonc { "locations": [ { "locationId": "0-EU-ES-28-07-001-079", "name": "Malasaña, Madrid", "total": 123 } ] } ``` ### `reverseGeocodeLocation` — reverse geocoding `GET /v1/locations/by-coordinates` Given WGS84 `lat`/`lng`, returns the administrative chain and nearby POIs. **Query params** | Param | Type | Req. | Default | |-------|------|:----:|---------| | `lat` | float -90..90 | ✅ | — | | `lng` | float -180..180 | ✅ | — | | `country` | string 2 | ❌ | `es` | | `locale` | string | ❌ | — | **Response 200**: same `{ "locations": [...] }` schema. Many items are POIs without a reusable `locationId`. **Cache**: 7 days. ### `resolveShortUris` — resolve short codes `POST /v1/locations/by-shorturis` Resolves Idealista short codes (`[xxx]` format) to the real `locationId`. **Query params**: `country` (default `es`). **JSON body** ```jsonc { "operation": "sale", // sale | rent "propertyType": "newDevelopments", "shortUris": "[bd4]", // bracketed code (string, not array) "order": "weigh", "sort": "desc", "maxItems": 0, // 0 = metadata only, no items "locale": "es" } ``` **Response 200** ```jsonc { "locations": [ { "locationId": "0-EU-ES-28", "name": "Madrid provincia", "total": 123 } ] } ``` **Cache**: 7 days. ### `listLocationChildren` — geographic children `GET /v1/locations/{location_id}/children` Hierarchy: province → municipalities → districts → neighbourhoods. **Path params**: `location_id` (Idealista tag, e.g. `0-EU-ES-28`). **Query params**: `country` (default `es`), `locale`. **Response 200** ```jsonc { "locations": [ { "locationId": "0-EU-ES-28-06", "name": "Corredor del Henares, Madrid", "total": 1234, "numSubLocations": 25 }, { "locationId": "0-EU-ES-28-07-001-079", "name": "Madrid, Madrid", "total": 28774 } ] } ``` > The `total` field in children responses is not reliable (often `0`). To count listings for a location, call the relevant search endpoint with `maxItems=1` and read `total`. **Cache**: 7 days. ### `listPromotionUnits` — units of a promotion `GET /v1/promotion/{promotion_id}/units` Individual listings inside a new-development promotion. Internally calls `searchListings` with `parentPromotionId=` and `propertyType=homes`. **Path params**: `promotion_id` (string). **Standard query params**: `operation` (`sale`\|`rent`, default `sale`), `numPage`, `maxItems`, `country`, `order`, `sort`, `locale`. **Filters**: check `getSearchFilters(operation="sale", propertyType="newDevelopments", locationIds=)` for the applicable set. Filters that make no sense for new developments (e.g. `occupationType`, `furnished`, `petsAllowed`, rent-only ones, etc.) are ignored without error. **Response 200**: same schema as `searchListings`. **Cache**: 5 min. ## Summary matrix | Method | Path | `operationId` | Cache | Billed | |--------|------|---------------|-------|:------:| | GET | `/integration-guide.html` | — (HTML integration guide) | 1 h | ❌ | | GET | `/locations-es-index.json` | — (pre-computed Spanish segment map, rent+sale) | 24 h | ❌ | | GET | `/locations-it-index.json` | — (pre-computed Italian segment map, rent+sale) | 24 h | ❌ | | GET | `/openapi.json` | — (static OpenAPI 3.1.0 spec) | 1 h | ❌ | | GET | `/v1/search` | `searchListings` | 5 min | ✅ | | GET | `/v1/search/filters` | `getSearchFilters` | 7 d | ✅ | | GET | `/v1/offices` | `listOffices` | 5 min | ✅ | | GET | `/v1/premises` | `listPremises` | 5 min | ✅ | | GET | `/v1/garages` | `listGarages` | 5 min | ✅ | | GET | `/v1/storage-rooms` | `listStorageRooms` | 5 min | ✅ | | GET | `/v1/lands` | `listLands` | 5 min | ✅ | | GET | `/v1/buildings` | `listBuildings` | 5 min | ✅ | | GET | `/v1/new-homes` | `listNewHomes` | 5 min | ✅ | | GET | `/v1/rent-homes` | `listRentHomes` | 5 min | ✅ | | GET | `/v1/property/{ad_id}` | `getPropertyDetail` | 30 min | ✅ | | GET | `/v1/promotion/{promotion_id}` | `getPromotionDetail` | 30 min | ✅ | | GET | `/v1/property/{ad_id}/stats` | `getPropertyStats` | 30 min | ✅ | | GET | `/v1/locations/regions` | `listSpanishRegions` | 30 d | ✅ | | GET | `/v1/locations/autocomplete` | `autocompleteLocations` | 7 d | ✅ | | GET | `/v1/locations/by-coordinates` | `reverseGeocodeLocation` | 7 d | ✅ | | POST | `/v1/locations/by-shorturis` | `resolveShortUris` | 7 d | ✅ | | GET | `/v1/locations/{location_id}/children` | `listLocationChildren` | 7 d | ✅ | | GET | `/v1/promotion/{promotion_id}/units` | `listPromotionUnits` | 5 min | ✅ | ## Common integration patterns 1. **Search by city** → `autocompleteLocations(query="Madrid")` → take `locationId` → `searchListings(operation, propertyType, locationIds=[])` → paginate `numPage`. 2. **Search by map** → `reverseGeocodeLocation(lat, lng)` → keep results that have a `locationId` → `searchListings(...)`. 3. **Resolve an Idealista short URL** (`idealista.com/.../[bd4]/`) → `resolveShortUris({shortUris:"[bd4]", operation, propertyType})` → use the returned `locationId`. 4. **Dynamic filter UI** → `getSearchFilters(operation, propertyType, locationIds)` → render `blocks[*].filters` by `dataType` and `settingsUI.component`. 5. **Listing detail + analytics** → parallel calls `getPropertyDetail(ad_id)` and `getPropertyStats(ad_id)`. For a new-development promotion use `getPromotionDetail(promotion_id)`. 6. **New developments → units** → `listNewHomes(locationIds)` → for each promotion `listPromotionUnits(promotion_id=propertyCode)`. 7. **Exhaustive crawl of a large area** → seed with `listSpanishRegions` → for each province, if `total > ~2000` (check with `maxItems=1`) recurse into `listLocationChildren` until every segment is under the limit → paginate each segment → dedupe by `propertyCode`. Runnable script at `GET /integration-guide.html`. Shortcut: **`GET /locations-es-index.json`** and **`GET /locations-it-index.json`** (free, unauthenticated) are pre-computed national segment maps for `rent` and `sale` homes — start from one instead of spending the segmentation calls. (`/locations-index.json` is the legacy name and now `301`s to the Spanish one.) 8. **Force fresh data** (dashboards, tests) → add the `X-No-Cache: true` header. ## Robustness notes - Schemas are permissive; don't assume the payload is limited to what's documented. - Search endpoints may return `total=0` with `elementList=[]` — not an error. - Search pagination degrades past ~2,000 results per query (repeated listings, see domain concepts); split by child locations instead of paging deeper. - `502 upstream_protocol` usually means an upstream contract change: catch and retry with backoff; notify maintenance. - `503 upstream_unavailable` = circuit breaker open (IP flagged or persistent 407); exponential backoff and retry later. - `410 ad_deactivated` is terminal: don't retry, the listing is gone from the system.