# Fotocasa Scraper $0.95/1K — Spain Real Estate Data & Agencies (`oswaldocarabano/fotocasa-scraper`) Actor

Scrape Fotocasa.es property listings for sale and rent across Spain: price, surface, GPS, the full 8-level location hierarchy with IDs, photos tagged by room, and the listing agency with its phone. Median 45 of 78 fields filled. Energy certificates and street via detail pages. No login.

- **URL**: https://apify.com/oswaldocarabano/fotocasa-scraper.md
- **Developed by:** [Oswaldo Carabano](https://apify.com/oswaldocarabano) (community)
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $0.95 / 1,000 property listings

This Actor is paid per event. You are not charged for the Apify platform usage, but only a fixed price for specific events.

Learn more: https://docs.apify.com/actors/running/actors-in-store.md#pay-per-event

## What's an Apify Actor?

An Actor is a serverless cloud program that runs on the Apify platform. It has two run modes.
In Batch mode, an Actor accepts a well-defined JSON input, performs an action which can take anything from a few seconds to a few hours,
and optionally produces a well-defined JSON output, datasets with results, or files in key-value store.
In Standby mode, an Actor provides a web server which can be used as a website, API, or an MCP server.

Apify vocabulary and the platform model are defined once, in the agent quickstart at https://apify.com/agents.md.

## How to integrate an Actor?

If asked about integration, you help developers integrate Actors into their projects.
You adapt to their stack and deliver integrations that are safe, well-documented, and production-ready.

Do not guess an integration path. Every one of them is in the agent quickstart at https://apify.com/agents.md: the Apify MCP server, Agent Skills with the Apify CLI, the JavaScript and Python clients, the REST API, and the account-free path for an agent with no human to sign in. It also carries the rule on stating cost before the first paid run.

For examples already wired to this Actor's own input schema, see the [API](#api) section below.

Each client library has reference documentation the quickstart does not restate: [JavaScript/TypeScript](https://docs.apify.com/api/client/js/docs.md) (`npm install apify-client`) and [Python](https://docs.apify.com/api/client/python/docs.md) (`pip install apify-client`).

# README

## Fotocasa Scraper — Spain real estate data, agencies and geo

**Scrape property listings from [Fotocasa.es](https://www.fotocasa.es), Spain's
second-largest real estate portal (Adevinta).** Homes, commercial units, land,
garages and storage rooms, for sale and for rent, anywhere in Spain.

Every row carries the price, the surface, the **full eight-level location
hierarchy with numeric IDs**, the GPS point, the listing **agency with its
phone**, and every photo tagged by the room it shows.

**No login. No session cookies. No browser.** $0.95 per 1,000 listings.

***

### What you can use this for

| If you are… | You want |
|---|---|
| Building a **Spain real estate dataset** or market model | the nationwide listing feed, with price, surface and GPS on every row (n = 3,184) |
| Doing **real estate lead generation** | the agency directory: name, business phone, logo and profile URL |
| Running a **property data API** inside your product | structured JSON over the Apify API, with the age of every row declared |
| Tracking **housing market data** or price movements | `published_at`, `price_reduced` and a run you can schedule daily |
| Valuing or comparing properties | energy certificate, construction type, plot surface and street from detail pages |
| Researching a specific market — Madrid, Barcelona, Costa del Sol, Valencia | any location in Fotocasa's own tree, from country down to neighbourhood |

***

### What actually arrives in a row

Most Fotocasa scrapers give you a price, a size and a URL. This one gives you a
**median of 45 populated fields out of 78** — and **58 of 78** when you turn on
detail pages.

Every percentage on this page was measured against the live site on 18 Sep 2026,
and every one of them carries its sample size. **We do not list a field whose
fill rate we have not measured.**

#### Always there — 100% (n = 3,184 listings across 16 search profiles)

`id` · `url` · `price` · `currency` · `operation` · `property_type` ·
`published_at` · `description` · `latitude` · `longitude` · `postal_code` ·
`country` · `region` · `province` · `municipality` and their numeric IDs ·
`photo_count` · `advertiser_type_id`

> **The location hierarchy is the part nobody else ships whole.** Eight levels,
> each with its name *and* its fotocasa ID, so you can group and join across runs
> without matching strings.

#### Usually there

| Field | Fill rate | n |
|---|---:|---:|
| `surface_m2` | 99.9% | 3,184 |
| `agency_phone` *(professional advertisers)* | 96.4% | 1,911 |
| `district` | 90.2% | 3,184 |
| `bathrooms` | 73.5% | 3,184 |
| `rooms` | 67.9% | 3,184 |
| `neighborhood` | 64.9% | 3,184 |
| `conservation_state_id` | 50.5% | 3,184 |
| `has_air_conditioning` | 49.4% | 3,184 |
| `floor` | 45.7% | 3,184 |
| `has_elevator` | 44.0% | 3,184 |
| `has_heating` | 39.9% | 3,184 |
| `price_reduced` | 28.3% | 3,184 |
| `has_terrace` | 29.0% | 3,184 |
| `has_balcony` | 13.9% | 3,184 |
| `has_garden` | 7.1% | 3,184 |
| `has_swimming_pool` | 6.4% | 3,184 |
| `has_parking` | 3.7% | 3,184 |

**`rent_periodicity_id` is 98.9% on rentals and 0% on sales** (n = 784 / 2,400),
because it only exists on a rental. Quoting a single blended number for it would
be meaningless, so we do not.

#### Photos tagged by room

`room_types_photographed` tells you which rooms a listing actually photographed —
kitchen, bathroom, terrace, garden, bedroom, living room, exterior. **81.5% of
listings have at least one tagged photo** (n = 840). No competing actor exposes
this.

#### What we do NOT promise

Four fields come back empty every single time, so they are not in the schema and
you will never see them: **storage room, furnished flag, photo-report flag and
external contact**. `otherFeatures` was `null` in **0 of 3,184** listings, so it
is gone too.

***

### The agency dataset — a second product, at zero extra cost

Turn on `includeAgencies` and you get a separate `agencies` dataset: name, phone,
logo and profile URL of every professional agency found. There are roughly **7
distinct agencies per 30 listings**, and building it costs **zero extra
requests** — the data is already in the search response.

**No competing actor in this niche ships an agency dataset.**

***

### Detail pages — read this before turning them on

`scrapeDetails` adds **78 fields that only exist on the listing page**:

| Field | Fill rate | n |
|---|---:|---:|
| `created_at`, `construction_type`, `ground_surface_m2` | 100% | 220 |
| `energy_certificate_value`, `environment_impact_value` | 100% | 220 |
| `energy_certificate_rating`, `environment_impact_rating` | 99.1% | 220 |
| `extra_features` | 98.6% | 220 |
| `quality_rate` | 83.6% | 220 |
| **`street_name`** | **72.0%** | 150 |
| `agency_web` | 62.7% | 220 |
| **`street_number`** | **22.0%** | 150 |

#### Two honest caveats

**1. Street number only when fotocasa says the address is exact.** We measured
three address visibility modes (n = 150): exact 22.0%, and two blurred modes at
50.0% and 28.0%. We ship `street_name` whenever fotocasa publishes it, `street_number`
only in exact mode, and **`address_is_exact` plus `address_visibility_mode` on
every row** so you can filter by precision instead of trusting us.

**2. A detail page costs 65× a listing.** 614 KB against 9.4 KB. That is why
detail pages are **off by default** and billed as a separate event: switching
them on by default would multiply your bill by 65 without asking.

***

### Privacy: whose phone you get, and whose you don't

fotocasa has two kinds of advertiser, and this actor treats them differently on
purpose.

Measured on n = 2,164 listings, sampled in rural provinces where private
advertisers concentrate.

| | Professional agency | Private individual |
|---|---:|---:|
| Share of listings | 88.3% | **11.7%** |
| Phone present in the source | 96.4% | **0.0%** |
| What we ship | name, phone, logo, profile | **contact fields redacted** |

An agency phone is professional contact data and is part of what you are buying.
A private individual's is not, and we do not ship it — **including the phone
numbers typed inside the free-text description, which 2.4% of private listings
contain** (n = 124). Those we strip.

If an advertiser type appears that we have not verified, the actor **redacts
first and asks questions later**, flags the row via
`is_professional_advertiser: false`, and does not charge for it.

***

### Data freshness

Every row carries **`from_cache`, `fetched_at` and `data_age_hours`**. You always
know how old your data is. `maxCacheAgeDays: 0` forces a fresh fetch.

***

### How the crawl works, and why there is a cap

The search API stops returning new results after **page 392** — and then quietly
returns *the same 30 listings again* with HTTP 200 forever. So this actor never
trusts a status code: it detects the end of pagination by **duplicate IDs**, and
it never charges you for a duplicate.

To go past 11,760 listings per query it **splits by fotocasa's own location
tree**, using the child counters fotocasa returns in every response. We verified
the split is exactly disjoint in **17 of 18 partitions** across 6 provinces and 3
levels — and the actor detects the 18th case, where a node returns siblings
instead of children, and paginates instead of descending.

**There is no download-everything mode.** `maxItems` is a hard cap. The Spanish
sale inventory alone is about 470,000 listings, and extracting a substantial part
of a database is exactly what the EU sui generis database right protects against.

***

### Input

| Field | Default | Notes |
|---|---|---|
| `location` | all of Spain | fotocasa `combinedLocationIds` |
| `transactionType` | `sale` | `sale`, `rent` |
| `propertyType` | `home` | home, commercial, land, garage, storage |
| `sortBy` | `relevance` | relevance, price, newest, surface, rooms |
| `minPrice` / `maxPrice` / `minRooms` / `minSurface` / `maxSurface` | — | |
| `maxItems` | 1000 | hard cap |
| `scrapeDetails` | `false` | 65× the bandwidth, separate event |
| `includeAgencies` | `false` | zero extra requests |
| `maxCacheAgeDays` | 7 | 0 = always fresh |

**Every option here was verified to change the result against the live API.**
fotocasa silently ignores parameters it does not recognise and returns the entire
catalogue with HTTP 200 — we tested five plausible-looking filters that do
exactly that, and none of them are in this form. A filter that does not filter is
worse than no filter, because you cannot tell.

***

### Pricing

| Event | Price |
|---|---:|
| Actor start | $0.00001 |
| **Property listing** | **$0.00095** |
| Agency | $0.002 |
| Detail page | $0.006 |

You are never charged for an error row, a duplicate, a listing without a price,
or a row whose advertiser contact had to be redacted.

***

### Support and data removal

Questions, bugs and feature requests: open an issue on the actor page. We answer
within 48 hours.

**Data removal:** if a listing concerns you and you want it out of our cache,
write to **privacy@actorstack.dev**. We keep a suppression list, and removal does
not require a redeploy.

***

### Frequently asked questions

**What data can I scrape from Fotocasa?**
Price, surface in m², rooms, bathrooms, floor, property type and operation
(sale/rent), the GPS coordinates, the postcode, the full eight-level location
hierarchy with Fotocasa's own numeric IDs, the publication date, the description,
every photo URL tagged by room, and the listing agency with its name, phone, logo
and profile URL. A median of 45 fields out of 78 come back populated (n = 3,184).

**Can I get real estate agency phone numbers from Fotocasa?**
Yes, for professional agencies: the phone came back on 96.4% of the 1,911
professional advertisers measured. Turn on `includeAgencies` for a separate
deduplicated agency dataset. Private individuals are excluded — Fotocasa does not
publish their phone at all (0% of 253 measured) and this actor redacts their
contact details, including phone numbers typed inside the description text.

**Does it need a Fotocasa account or login?**
No. There is no account, no session cookie and no browser. The actor reads the
same public search endpoint the website itself calls.

**How much does it cost?**
$0.00095 per listing, which is $0.95 per 1,000. Agencies are $0.002 each and
detail pages $0.006. You are never charged for an error row, a duplicate, a
listing without a price, or a row whose advertiser contact had to be redacted.

**How many listings are there on Fotocasa?**
About 470,000 homes for sale in Spain at the time of measuring, plus 52,020
rentals, 78,064 land plots, 39,178 commercial units, 8,047 storage rooms and
2,200 garages. `maxItems` caps every run — there is no bulk-mirror mode.

**Can I scrape a specific city or province?**
Yes. Pass any `combinedLocationIds` from Fotocasa's own location tree: Spain,
a province like Barcelona or Málaga, a city like Madrid, or a single
neighbourhood. The actor walks the tree automatically when a location holds more
listings than one query can return.

**Can I get the street address?**
Partly, and we are precise about it. The street name comes back on 72% of detail
pages, and the street number only when Fotocasa marks the address as exact —
22% of detail pages (n = 150). Every row carries `address_is_exact` and
`address_visibility_mode` so you can filter by precision rather than trust us.

**Does it get energy performance certificates?**
Yes, from detail pages: the efficiency rating letter on 99.1% and its kWh/m²
value on 100% (n = 220), plus the CO₂ emissions rating and value.

**How fresh is the data?**
Every row declares `from_cache`, `fetched_at` and `data_age_hours`. Set
`maxCacheAgeDays: 0` to always fetch fresh.

**How does it compare to other Fotocasa scrapers?**
The difference is fields per row and honesty about them: this actor publishes the
measured fill rate of every field with its sample size, ships the location
hierarchy whole, tags photos by room, and builds an agency dataset that no
competing actor offers. It is also slightly cheaper than the market floor.

***

### Disclaimer

This actor extracts **publicly available** listing data — no login, no session
cookies, no account. You are responsible for how you use the data, including
compliance with the GDPR and with fotocasa's terms where they apply to you. It is
not affiliated with or endorsed by fotocasa or Adevinta.

# Actor input Schema

## `location` (type: `string`):

fotocasa location ID (combinedLocationIds). Defaults to all of Spain. Every response carries the child locations of whatever you ask for, so the actor walks the tree for you: country → province → comarca → municipality → district → neighbourhood. Examples: Spain 724,0,0,0,0,0,0,0,0 · Madrid city 724,14,28,173,0,28079,0,0,0 · Barcelona province 724,9,8,0,0,0,0,0,0 · Malaga province 724,1,29,0,0,0,0,0,0.

## `transactionType` (type: `string`):

Buy or rent. New-build (transactionTypeId 4) is not offered because it returns zero results against the live API.

## `propertyType` (type: `string`):

Counts measured against the live API on 18 Sep 2026.

## `sortBy` (type: `string`):

Only the sort values that were verified to change the result against the live API. fotocasa silently ignores sort values it does not know and returns the default order, so unverified options are not offered here.

## `sortDescending` (type: `boolean`):

Descending by default. With sortBy = price, turn this off for cheapest first — verified to actually reverse the order against the live API.

## `minPrice` (type: `integer`):

Only listings priced at or above this. Verified against the live API: it moves the count from 470,340 to 302,763 nationwide.

## `maxPrice` (type: `integer`):

Only listings priced at or below this. Verified against the live API.

## `minRooms` (type: `integer`):

Only listings with at least this many rooms. Verified against the live API.

## `minSurface` (type: `integer`):

Only listings of at least this many square metres. Verified against the live API.

## `maxSurface` (type: `integer`):

Only listings of at most this many square metres. Verified against the live API.

## `maxItems` (type: `integer`):

Hard cap for this run. There is no download-everything mode: the Spanish sale inventory alone is roughly 470,000 listings, and extracting a substantial part of a database is what the EU sui generis database right protects against.

## `scrapeDetails` (type: `boolean`):

Adds street name and number, energy certificate, creation date, construction type, plot surface and quality score — 78 fields that only exist on the listing page. Costs 65x more bandwidth per listing than the search API, so it is billed as a separate event and is off by default.

## `includeAgencies` (type: `boolean`):

Writes a separate `agencies` dataset with name, phone, logo and profile URL of every professional agency found. Costs zero extra requests: the data is already in the search response. Private advertisers are never included.

## `respectRobotsTxt` (type: `boolean`):

Off by default, so detail pages are fetched for every listing. Turn it on to have the actor honour fotocasa's robots.txt on the website host, which skips detail pages for some locations. This has no effect on listings: the search API is a different host that publishes no robots.txt.

## `maxCacheAgeDays` (type: `integer`):

Serve from cache when the stored copy is younger than this. 0 always fetches fresh. Every row declares from\_cache, fetched\_at and data\_age\_hours so you always know how old the data is.

## `maxConcurrency` (type: `integer`):

Kept low on purpose. The API answered 24 parallel requests without a single non-200, but starting below a known ceiling rather than at it is what keeps a scraper working next month.

## `proxyConfiguration` (type: `object`):

Optional. The actor reaches fotocasa fine without a proxy and falls back to a Spanish residential IP automatically if it ever starts getting blocked.

## Actor input object example

```json
{
  "location": "724,14,28,173,0,28079,0,0,0",
  "transactionType": "sale",
  "propertyType": "home",
  "sortBy": "relevance",
  "sortDescending": true,
  "maxItems": 200,
  "scrapeDetails": false,
  "includeAgencies": false,
  "respectRobotsTxt": false,
  "maxCacheAgeDays": 7,
  "maxConcurrency": 5,
  "proxyConfiguration": {
    "useApifyProxy": false
  }
}
```

# Actor output Schema

## `listings` (type: `string`):

One row per listing: price, surface, the full 8-level location hierarchy with IDs, GPS point, photos tagged by room, and the listing agency with phone.

## `datasetUrl` (type: `string`):

Download the listings as JSON, CSV or Excel.

## `consoleUrl` (type: `string`):

Browse this run, including the agencies and errors datasets.

# API

You can run this Actor programmatically using our API. Below are code examples in JavaScript, Python, and CLI, as well as the OpenAPI specification and MCP server setup.

## JavaScript example

```javascript
import { ApifyClient } from 'apify-client';

// Initialize the ApifyClient with your Apify API token
// Replace the '<YOUR_API_TOKEN>' with your token
const client = new ApifyClient({
    token: '<YOUR_API_TOKEN>',
});

// Prepare Actor input
const input = {
    "location": "724,14,28,173,0,28079,0,0,0",
    "maxItems": 200
};

// Run the Actor and wait for it to finish
const run = await client.actor("oswaldocarabano/fotocasa-scraper").call(input);

// Fetch and print Actor results from the run's dataset (if any)
console.log('Results from dataset');
console.log(`💾 Check your data here: https://console.apify.com/storage/datasets/${run.defaultDatasetId}`);
const { items } = await client.dataset(run.defaultDatasetId).listItems();
items.forEach((item) => {
    console.dir(item);
});

// 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/js/docs

```

## Python example

```python
from apify_client import ApifyClient

# Initialize the ApifyClient with your Apify API token
# Replace '<YOUR_API_TOKEN>' with your token.
client = ApifyClient("<YOUR_API_TOKEN>")

# Prepare the Actor input
run_input = {
    "location": "724,14,28,173,0,28079,0,0,0",
    "maxItems": 200,
}

# Run the Actor and wait for it to finish
run = client.actor("oswaldocarabano/fotocasa-scraper").call(run_input=run_input)

# Fetch and print Actor results from the run's dataset (if there are any)
print(f"💾 Check your data here: https://console.apify.com/storage/datasets/{run.default_dataset_id}")
for item in client.dataset(run.default_dataset_id).iterate_items():
    print(item)

# 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/python/docs/quick-start

```

## CLI example

```bash
echo '{
  "location": "724,14,28,173,0,28079,0,0,0",
  "maxItems": 200
}' |
apify call oswaldocarabano/fotocasa-scraper --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,oswaldocarabano/fotocasa-scraper"
        }
    }
}
```

The hosted server signs you in with OAuth on first connect, so no API token belongs in this config. Clients without OAuth support can send an `Authorization: Bearer <APIFY_API_TOKEN>` header instead, using a token from API & Integrations in Apify Console (https://console.apify.com/settings/integrations).

## OpenAPI specification

Download the OpenAPI definition: https://api.apify.com/v2/actors/rZgbEjDyQSSp1i0Gb/builds/BBgfU0wHrhXLGfVJV/openapi.json
