How to get Imovirtual listings in Python
By the end of this guide you have a CSV of Lisbon-area flats for rent from Imovirtual with rent, rooms and size, from one script you can run again tomorrow. Everything below was run on 16 October 2026; the output table is what it returned.
Why scraping Imovirtual yourself breaks
- Imovirtual has no public API for reading listings. Agencies and developers publish through partner CRMs, and Imovirtual is listed on the OLX Group partner API for posting adverts.
- The results are embedded in each page as Next.js data (__NEXT_DATA__), which you have to dig the listings out of.
- Searches use Imovirtual's own place names from its links ("lisboa", "porto").
None of that is impossible, but each point is code you then maintain. The rest of this guide uses Listingrail, which does the reading and hands back clean JSON. The Imovirtual API page has the official options with sources.
1. Get a key
One request with your email returns a key with 100 free listings. It is shown once, so keep it in an environment variable.
curl -X POST https://listingrail.com/v1/signup \
-H "content-type: application/json" \
-d '{"email": "you@example.com"}'
export LISTINGRAIL_KEY=re_...2. Search and save to CSV
Install requests (pip install requests), then run:
import csv
import os
import requests
r = requests.post(
"https://listingrail.com/v1/listings/search",
headers={"Authorization": f"Bearer {os.environ['LISTINGRAIL_KEY']}"},
json={"source":"imovirtual","location":"lisboa","type":"rent","limit":50},
timeout=300,
)
r.raise_for_status()
listings = r.json()["listings"]
fields = ["price", "currency", "rooms", "area", "address", "url"]
with open("imovirtual-lisbon.csv", "w", newline="", encoding="utf-8") as f:
writer = csv.writer(f)
writer.writerow(fields)
for home in listings:
writer.writerow([home[k] for k in fields])
print(len(listings), "listings saved to imovirtual-lisbon.csv")The request body, on its own:
{
"source": "imovirtual",
"location": "lisboa",
"type": "rent",
"limit": 50
}Each listing returned costs one credit. A search that finds nothing, or fails at Imovirtual, costs nothing. A request can take up to a minute or two, because Imovirtual is read at that moment.
3. What comes back
The first three rows of the run on 16 October 2026:
Every listing has the same keys on all 17 portals, so the code for Imovirtual reads the others too. Fields Imovirtual does not show come back as null, and the portal's own record sits in raw. With pandas:
import pandas as pd
df = pd.DataFrame(listings)
df["agent"] = df["agent"].str.get("company")
print(df[["price", "rooms", "area", "address", "agent"]].head())4. Get new listings and price cuts every day
Running the script on a schedule works, but then you compare the files yourself. A monitor stores the search, checks it once a day, and sends only what changed: new listings and price cuts. The free key holds one monitor.
requests.post(
"https://listingrail.com/v1/monitors",
headers={"Authorization": f"Bearer {os.environ['LISTINGRAIL_KEY']}"},
json={
"source": "imovirtual",
"location": "lisboa",
"filters": {"type":"rent"},
"webhook_url": "https://example.com/new-listings",
},
timeout=30,
)Changes can also be read with GET /v1/monitors/{id}/changes or downloaded as CSV. Plans and limits are on the monitors page.
Imovirtual quirks worth knowing
- Pass a place as written in Imovirtual links, e.g. "lisboa" or "porto". "lisboa" is the district: this run included flats in Oeiras and Amadora.
- rooms is Imovirtual's room count, not the T number in the title: in this run a T1 had rooms 2 and a T2 rooms 3. beds is null.
- Some addresses start with the postcode ("1000-162 | Arroios, ..."). area is in square metres.
- agent.company is null for private owners (raw.is_private_owner), as for all three rows below.
Questions
Does Imovirtual have an API?
Not for reading. Its integrations publish an agency's own listings from a CRM.
How do I get bedrooms?
Read the T number in the title (T2 is two bedrooms). In this run rooms was one more than the T number.
Does it cover homes for sale?
Yes: use "type": "sale".