# Funda Scraper 🏠 Sale & Rental Listings | Only $0.75/1,000 (`pebbleworklabs/funda-listings-scraper`) Actor

Extract Funda sale and rental listings by URL or city. Get structured property details, optional raw records and public agency contacts. Track price and status changes across runs. Start with a 5-listing sample.

- **URL**: https://apify.com/pebbleworklabs/funda-listings-scraper.md
- **Developed by:** [Pebblework Labs Inc](https://apify.com/pebbleworklabs) (community)
- **Features:** Limited permissions
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $0.75 / 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

## Funda Scraper 🏠 | $0.75 per 1,000 Listings

![Dutch canal houses illustrating Funda property data, agency contacts and change tracking](https://api.apify.com/v2/key-value-stores/T2RYWpeIXuhGjM7Yj/records/funda-listing-banner-v1.png?signature=SlT9M9342ABuGyZ8SWJx)

**Turn a Funda search into a property dataset. Come back to see what changed.**

Paste a Funda search or property URL, or enter a Dutch city and price range. Get one structured row per unique listing, with optional property details, the full raw source record, and public agency contacts. Save a monitoring key to compare price, status and property changes across runs.

Built for housing research, property analysis and recurring market monitoring. No Funda login required.

### Try it with 5 listings

Enter these settings in the Input tab, then select **Start**. Once the run finishes, open **Dataset** to inspect the property rows and download your results. This example requests up to five Amsterdam homes with property details.

```json
{
  "location": "Amsterdam",
  "transaction": "sale",
  "maxItems": 5,
  "includeDetails": true,
  "residentialOnly": true,
  "maxRequests": 8,
  "maxRunSeconds": 180
}
```

Prefer a URL? Replace `location` and `transaction` with:

```json
{
  "searchUrls": ["https://www.funda.nl/zoeken/koop?selected_area=amsterdam&price=300000-600000"],
  "maxItems": 5,
  "includeDetails": true,
  "residentialOnly": true,
  "maxRequests": 8,
  "maxRunSeconds": 180
}
```

Use either URL inputs or simple search settings in one run. Work limits can stop a run before the requested number is reached; its coverage summary explains why.

### Bulk extraction

Requests above 50 properties automatically split dense searches into smaller price ranges. Results appear together in one dataset, deduplicated by Funda property ID. Each page is checkpointed; unfinished partitions retry with a bounded delay while completed work is retained. The Actor's status and **Extraction progress** output show delivered rows and pending or failed partitions. Confirmed rows can be downloaded while collection continues.

This bounded example targets 1,000 basic listing snapshots:

```json
{
  "location": "Amsterdam",
  "maxItems": 1000,
  "autoPartition": true,
  "includeDetails": false,
  "maxPages": 160,
  "maxRequests": 250,
  "maxRunSeconds": 1800,
  "maxTrafficMb": 150
}
```

All partitions share these whole-run limits and one delivery/billing journal. Reaching 1,000 rows is limited coverage of the search, not a claim that every matching property was collected. Source failures or exhausted budgets can produce fewer rows. Original scope totals are reconciled before reporting complete coverage; unpriced listings are checked through the original search when needed. Explicit sort orders and starting pages retain ordinary pagination. Set `autoPartition: false` to use ordinary pagination for any search.

To resume an interrupted run, use Apify's **Resurrect** action with its original build and unchanged input. It reconciles existing output before continuing; work and fee budgets do not reset. Starting a new run creates a separate job and can charge for the same results again once customer billing is enabled.

### What can you do with it?

| Your goal | What to use |
| --- | --- |
| Compare homes in a city or price band | Structured prices, addresses, living areas, bedrooms and energy labels |
| Build a richer property dataset | Optional descriptions, coordinates, construction year, features, VvE and cadastral sections |
| Watch a search over time | Persistent first/last observed times, changed fields and previous values |
| Connect properties with their agencies | Optional public agency names, phone numbers, emails and websites, where available |
| Keep source fields for your own analysis | Optional complete raw property record alongside normalized fields |
| Feed a report or data pipeline | Apify dataset access, JSON exports and CSV options |

### Why try this Funda scraper?

- **Start with a city, not a query URL.** Choose sale or rent, then narrow by price, living area, room count or property type.
- **Get useful columns immediately.** Numeric EUR prices, normalized statuses and energy labels, stable listing identities, and explicit missing values make the output easier to work with.
- **Keep the raw data when you need it.** Enable `includeRawRecord` to retain the full detail response alongside the structured record. This also enables detail collection.
- **Follow changes across runs.** Reuse a `monitoringKey` to identify newly observed, updated, unchanged and returned listings. Use `changesOnly` to suppress verified unchanged rows.
- **Add public agency contacts.** Enable `includeAgencyContacts`; each unique agency is looked up once per run and its available contact fields are attached to relevant properties.
- **Know what was collected.** The run summary distinguishes complete, empty, limited and partial results, with per-search coverage and stop reasons.

### Choose how much data to collect

| Mode | Settings | Output |
| --- | --- | --- |
| Listing snapshot | Default | Search-result fields such as price, address, area, bedrooms and energy label, where available |
| Property details | `includeDetails: true` | Adds descriptions, coordinates, construction year, full feature sections and media links, where available |
| Raw source record | `includeRawRecord: true` | Adds the complete property detail object and the original search record for search-derived rows |
| Agency contacts | `includeAgencyContacts: true` | Adds public agency name, phone, email and website, where available |
| Repeat-run monitoring | Set `monitoringKey` | Adds observed history and field changes for one fixed search scope |

A direct Funda property URL fetches that property's details automatically. Photos, floor plans, videos and brochures are returned as links; media files are not downloaded.

### Example output

Trimmed from a real detailed result collected on November 5, 2026. Prices, availability and source data can change.

```json
{
  "schemaVersion": "2.0",
  "globalId": "8216708",
  "transaction": "sale",
  "address": "Columbusplein 12-1",
  "postalCode": "1057TZ",
  "city": "Amsterdam",
  "province": "Noord-Holland",
  "neighbourhood": "Columbusplein e.o.",
  "price": 450000,
  "currency": "EUR",
  "bedrooms": 2,
  "energyLabel": "C",
  "yearBuilt": 1929,
  "status": "available",
  "detailStatus": "complete"
}
```

Detailed rows can also contain `description`, `latitude`, `longitude`, `featureSections`, `cadastral`, `vve`, `floorPlans`, `photos360`, `videos` and `brochureUrl`. Optional `rawRecord`, `agencyContacts` and `change` fields hold raw source data, agency enrichment and monitoring history. Missing source values remain `null`; a missing value is not converted to zero.

### Track new observations and price changes

Run this input, then repeat it later with the same monitoring key:

```json
{
  "location": "Amsterdam",
  "transaction": "sale",
  "minPrice": 300000,
  "maxPrice": 600000,
  "includeDetails": true,
  "residentialOnly": true,
  "monitoringKey": "amsterdam-sale-300k-600k",
  "changesOnly": true,
  "maxItems": 20,
  "maxRequests": 25,
  "maxRunSeconds": 180
}
```

The first run establishes observations; later runs compare the homes they actually see. Updated rows include `changedFields` and previous values. Reusing the same key with a different search scope is rejected; use a new key when changing that scope. Run one collection at a time for each key.

An unchanged search can produce zero dataset rows with `changesOnly: true`. Suppressed observations still count toward the item limit and consume source requests. A failed detail lookup stays explicitly unverified rather than being labelled a change.

The separate `REMOVALS` output is only populated after complete, exact search coverage. Absence means a listing left that search scope; it does not prove a sale or delisting. A capped snapshot cannot establish complete market changes. First-seen times mean first observed by this monitoring key, not the property's original publication date.

### Main inputs

| Input | Purpose | Default |
| --- | --- | --- |
| `location` | Dutch city or Funda area slug; use instead of URLs | Required in simple search mode |
| `transaction` | `sale` or `rent` | `sale` in simple search mode |
| `searchUrls` | Funda search URLs or individual property URLs | Use instead of simple search settings |
| `minPrice`, `maxPrice` | Price range in EUR | Unset |
| `minLivingArea`, `maxLivingArea` | Living-area range in m² | Unset |
| `minRooms`, `minBedrooms` | Minimum room/bedroom count | Unset |
| `propertyTypes` | `house`, `apartment`, `parking` or `building_plot` | Unset |
| `includeDetails` | Collect detailed property information | `false` |
| `includeRawRecord` | Preserve complete raw property data; enables details | `false` |
| `includeAgencyContacts` | Fetch available public agency contacts | `false` |
| `monitoringKey` | Persistent history for a fixed search scope | Unset |
| `changesOnly` | Suppress verified unchanged rows; requires a monitoring key | `false` |
| `residentialOnly` | Keep confirmed houses/apartments | `false` |
| `maxItems` | Global unique-observation limit across all inputs | `20`; maximum `1000` |

Use `maxRequests`, `maxPages`, `maxRunSeconds` and `maxTrafficMb` to bound source work. An item limit is a ceiling, not a promise that the source will return that many homes.

### Export and automate

Use the Apify dataset API to connect results to your own workflow. To repeat a search, save it as an Apify task and configure a schedule; use the same monitoring key when you want a continuing history. Running the Actor once does not create a schedule.

Download **JSON/JSONL for complete nested and raw records**. Native CSV is convenient for selected fields, but Apify's [2,000-column limit](https://docs.apify.com/storage/dataset) can omit fields when large nested records are flattened. Use JSON/JSONL when you need all nested fields; select a smaller set of fields for CSV.

### Pricing and beta availability

Currently a **private beta** with pay-per-event pricing configured. The Actor has not been published on the Store. Basic extraction starts at **$0.75 per 1,000 delivered listings**, plus the run-start fee. Start with the five-listing example to assess your results.

| Event | Price (USD) |
| --- | ---: |
| Basic property listings | $0.75 per 1,000 delivered listings |
| Complete property details | Additional $0.75 per 1,000 complete details ($1.50 total per 1,000 detailed listings) |
| Optional agency contacts | $3.50 per 1,000 successfully enriched unique agencies per run |
| Run start | $0.005 per run at up to 1 GB RAM, including runs with no new or changed output; $0.01 at 2 GB |

These event prices include standard run infrastructure costs. Overlapping inputs do not charge twice for the same property in one run. Failed detail and agency lookups do not incur their optional enrichment fees. Repeated scheduled runs can charge for results again. With `changesOnly`, suppressed unchanged rows have no listing, detail or contact fee; the run-start fee still applies. Apify's Pricing tab is the source of truth for active prices.

`maxSpendUsd` limits listing, detail and agency fees. The run-start fee is additional, including when this input is zero. Use Apify’s maximum cost per run to cap all event fees including the start fee. At up to 1 GB RAM, the minimum selectable total cap is $0.005; a detailed listing needs at least $0.0065 including start. At 2 GB, allow $0.01 for start alone. Use the source-work limits above to bound a trial. Owner-run infrastructure benchmarks are not customer prices.

### Frequently asked questions

**Does it work for rentals?** Yes. Set `transaction` to `rent`, or use a Funda `/zoeken/huur` URL. Use sale or rental asking prices for analysis; these are not verified completed transaction prices.

**Does it support fundainbusiness.nl?** No. This Actor accepts supported funda.nl search and property URLs; the separate commercial portal is not supported.

**Can I fetch a single property?** Yes. Put its current Funda `/detail/koop/...` or `/detail/huur/...` URL in `searchUrls` and set `maxItems` to `1`.

**Can I collect all of the Netherlands?** Collection follows your supplied search scope, up to 1,000 unique observations per run and your other work limits. Nationwide completeness and unlimited extraction are not promised. Automatic nationwide partitioning is not implemented.

**Are contact details guaranteed?** No. Agency enrichment returns public details available from the source. It does not discover private owner contacts or infer missing emails.

**Are descriptions translated?** No. Source descriptions and feature labels are generally Dutch. Neighbourhood names are available where supplied; demographic enrichment is not included.

**What happens if collection stops early?** Inspect the `SUMMARY` output. It reports coverage, counts, errors and stop reasons. Bounded runs can finish with limited coverage; source failures and excessive row issues are reported separately. Previously confirmed rows are retained.

**How was it tested?** Automated tests cover extraction, input validation, monitoring, spending limits, interrupted delivery and billing reconciliation. Private cloud checks inspect actual exported rows, not only the container status. An earlier reliability check exhausted its 60-second source budget without a result; the current release adds request diagnostics and reports unsuccessful zero-result collection as failed. A successful small sample does not establish seven-day reliability or nationwide completeness.

### Help and technical details

Report a problem through the Actor's support/Issues channel when available. Include the run ID, input and the fields you expected, without credentials. This helps reproduce missing results, source changes and filter problems.

Use the Input, Output and API tabs for field definitions and integration examples. For complete nested data, export JSON or JSONL.

This Actor is independent of Funda and is not affiliated with or endorsed by Funda.

# Actor input Schema

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

Optional Funda search or detail URLs. Use these OR simple search settings. Filters are verified against source criteria.

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

Maximum unique listings observed across all URLs after residential filtering (1,000 max). Unchanged rows suppressed by changesOnly still consume this work limit.

## `includeDetails` (type: `boolean`):

Include listing details.

## `autoPartition` (type: `boolean`):

For requests above 50 properties, split dense searches into smaller price ranges and combine unique results in one dataset. Saved page cursors and bounded batch retries share the whole run's work and fee limits. Explicit sort orders and starting pages are preserved through ordinary pagination.

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

Property detail proxy. Default residential Netherlands; platform proxy usage is billed to the account.

## `maxPages` (type: `integer`):

Whole-run search-page limit, including partition discovery and retried pages. Bulk jobs need a larger limit than small samples. Stops with limited coverage when reached.

## `maxRequests` (type: `integer`):

Maximum source requests including retries. Stops with limited coverage when reached.

## `maxRunSeconds` (type: `integer`):

Source time budget in seconds. Stops with limited coverage when reached. A restarted run continues with its remaining time.

## `maxTrafficMb` (type: `integer`):

Maximum decoded response traffic (MB). Stops with limited coverage when reached.

## `searchProxyConfiguration` (type: `object`):

Default Apify Unblocker. Uses its interception certificate only when UNBLOCKER is explicitly selected. Search pages currently refuse plain HTTP and browser sessions.

## `residentialOnly` (type: `boolean`):

When enabled, emit confirmed apartments and houses only. Parking, building plots, other and unclassified types are excluded before optional search-result detail requests. Exclusions are counted in SUMMARY.

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

Use a city such as Amsterdam or Den Haag, or a Funda area slug. Leave blank when supplying searchUrls.

## `transaction` (type: `string`):

Used with location. Omit with searchUrls. Sale when omitted in simple search mode.

## `propertyTypes` (type: `array`):

Optional simple-search property filter. Leave blank with searchUrls.

## `includeRawRecord` (type: `boolean`):

Also retain the complete source detail record and original search record. Enables includeDetails. Larger exports use more storage.

## `includeAgencyContacts` (type: `boolean`):

Add agency name, public phone, email and website. One lookup and one preview billing event per successfully enriched unique agency per run.

## `monitoringKey` (type: `string`):

Optional stable name for this exact search scope. Tracks new, updated, unchanged and returned listings across runs. Use a different key for a different scope.

## `changesOnly` (type: `boolean`):

Requires monitoringKey. Suppress unchanged rows while retaining observations. Source reads still incur owner platform/proxy usage. Removals are separate and only inferred after complete coverage.

## `maxSpendUsd` (type: `number`):

Cap listing/detail/agency event fees (simulated while private). Zero starts no source requests. When activated, the fixed run-start fee is additional; use Apify maximum cost per run to cap all event fees including the start. Does not cap private platform/proxy usage.

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

Optional simple-search setting. Leave blank with searchUrls.

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

Optional simple-search setting. Leave blank with searchUrls.

## `minLivingArea` (type: `integer`):

Optional simple-search setting. Leave blank with searchUrls.

## `maxLivingArea` (type: `integer`):

Optional simple-search setting. Leave blank with searchUrls.

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

Optional simple-search setting. Leave blank with searchUrls.

## `minBedrooms` (type: `integer`):

Optional simple-search setting. Leave blank with searchUrls.

## Actor input object example

```json
{
  "maxItems": 5,
  "includeDetails": true,
  "autoPartition": true,
  "proxyConfiguration": {
    "useApifyProxy": true,
    "apifyProxyGroups": [
      "RESIDENTIAL"
    ],
    "apifyProxyCountry": "NL"
  },
  "maxPages": 20,
  "maxRequests": 12,
  "maxRunSeconds": 180,
  "maxTrafficMb": 50,
  "searchProxyConfiguration": {
    "useApifyProxy": true,
    "apifyProxyGroups": [
      "UNBLOCKER"
    ]
  },
  "residentialOnly": true,
  "location": "Amsterdam",
  "includeRawRecord": false,
  "includeAgencyContacts": false,
  "changesOnly": false
}
```

# Actor output Schema

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

One row per unique Funda global listing ID. Check SUMMARY before treating coverage as complete.

## `summary` (type: `string`):

Coverage outcome, source totals, limits and errors.

## `progress` (type: `string`):

Delivered rows, pending/completed/failed partitions and final outcome. Download confirmed rows from Property listings while the run continues.

## `removals` (type: `string`):

Monitoring only. Absence from the exact search scope, not proof of delisting or sale. Never inferred from limited or failed coverage.

# 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 = {
    "maxItems": 5,
    "includeDetails": true,
    "maxRequests": 12,
    "maxRunSeconds": 180,
    "residentialOnly": true,
    "location": "Amsterdam"
};

// Run the Actor and wait for it to finish
const run = await client.actor("pebbleworklabs/funda-listings-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 = {
    "maxItems": 5,
    "includeDetails": True,
    "maxRequests": 12,
    "maxRunSeconds": 180,
    "residentialOnly": True,
    "location": "Amsterdam",
}

# Run the Actor and wait for it to finish
run = client.actor("pebbleworklabs/funda-listings-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 '{
  "maxItems": 5,
  "includeDetails": true,
  "maxRequests": 12,
  "maxRunSeconds": 180,
  "residentialOnly": true,
  "location": "Amsterdam"
}' |
apify call pebbleworklabs/funda-listings-scraper --silent --output-dataset

```

## MCP server setup

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