# France Property Sales & Prices (DVF) (`suitable_sunspot/dvf-france-property-sales`) Actor

Every French property sale, straight from the official DVF register: price, address, surface, date and price per m2. Filter by department, commune, postal code, price, surface, year and property type. Multi-lot sales merged into one clean JSON row.

- **URL**: https://apify.com/suitable_sunspot/dvf-france-property-sales.md
- **Developed by:** [Dmitry Melikhov](https://apify.com/suitable_sunspot) (community)
- **Categories:** Real estate
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $3.00 / 1,000 property transaction 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

## France Property Sales & Prices (DVF)

**Every French property sale, as clean JSON.** This Actor queries the official *Demandes de valeurs foncières* (DVF) register — the sales database published by the French tax administration (DGFiP) — and returns transactions filtered by department, commune, postal code, price, surface, date range and property type, each with a computed price per square metre.

The raw source is public and free, but it ships as large pipe-delimited text files per year, documented only in French, and with no filtering API. This Actor downloads the Etalab-cleaned per-department CSVs, applies your filters, and returns clean JSON — no file wrangling required.

### Use cases

- **Valuation and comparables** — pull every sale in a commune for a given year and surface range, then read `prixM2` to price a property against real transactions rather than asking prices.
- **Price-per-m² trends** — track how €/m² moves across departments, communes or years for market research and reporting.
- **Off-plan and new-build tracking** — `salesOnly` keeps genuine sales, including VEFA (off-plan), while dropping expropriations, adjudications and exchanges.
- **Geospatial and mapping** — every row carries latitude and longitude, so results drop straight into a map or a spatial analysis.
- **Multi-lot and portfolio analysis** — a sale bundling an apartment, a cellar and a parking space is three raw rows; with `oneRowPerTransaction` (default) they merge into one sale-level record with summed surface and a correct €/m².
- **Data pipelines** — clean JSON out, so results feed straight into a warehouse or dashboard without parsing French fixed-width files.

### Output

With `oneRowPerTransaction` (default), one row per real-world sale:

```json
{
  "idMutation": "2024-1193218",
  "dateMutation": "2024-01-04",
  "natureMutation": "Vente",
  "valeurFonciere": 1042000.0,
  "prixM2": 12116.28,
  "adresse": "4 VLA PERREUR",
  "codePostal": "75020",
  "codeCommune": "75120",
  "nomCommune": "Paris 20e Arrondissement",
  "codeDepartement": "75",
  "typesLocal": ["Appartement", "Dépendance"],
  "surfaceReelleBati": 86.0,
  "surfaceDependance": null,
  "nombrePiecesPrincipales": 4,
  "surfaceTerrain": null,
  "longitude": 2.405228,
  "latitude": 48.868216
}
```

`surfaceReelleBati` is the **residential / main** built surface — annexes such as
cellars, garages, parking and sheds (type `Dépendance`) are excluded and reported
separately as `surfaceDependance` (which is `null` when the source reports no
surface for them). `prixM2` is `valeurFonciere` divided by `surfaceReelleBati`
(the living area), so a sale that bundles a cellar no longer drags the €/m² down.
`nombrePiecesPrincipales` and `surfaceTerrain` are summed across every lot. With
`oneRowPerTransaction: false`, you get the raw per-lot rows instead — `typesLocal`
becomes a single `typeLocal`, and a multi-lot sale produces one row per distinct
type it contains (the raw file doesn't separate surface per lot, so each of those
rows repeats the same summed surface/price — if you need true per-lot surface,
query one small commune and reconcile the raw file yourself for that case).

### Input

| Field | Type | Notes |
|---|---|---|
| `departments` | array of strings | **Required.** French department codes, e.g. `["75", "92"]`. Corsica is `2A`/`2B`; overseas departments are `971`-`976`. |
| `year` | string | One of `2019`-`2026`. Defaults to `2024`. (The latest year is partial until the publisher's next update.) |
| `communeName` | string | Case-insensitive substring match. |
| `postalCode` | string | Exact match. |
| `propertyType` | string | `Maison`, `Appartement`, `Dépendance`, or the industrial/commercial category. |
| `minPrice` / `maxPrice` | integer | Filters on the declared transaction price (EUR). |
| `minSurface` / `maxSurface` | integer | Filters on the living built surface (sqm), excluding `Dépendance` annexes. |
| `dateFrom` / `dateTo` | string | `YYYY-MM-DD`. |
| `salesOnly` | boolean | Default `true`. Excludes expropriations, adjudications and exchanges — keeps actual sales (including off-plan/VEFA). |
| `oneRowPerTransaction` | boolean | Default `true`. The raw source has one row per *lot* — a sale bundling an apartment, a cellar and a parking space is 3 raw rows. With this on, rows sharing the same `idMutation` are merged: living surface (`surfaceReelleBati`) and room counts are summed, annex surface goes to `surfaceDependance`, `prixM2` is computed on the living surface, and every property type involved is listed in `typesLocal`. Set to `false` to get the raw per-lot rows instead (see below). |
| `maxResults` | integer | Hard cap on rows returned, applied department by department in the order given. Default `1000`. |

Note: the official DVF source currently publishes 2019–2025. 2026 is selectable in advance and will return results once the publisher releases it.

### Data source and license

Source: [data.gouv.fr — Demandes de valeurs foncières géolocalisées](https://www.data.gouv.fr/datasets/demandes-de-valeurs-foncieres/),
redistributed per-department by Etalab at `files.data.gouv.fr/geo-dvf`.
Published under [Licence Ouverte / Open Licence 2.0](https://www.etalab.gouv.fr/licence-ouverte-open-licence/).
Updated by the publisher roughly twice a year (April and October); the most
recent full year is usually only complete by the following spring.

### Run tests

```bash
.venv/bin/python -m pytest tests/ -q
```

# Actor input Schema

## `departments` (type: `array`):

French department codes to query, e.g. \["75", "92", "2A"]. Overseas departments (971-976) and Corsica (2A/2B) are supported.

## `year` (type: `string`):

Transaction year. The source (data.gouv.fr geo-dvf) covers 2019-2025; 2026 is added ahead of the publisher's next release and will return data once published (usually April and October).

## `communeName` (type: `string`):

Case-insensitive substring match on the commune name, e.g. "Lyon". Leave empty for all communes in the selected department(s).

## `postalCode` (type: `string`):

Exact postal code filter, e.g. "75011".

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

Filter to one property type. Leave empty for all types.

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

Filters on valeur_fonciere (the total declared transaction price, not necessarily the price of a single lot).

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

Filters on valeur_fonciere, the upper bound.

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

Filters on surface_reelle_bati. Rows with no built surface (e.g. bare land) are excluded once this is set.

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

Filters on surface_reelle_bati, the upper bound.

## `dateFrom` (type: `string`):

Only include transactions on or after this date.

## `dateTo` (type: `string`):

Only include transactions on or before this date.

## `salesOnly` (type: `boolean`):

Keep only nature_mutation = "Vente" or "Vente en l'état futur d'achèvement" (VEFA), excluding expropriations, adjudications, exchanges, etc.

## `oneRowPerTransaction` (type: `boolean`):

The raw source has one row per lot, so a sale bundling an apartment plus a cellar and a parking space is 3 raw rows. When true (recommended), rows sharing the same id_mutation are merged into one: surfaces and room counts are summed, and all distinct property types involved are listed. When false, you get the raw per-lot rows instead.

## `maxResults` (type: `integer`):

Hard cap on returned rows, applied after filtering, department by department in the order given. Use this to bound cost on a pay-per-result Actor.

## Actor input object example

```json
{
  "departments": [
    "75"
  ],
  "year": "2025",
  "propertyType": "",
  "salesOnly": true,
  "oneRowPerTransaction": true,
  "maxResults": 1000
}
```

# Actor output Schema

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

No description

# 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 = {
    "departments": [
        "75"
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("suitable_sunspot/dvf-france-property-sales").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 = { "departments": ["75"] }

# Run the Actor and wait for it to finish
run = client.actor("suitable_sunspot/dvf-france-property-sales").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 '{
  "departments": [
    "75"
  ]
}' |
apify call suitable_sunspot/dvf-france-property-sales --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,suitable_sunspot/dvf-france-property-sales"
        }
    }
}
```

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/YKAlMwGiGWNYdpcHh/builds/JUnEaWMicun15SSNF/openapi.json
