apidea

Idealista API with Python: search listings and export a CSV

Search rental homes in Madrid, filter by price and save the results to a CSV. This guide includes a complete Python and HTTPX script you can adapt to your location.

apidea is an unofficial third-party API. It is not an Idealista product and is not affiliated with, endorsed or sponsored by Idealista S.A.U. Listings belong to Idealista and their original advertisers.

1. Set up Python and your API key

You need Python 3.12 or later and a subscription to apidea on RapidAPI. Choose a plan for your usage and copy your key from RapidAPI. Quotas and billing are managed there; each request made by the script counts toward your plan usage.

This example uses a RapidAPI key. Official Idealista API credentials do not work with these endpoints. If you are looking for the official service, its Search API access request is separate from apidea.

Create an environment and install the HTTP client. On macOS or Linux:

python3 -m venv .venv
source .venv/bin/activate
python -m pip install httpx
export RAPIDAPI_KEY='YOUR_RAPIDAPI_KEY'

On Windows, using PowerShell:

py -m venv .venv
.\.venv\Scripts\Activate.ps1
python -m pip install httpx
$env:RAPIDAPI_KEY = 'YOUR_RAPIDAPI_KEY'

The key is read from an environment variable. Keep it out of your Python file and source repository. Requests run on your computer or server.

2. Search listings and export a CSV

Save the following code as idealista_export.py. It searches for rental homes up to €1,500 in Madrid city, with the newest listings first. It fetches up to three pages of 40 results, deduplicates by propertyCode and writes idealista.csv.

This is a sample of up to 120 listings, not a complete Madrid export. It makes at most three search requests, with no automatic retries or property-detail calls.

import csv
import os
import time

import httpx

HOST = "idealista-real-estate.p.rapidapi.com"
MAX_PAGES = 3
OUTPUT = "idealista.csv"
PARAMS = {
    "country": "es",
    "locale": "es",
    "operation": "rent",
    "propertyType": "homes",
    "locationIds": "[0-EU-ES-28-07-001-079]",
    "priceTo": 1500,
    "order": "publicationDate",
}
FIELDS = [
    "propertyCode", "price", "size", "rooms",
    "municipality", "address", "url",
]


def fetch_listings(client, params, max_pages=MAX_PAGES):
    if not 1 <= max_pages <= 50:
        raise ValueError("Use 1 to 50 pages; split larger searches by location.")
    listings = []
    seen = set()
    total = 0
    for page in range(1, max_pages + 1):
        response = client.get(
            "/v1/search",
            params={**params, "numPage": page, "maxItems": 40},
        )
        response.raise_for_status()
        data = response.json()
        if page == 1:
            total = data["total"]
        for item in data["elementList"]:
            code = item["propertyCode"]
            if code not in seen:
                seen.add(code)
                listings.append(item)
        if not data["elementList"] or page >= data["totalPages"]:
            break
        if page < max_pages:
            time.sleep(1)
    return listings, total


def csv_cell(value):
    # Keep text from being interpreted as a spreadsheet formula.
    if isinstance(value, str) and value.startswith(("=", "+", "-", "@", "\t", "\r", "\n")):
        return "'" + value
    return value


def write_csv(listings):
    with open(OUTPUT, "w", newline="", encoding="utf-8-sig") as file:
        writer = csv.DictWriter(file, fieldnames=FIELDS)
        writer.writeheader()
        for item in listings:
            writer.writerow({field: csv_cell(item.get(field)) for field in FIELDS})


