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.
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”.
| Parameter | How to use it |
|---|---|
operation | rent 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. |
priceTo | Maximum price. Remove this entry to search without that limit. |
bedrooms | A string listing the desired counts: "2,3" for two or three bedrooms. |
country | es, pt or it. It must match the location's country. |
locale | Text 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.
| Error | What to check |
|---|---|
| 400 / 422 | Parameter names, types and values. An unknown name causes 400 unknown_query_parameter; fix it before repeating the request. |
| 401 / 403 | Your RapidAPI key, subscription and host. Check that the subscription is for this API. |
| 404 | Path or location. An obsolete location returns location_not_found; resolve it again. |
| 429 | Gateway quota or request rate. Check your plan and respect Retry-After if it is included in the response. |
| 502 / 503 | Upstream 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.