# Fotocasa Price History — First Seen & Drops (`noahadler/fotocasa-price-history`) Actor

Fotocasa historico for Spain ads: first seen, precio anterior piso, price drops and delisted status. Snapshot Fotocasa Madrid/Barcelona URLs; scheduled runs build history when the portal hides the first publication date. Not escrituras. RESIDENTIAL ES.

- **URL**: https://apify.com/noahadler/fotocasa-price-history.md
- **Developed by:** [Noah Adler](https://apify.com/noahadler) (community)
- **Categories:** Real estate, Lead generation
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $6.00 / 1,000 price histories

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 Price History — First Seen, Drops & Delisted

**Fotocasa historico** for Spain property ads: track **first seen**, **precio anterior piso**, price **drops** and **delisted** listings on **Fotocasa.es**.

This is **not** another Fotocasa search dump (that Actor already exists). It is the Keepa-style watch that Forocoches, Burbuja and Rankia threads asked for: the **first publication date** is hidden when an ad is renewed; the **same listing ID** often comes back when the ad is relisted.

> **Honest limit:** Fotocasa rarely publishes a full camelcamelcamel chart. Run 1 is a **snapshot**. Schedule the Actor on the same `snapshotKey` / URLs and **you** get the history. This Actor never invents **escrituras** or notary prices (Idealista sells those at €2400+VAT / 500 records).

**Input:** property URLs (`…/d`) and/or search URLs (`…/l`, Madrid / Barcelona).\
**Output:** one Dataset row per listing check.\
**Proxy:** RESIDENTIAL **ES**.

***

### Table of contents

1. [What this Actor does](#what-this-actor-does)
2. [What is real vs snapshot](#what-is-real-vs-snapshot)
3. [What you get](#what-you-get)
4. [Features](#features)
5. [Input](#input)
6. [Output example](#output-example)
7. [Output fields](#output-fields)
8. [Quick start (API)](#quick-start-api)
9. [Use cases](#use-cases)
10. [Limitations](#limitations)
11. [FAQ](#faq)
12. [Keywords](#keywords)

***

### What this Actor does

| Step | Action |
|------|--------|
| 1 | Open Fotocasa **search** pages and/or **property** URLs you pass |
| 2 | Read listing JSON (price, id, city, portal date) — same HTTP pattern as the Fotocasa listings Actor |
| 3 | If the page exposes a price chart / original price, copy it into `priceHistory` with `source=site` |
| 4 | Diff against the previous KV snapshot (or `previousSnapshot` JSON) → `lastPriceChange`, `firstSeenAt`, `delisted` |
| 5 | Save the snapshot so the **next scheduled run** extends the history |

***

### What is real vs snapshot

| You asked for | What you actually get |
|---------------|------------------------|
| Current asking price | **Real** — `currentPrice` + `currency=EUR` from Fotocasa |
| Stable id | **Real** — `propertyId` from the ad (Burbuja: same ID when relisted) |
| Fecha de primera publicación | **Only if Fotocasa still shows it.** Portal `date` is usually the **last renew**. Otherwise `firstSeenAt` = first time **this Actor** saw the id |
| Precio anterior / drops | **Site** if a previous/original price is in the JSON; otherwise **snapshot diff** after run 2+ |
| Escrituras / precio de compraventa | **Never.** Rankia-style notary files are a different product |

`historySource` is `site`, `snapshot`, or `mixed` so you can filter.

***

### What you get

**Identity**

- `propertyId`, `url`, `title`, `city`, `status` (`active` / `delisted` / `unknown`)

**Price**

- `currentPrice`, `currency` (always EUR when a price exists)
- `lastPriceChange` (`fromPrice`, `toPrice`, `dropEur`, `dropPct`, `at`)
- `priceHistory[]` (`price`, `seenAt`, `source`)

**Time**

- `firstSeenAt`, `portalPublishedAt`, `daysOnPortal`, `scrapedAt`

***

### Features

| Capability | Detail |
|------------|--------|
| **Property URLs** | Watch specific pisos (`…/d`) |
| **Search snapshot** | Madrid / Barcelona `/l` pages up to `maxItems` |
| **KV diff** | Same Actor storage + optional `previousSnapshot` |
| **Delisted** | 404/tombstone, or id missing from the same search vs last snapshot |
| **No escrituras** | Asking prices only |
| **RES ES** | Residential Spain injected if country is empty |

***

### Input

| Field | Required | Description |
|-------|----------|-------------|
| `propertyUrls` | Cond. | Fotocasa detail URLs |
| `searchUrls` | Cond. | Fotocasa list URLs (`…/l`) |
| `maxItems` | No | Default 10 (max 80). Each row is a listing check (PPE **Price history**, not a cheap dump) |
| `snapshotKey` | No | Reuse on a schedule |
| `previousSnapshot` | No | Paste prior JSON if you keep history outside Apify |
| `persistSnapshot` | No | Default true |
| `proxyConfiguration` | No | RESIDENTIAL + ES |

Need **propertyUrls or searchUrls**.

#### Example — Madrid sale snapshot

```json
{
  "searchUrls": [
    "https://www.fotocasa.es/es/comprar/viviendas/madrid-capital/todas-las-zonas/l"
  ],
  "maxItems": 8,
  "proxyConfiguration": {
    "useApifyProxy": true,
    "apifyProxyGroups": ["RESIDENTIAL"],
    "apifyProxyCountry": "ES"
  }
}
```

#### Example — Barcelona listing URL

Paste a live `…/comprar/vivienda/barcelona-capital/…/{id}/d` URL in `propertyUrls`. The Store Try it example is the Madrid search snapshot below.

***

### Output example

```json
{
  "propertyId": "189717534",
  "url": "https://www.fotocasa.es/es/comprar/vivienda/madrid-capital/lista/id/189717534/d",
  "title": "Piso en Barrio de Salamanca",
  "currentPrice": 650000,
  "currency": "EUR",
  "city": "Madrid",
  "firstSeenAt": "2026-09-24T17:00:00Z",
  "lastPriceChange": null,
  "priceHistory": [
    { "price": 650000, "currency": "EUR", "seenAt": "2026-09-24T17:00:00Z", "source": "snapshot" }
  ],
  "status": "active",
  "daysOnPortal": 12,
  "portalPublishedAt": "2026-09-12T10:00:00Z",
  "historySource": "snapshot",
  "scrapedAt": "2026-09-24T17:00:00Z"
}
```

On a later run, if the asking price dropped to 625000, `lastPriceChange.dropEur` is `25000` and `priceHistory` has both points. That drop is **your** snapshot, unless Fotocasa also showed an original price (`source=site`).

***

### Output fields

| Group | Fields |
|-------|--------|
| Identity | `propertyId`, `url`, `title`, `city`, `status` |
| Price | `currentPrice`, `currency`, `lastPriceChange`, `priceHistory` |
| Time | `firstSeenAt`, `portalPublishedAt`, `daysOnPortal`, `scrapedAt` |
| Provenance | `historySource` |
| Errors | `error`, `errorMessage` |

***

### Quick start (API)

```python
from apify_client import ApifyClient

client = ApifyClient("<YOUR_TOKEN>")
run = client.actor("noahadler/fotocasa-price-history").call(
    run_input={
        "searchUrls": [
            "https://www.fotocasa.es/es/comprar/viviendas/madrid-capital/todas-las-zonas/l"
        ],
        "maxItems": 8,
        "proxyConfiguration": {
            "useApifyProxy": True,
            "apifyProxyGroups": ["RESIDENTIAL"],
            "apifyProxyCountry": "ES",
        },
    }
)
for item in client.dataset(run["defaultDatasetId"]).iterate_items():
    print(item["propertyId"], item["currentPrice"], item["firstSeenAt"], item["historySource"])
```

Schedule daily/weekly with the **same** `snapshotKey` (or the same URLs) so `firstSeenAt` stays stable and drops appear.

***

### Use cases

- Buyer due diligence: did this piso sit and then drop?
- Investor comps: asking-price path without paying Idealista escrituras
- Agency watchlists: same Fotocasa id after a “new” ad (Burbuja pattern)
- Alerts: `status=delisted` on a search you already snapshot
- Cross-check vs the Fotocasa listings Actor (inventory vs history)

***

### Limitations

- **No notary / escritura prices.** Asking prices on Fotocasa only.
- Portal “publication” date **resets on renew** — that is the whole Forocoches complaint. We store `portalPublishedAt` as Fotocasa sent it and `firstSeenAt` from the first snapshot when the site omits a true first-publish field.
- Run 1 `priceHistory` is usually a **single snapshot point**. History depth = how long you schedule.
- Search snapshots only mark `delisted` for ids that were in **this** Actor’s previous snapshot of the same watch, not the whole Spanish inventory.
- Respect Fotocasa Terms of Service. RESIDENTIAL ES if you see 403/empty JSON. WAF after that → drop, not infinite rewrites.

***

### FAQ

**Is this the Fotocasa listings scraper?**\
No. Listings = current ads. This Actor = first seen, precio anterior, drops, delisted.

**Why is `lastPriceChange` null?**\
First run, or the price did not move vs the last snapshot.

**Can I get fecha publicacion Idealista?**\
This build is **Fotocasa**. Idealista first-publish is the same product idea on another portal — not bundled here.

**Do you scrape Rankia escrituras?**\
No.

**Blocked?**\
RESIDENTIAL + `apifyProxyCountry=ES`.

***

### Keywords

fotocasa historico, fotocasa historico precios, idealista historico precios, fecha publicacion idealista, precio anterior piso, fotocasa price history, first seen fotocasa, fotocasa delisted

Related listings Actor: `fotocasa-spain-property-scraper`.

# Actor input Schema

## `propertyUrls` (type: `array`):

Detail ads (…/d). Same listing ID after a relist is the key Forocoches/Burbuja asked for.

## `searchUrls` (type: `array`):

List pages to snapshot (…/l). Example: Madrid or Barcelona comprar viviendas.

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

Cap per run (1–80). Each row is a listing check / history event (dearer than a raw search dump).

## `snapshotKey` (type: `string`):

KV key for this watchlist. Leave empty to hash the URLs. Reuse the same key on a schedule so firstSeenAt and drops persist.

## `previousSnapshot` (type: `object`):

Paste a prior KV snapshot ({ items: { id: { currentPrice, firstSeenAt, … } } }) if you store history outside this Actor. Otherwise the Actor KV is used.

## `persistSnapshot` (type: `boolean`):

On by default. Next scheduled run diffs prices and marks delisted IDs missing from the same search.

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

Fotocasa often needs RESIDENTIAL Spain. Country ES is injected if you leave it empty.

## Actor input object example

```json
{
  "propertyUrls": [],
  "searchUrls": [
    "https://www.fotocasa.es/es/comprar/viviendas/madrid-capital/todas-las-zonas/l"
  ],
  "maxItems": 10,
  "persistSnapshot": true,
  "proxyConfiguration": {
    "useApifyProxy": true,
    "apifyProxyGroups": [
      "RESIDENTIAL"
    ],
    "apifyProxyCountry": "ES"
  }
}
```

# Actor output Schema

## `history` (type: `string`):

Fotocasa listing checks with firstSeenAt and priceHistory.

# 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 = {
    "propertyUrls": [],
    "searchUrls": [
        "https://www.fotocasa.es/es/comprar/viviendas/madrid-capital/todas-las-zonas/l"
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("noahadler/fotocasa-price-history").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 = {
    "propertyUrls": [],
    "searchUrls": ["https://www.fotocasa.es/es/comprar/viviendas/madrid-capital/todas-las-zonas/l"],
}

# Run the Actor and wait for it to finish
run = client.actor("noahadler/fotocasa-price-history").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 '{
  "propertyUrls": [],
  "searchUrls": [
    "https://www.fotocasa.es/es/comprar/viviendas/madrid-capital/todas-las-zonas/l"
  ]
}' |
apify call noahadler/fotocasa-price-history --silent --output-dataset

```

## MCP server setup

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

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/j9puS2pFbQnDnR63n/builds/ed3HKfFaitgF8ZaND/openapi.json