def main():
    key = os.environ.get("RAPIDAPI_KEY")
    if not key:
        raise SystemExit("Set the RAPIDAPI_KEY environment variable first.")
    headers = {
        "x-rapidapi-key": key,
        "x-rapidapi-host": HOST,
        "accept": "application/json",
    }
    try:
        with httpx.Client(
            base_url=f"https://{HOST}", headers=headers, timeout=30.0,
        ) as client:
            listings, total = fetch_listings(client, PARAMS)
    except httpx.HTTPStatusError as exc:
        raise SystemExit(
            f"HTTP {exc.response.status_code}: check the tutorial's error table."
        ) from exc
    except httpx.RequestError as exc:
        raise SystemExit("Network error or timeout; try again later.") from exc
    write_csv(listings)
    print(f"Saved {len(listings)} of {total} matches to {OUTPUT}.")
    print(f"Page limit: {MAX_PAGES}. This may be a partial export.")


if __name__ == "__main__":
    main()

Run it in the terminal where you set your key:

python idealista_export.py

The CSV is saved in your current directory. Open it in Excel or LibreOffice, or import it into Google Sheets. It uses UTF-8 with a BOM to preserve accented characters. A successful run overwrites any existing idealista.csv; change OUTPUT to keep previous exports.

An empty search creates a CSV with just the header row. If a request fails, the program stops before writing the file. Missing fields become empty cells.

3. Understand the response

GET /v1/search returns a JSON object. This is a fictional, shortened example showing its structure:

{
  "total": 1,
  "totalPages": 1,
  "actualPage": 1,
  "itemsPerPage": 40,
  "elementList": [
    {
      "propertyCode": "123456789",
      "propertyType": "flat",
      "operation": "rent",
      "price": 1200,
      "size": 65,
      "rooms": 2,
      "municipality": "Madrid"
    }
  ]
}

elementList holds the listings. total is the number of matches, totalPages the number of pages and actualPage the returned page. Use propertyCode to remove duplicates or request property details separately. Price, size and other fields may be null or unavailable.

4. Change locations and filters

Edit the script's PARAMS dictionary. Parameter names are case-sensitive; for example, search takes locationIds, with a plural “s”.

ParameterHow to use it
operationrent for rentals; sale for sales. Adjust the price limit when switching operations.
locationIds[0-EU-ES-28-07-001-079] is Madrid city. [0-EU-ES-28] covers the entire province. Keep the brackets.
priceToMaximum price. Remove this entry to search without that limit.
bedroomsA string listing the desired counts: "2,3" for two or three bedrooms.
countryes, pt or it. It must match the location's country.
localeText language, such as es or en. It does not change the search country.

For another city, call GET /v1/locations/autocomplete with query and country, then select a result with a locationId: some points of interest do not have one. Put the identifier inside brackets in locationIds. The location recipe shows the request. Changing only country while keeping Madrid's identifier will not work.

Available filters depend on context. Call GET /v1/search/filters with the same operation, property type and location before adding more filters to the example.

5. Pagination, limits and errors

numPage starts at 1 and maxItems accepts 1 to 40. The script stops at MAX_PAGES, the last page or an empty page. It waits one second between pages; adjust the pace to your plan's limits.

A single query only yields roughly the first 2,000 distinct listings. Increasing MAX_PAGES does not guarantee a complete export: the script allows at most 50 pages, but large searches need to be split by districts or neighbourhoods. See the location-based crawling guide. Listings may change while you paginate; deduplication does not make a search a fixed snapshot.

Search responses are cached for five minutes. If you need to force an update, see the cache options.

ErrorWhat to check
400 / 422Parameter names, types and values. An unknown name causes 400 unknown_query_parameter; fix it before repeating the request.
401 / 403Your RapidAPI key, subscription and host. Check that the subscription is for this API.
404Path or location. An obsolete location returns location_not_found; resolve it again.
429Gateway quota or request rate. Check your plan and respect Retry-After if it is included in the response.
502 / 503Upstream service failure. Wait and retry with a limit; contact support if it persists.

The example stops on these errors. Read the error reference before adding retries.

6. Continue your integration

For property details, new developments or wider areas, continue with the integration cookbook. The OpenAPI specification documents parameters and responses; llms.txt collects the contract for coding assistants.

View plans and get your key on RapidAPI →

For help with this integration, email contacto@apidea.es.