# Fotocasa Scraper - Spain Property Listings (`dami_studio/fotocasa-scraper`) Actor

Scrape fotocasa.es homes for sale and rent: price, price per m2, surface, rooms, bathrooms, floor, neighbourhood, coordinates, features, agency or private advertiser, photos and publication date.

- **URL**: https://apify.com/dami\_studio/fotocasa-scraper.md
- **Developed by:** [Dami's Studio](https://apify.com/dami_studio) (community)
- **Categories:** Real estate, Business
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $0.65 / 1,000 listing returneds

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

Pulls property listings off **fotocasa.es**, the Spanish portal for homes to buy and rent, and hands you back
one clean row per listing. Price, price per m², surface in square metres, rooms, bathrooms, floor,
the address down to the neighbourhood, real coordinates, the feature list, whether the advertiser is
an estate agency or a private seller, every photo URL, the description, the publication date and the
link to the listing itself.

No Fotocasa account. No API key. No cookies to paste in.

### Start with the part that usually gets buried

Fotocasa's **default relevance ordering shuffles between requests**. Ask for the same search page
twice, ten seconds apart, and the first listing is usually a different one. That is their behaviour,
not a bug in this Actor, and it means deep paging under relevance order will hand you the same
property twice. So this Actor sorts by **newest first** by default and de-duplicates every run by
listing id. You can switch back to Fotocasa's relevance order if you want it — the option is there —
but now you know why it is not the default.

A few other things worth knowing before you spend anything:

- **Floor is only on about half the listings.** Measured on a live Valencia rental search: 15 of 30
  rows carried a floor. Fotocasa does not make advertisers fill it in. Same story for
  `neighbourhood` (13 of 30) and `condition` (16 of 30). Price, surface, rooms, coordinates and the
  address hit 30 of 30.
- **This reads search results, not the individual listing pages.** Everything above comes from the
  search response. Things that only exist on a listing's own page — the energy certificate rating,
  the agency's internal reference number, the full amenity checklist — are not here.
- **Typing a place name gets you Fotocasa's best match, which is not always the one you pictured.**
  Type "Valencia" and you get the province, because that is where most of the listings are. Set
  **Area size** to *Town or city* and you get the city. And note that the `-capital` you see in
  their URLs is part of the address, not part of the name: their search box has never heard of
  "Madrid Capital", only "Madrid". If you want to be certain, build the search on fotocasa.es and
  paste the URL instead. That path does no guessing at all.

### Two ways to tell it what you want

**Paste a search URL.** Go to fotocasa.es, set whatever filters you like, copy the address bar:

```json
{
  "startUrls": [
    "https://www.fotocasa.es/es/comprar/viviendas/barcelona-capital/todas-las-zonas/l",
    "https://www.fotocasa.es/es/alquiler/viviendas/madrid-capital/todas-las-zonas/l?minPrice=900&maxPrice=1600"
  ],
  "maxItems": 500
}
```

Whatever the site put in that URL comes along with it. This is the path to use when you already know
exactly what you want, or when the filter you need is not in the list below.

**Or describe it.** The Actor looks the place up against Fotocasa's own place list and builds the
search for you:

```json
{
  "location": "Barrio de Salamanca",
  "transactionType": "sale",
  "propertyType": "homes",
  "minPrice": 400000,
  "maxPrice": 900000,
  "minRooms": 2,
  "minSurface": 80,
  "advertiser": "private",
  "sortType": "publicationDate",
  "maxItems": 250
}
```

You can do both in the same run. The URLs get read first, then the built search, and the whole run
is de-duplicated together.

#### Input fields

| Field | What it does |
|---|---|
| `location` | A town, district or neighbourhood as Fotocasa's search box knows it: `Madrid`, `Valencia`, `Barrio de Salamanca`, `Marbella` |
| `locationLevel` | Which size of area the name should resolve to: `auto`, `MUNICIPALITY`, `PROVINCE`, `REGION`, `DISTRICT`, `NEIGHBORHOOD` |
| `transactionType` | `sale` or `rent` |
| `propertyType` | `homes`, `garages`, `offices`, `premises`, `land`, `rural-houses`, `buildings`, `storage-rooms` |
| `startUrls` | Fotocasa search URLs, one per line |
| `maxItems` | Hard cap on listings delivered. Also your spending cap |
| `sortType` | `publicationDate`, `price`, `pricem2`, `surface`, `scoring` |
| `advertiser` | `any`, `agency`, `private` |
| `minPrice` / `maxPrice` | Euros. Monthly rent for rentals |
| `minSurface` / `maxSurface` | Square metres |
| `minRooms` / `maxRooms` | |
| `minBathrooms` / `maxBathrooms` | |
| `proxyConfiguration` | Leave it alone unless you need a specific network |

The price, surface, room and bathroom filters are applied by Fotocasa itself, so they narrow the
result set before anything is charged. The **advertiser** filter is the exception: Fotocasa has no
server-side switch for it, so the Actor reads the page and drops the rows that do not match. **You
are not charged for the rows it drops.** The run tells you how many there were.

### What a row looks like

This is a real row, lightly trimmed for width, from a Valencia rental search:

```json
{
  "id": "190879807",
  "url": "https://www.fotocasa.es/es/alquiler/vivienda/valencia-capital/aire-acondicionado-calefaccion-amueblado/190879807/d",
  "transactionType": "rent",
  "propertyType": "home",
  "propertySubtype": "intermediate floor",
  "price": 1400,
  "priceText": "1.400 €",
  "pricePeriod": "month",
  "pricePerM2": 17.5,
  "surfaceM2": 80,
  "rooms": 3,
  "bathrooms": 2,
  "floor": "4",
  "floorCode": 9,
  "condition": "good",
  "isNewConstruction": false,
  "neighbourhood": "Els Orriols",
  "district": "Rascanya",
  "municipality": "Valencia",
  "province": "Valencia",
  "postcode": "46019",
  "latitude": 39.49541071132765,
  "longitude": -0.36959676906258937,
  "advertiserType": "agency",
  "advertiserName": "HOUSINGANYWHERE",
  "advertiserUrl": "https://www.fotocasa.es/es/pro/housinganywhere/",
  "phone": null,
  "features": ["furnished", "washing_machine", "tv", "pets_allowed", "laundry"],
  "images": ["https://static.fotocasa.es/images/ads/7f4cc921-08a3-4102-8657-88f1b434907d?rule=original"],
  "imageCount": 6,
  "hasFloorPlan": false,
  "hasVirtualTour": false,
  "description": "Esta apartmento en Valencia, ofrece un espacio habitable...",
  "publishedAt": "2026-09-20T01:01:51.013Z",
  "page": 1,
  "position": 1,
  "scrapedAt": "2026-09-20T00:06:55.372Z"
}
```

A few notes on individual fields, because guessing at them wastes your time:

- **`price` is a number, `priceText` is Fotocasa's own formatting.** Sort and filter on the number.
- **`pricePeriod`** is `null` on a sale and `month` on almost every rental. Other values exist
  (`week`, `day`, `season`, `year`) and show up on holiday lets.
- **`pricePerM2` is worked out here**, not supplied by Fotocasa — it is `price ÷ surfaceM2`, rounded
  to two decimals. On a rental that makes it rent per square metre *per month*, which is the number
  Spanish rental analysts actually use, but it is worth saying out loud.
- **`floor`** is a readable string: `"basement"`, `"ground floor"`, `"1"` through `"15"`, or
  `"above floor 15"`. `floorCode` is Fotocasa's own integer, kept alongside it. Three of their codes
  never appeared in the sample used to work the mapping out, so a listing carrying one of those gets
  `floor: null` and keeps its `floorCode`. Better a blank than a wrong floor.
- **`features`** uses Fotocasa's own key names — `terrace`, `elevator`, `pets_allowed`,
  `air_conditioner`, `community_pool` and so on. They are not translated, because translating them
  would mean inventing names that do not match anything on their site.
- **`advertiserType`** is `agency` or `private`. This is the one people ask for most, and it is
  genuinely there on every row.
- **`phone`** is the number Fotocasa publishes on the search card. Quite often there isn't one.

### Pricing and what actually bills

**You pay per listing delivered, and for starting the run.** That is the whole of it.

These are **free** and are labelled so you can filter them out:

- the sample row an empty run returns, marked `_sample: true`
- every diagnostic row, marked `_diagnostic: true`: a search that returned nothing, a URL that was
  not a Fotocasa address, a run that ran out of time
- rows dropped by the `advertiser` filter
- duplicates removed during paging

So a run that finds nothing costs you the start fee and nothing else, and a run that stops early
still only bills the listings it actually handed you. The current per-listing figure is on the
**Pricing** tab of this page.

`maxItems` is a hard ceiling, checked before every row. Set it and you cannot be surprised.

### What this does not do

- **It does not open individual listing pages.** No energy certificate, no agency reference number,
  no full amenity checklist, no floor plan images. Those live one click deeper and would cost you
  roughly one extra page fetch per property.
- **It does not do map-polygon or draw-your-own-area searches.** Fotocasa supports those on the
  site; this Actor works from place names and search URLs.
- **It does not reach anything behind a Fotocasa login.** No saved searches, no contact history, none of
  professional tools.
- **It does not give you a listing's history.** You get the price as it stands when you run it.
  `previousPrice` is filled only when Fotocasa itself is advertising a price drop, which is rare.
- **It does not translate anything.** Descriptions come back in the language the advertiser wrote
  them in, which is Spanish, and occasionally Catalan.
- **It cannot invent a field the advertiser left blank.** See the fill rates at the top.

### FAQ

**Can I scrape all of Madrid?**
Yes. A Madrid sale search reports roughly 11,700 live listings and the pager keeps going well past
where you would want to stop. Set `maxItems` to what you actually need. You are charged per
listing, so "everything" is a bill, not a setting.

**How many listings per page does it read?**
Thirty or thirty-one, which is what Fotocasa returns. Over four consecutive pages of a filtered
Valencia rental search it delivered 120 unique listings with zero duplicates.

**Does it work for rentals as well as sales?**
Both, and holiday lets and room shares too if you paste those URLs. `transactionType` covers the two
common ones; the rest come through a pasted URL.

**Can I filter to private sellers only?**
Yes, `advertiser: "private"`. Be aware that on most Spanish searches the great majority of listings
are agency listings, so a private-only run over a small area can come back nearly empty. That is the
market, not the Actor.

**Why does my "Valencia" search return the whole province?**
Because Fotocasa's place list has a province called Valencia with about 18,500 live ads and a city
called Valencia with about 3,700, and on Automatic the Actor takes whichever has more. Set **Area
size** to *Town or city* and you get the city. The run log always names the match it chose and the
other candidates it saw, so you can check.

**I typed "Madrid Capital" and got nothing.**
That is Fotocasa's URL slug, not a place name — their own search box does not recognise it either,
and returns street names. Type `Madrid`. The Actor strips a trailing "Capital" for you, but the
plain name is what to reach for.

**Do I need a proxy?**
No. The Actor goes out from the container's own address and only reaches for rotating datacenter
addresses if Fotocasa refuses it. If you have your own proxy servers you can supply them and they
will be used exactly as given.

**What happens if Fotocasa changes the page?**
The Actor reads the JSON payload Fotocasa's own front end hydrates from, not the HTML around it, so
a visual redesign does not touch it. If that payload ever moves, the run writes a free
`NO_DATA_BLOCK` diagnostic row rather than quietly returning nothing.

**Can I run it on a schedule?**
Yes, it is an ordinary Actor. Sort by `publicationDate`, keep `maxItems` small, and de-duplicate
against your own store on `id` to watch new listings appear in an area.

# Actor input Schema

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

Type a town, district or neighbourhood the way it appears in Fotocasa's own search box: "Madrid", "Valencia", "Barrio de Salamanca", "Marbella". The "-capital" in their URLs is part of the address, not the name. The Actor looks it up and takes the match with the most live listings, so a bare province name gets you the province; use Area size below to pin it to a town. Leave empty if you are pasting search URLs.

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

Which market to read. Only used with the Place field above; a pasted URL already says which one it is.

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

Only used with the Place field above.

## `locationLevel` (type: `string`):

Which size of area the place name should resolve to. Automatic takes whichever match has the most live listings, which for a province name means the whole province. Only used with the Place field above.

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

Hard cap on how many listings the run delivers, across every search. You are charged per listing delivered, so this is also your spending cap.

## `startUrls` (type: `array`):

Paste search pages straight from fotocasa.es, for example https://www.fotocasa.es/es/comprar/viviendas/barcelona-capital/todas-las-zonas/l — whatever filters you set on the site come with the URL. The Actor pages through each one until it runs out of results or hits your maximum. One URL per line.

## `sortType` (type: `string`):

Newest first is the default because Fotocasa's own relevance order shuffles between requests, which makes deep paging return the same listing twice. Only used with the Place field above.

## `advertiser` (type: `string`):

Keep only listings from estate agencies, or only from private sellers. Fotocasa has no server-side filter for this, so the Actor reads the page and drops the rest — you are not charged for anything it drops.

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

In euros. For rentals this is the monthly rent.

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

In euros. For rentals this is the monthly rent.

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

Built surface in square metres, as Fotocasa reports it. Applied by Fotocasa before anything is charged.

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

Built surface in square metres. Leave empty for no upper limit.

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

Bedrooms, not total rooms. Fotocasa caps this filter at 10.

## `maxRooms` (type: `integer`):

Bedrooms, not total rooms. Leave empty for no upper limit.

## `minBathrooms` (type: `integer`):

Bathrooms. Fotocasa caps this filter at 10.

## `maxBathrooms` (type: `integer`):

Bathrooms. Leave empty for no upper limit.

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

Leave this alone unless you need the run to go out through a particular network. The Actor reaches Fotocasa directly and only falls back to rotating datacenter addresses if it is refused. Metered proxy groups are not used and a request for one is swapped for datacenter.

## Actor input object example

```json
{
  "location": "Valencia",
  "transactionType": "sale",
  "propertyType": "homes",
  "locationLevel": "auto",
  "maxItems": 100,
  "startUrls": [],
  "sortType": "publicationDate",
  "advertiser": "any",
  "proxyConfiguration": {
    "useApifyProxy": false
  }
}
```

# Actor output Schema

## `results` (type: `string`):

One row per listing: price and price per m2, surface, rooms, bathrooms, floor, the address down to neighbourhood and postcode, coordinates, condition, feature list, whether the advertiser is an agency or a private seller, photo URLs, the description, the publication date and the listing link. Empty input writes one free sample row instead, and a search that fails or returns nothing writes a free diagnostic row saying which.

# 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": "Valencia",
    "transactionType": "sale",
    "propertyType": "homes",
    "locationLevel": "auto",
    "maxItems": 100,
    "startUrls": [],
    "sortType": "publicationDate",
    "advertiser": "any",
    "proxyConfiguration": {
        "useApifyProxy": false
    }
};

// Run the Actor and wait for it to finish
const run = await client.actor("dami_studio/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": "Valencia",
    "transactionType": "sale",
    "propertyType": "homes",
    "locationLevel": "auto",
    "maxItems": 100,
    "startUrls": [],
    "sortType": "publicationDate",
    "advertiser": "any",
    "proxyConfiguration": { "useApifyProxy": False },
}

# Run the Actor and wait for it to finish
run = client.actor("dami_studio/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": "Valencia",
  "transactionType": "sale",
  "propertyType": "homes",
  "locationLevel": "auto",
  "maxItems": 100,
  "startUrls": [],
  "sortType": "publicationDate",
  "advertiser": "any",
  "proxyConfiguration": {
    "useApifyProxy": false
  }
}' |
apify call dami_studio/fotocasa-scraper --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,dami_studio/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/LDtvHIaDQ7ugoG4py/builds/CtQ4oNuheallzadeh/openapi.json
