# DVF France property sales / Mutations immobilières (beta) (`wavy_millpond/dvf-france`) Actor

Search official DVF+ by commune name/INSEE, year, total price, built area and type. FR : mutations par commune, année, prix, surface et type. Beta / Bêta.

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

## Pricing

$0.25 / 1,000 résultat réel / real results

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

## DVF France — property transactions / transactions immobilières (beta / bêta)

[Open Actor / Ouvrir l’Actor](https://apify.com/wavy_millpond/dvf-france). Search official [Cerema DVF+ open data](https://www.data.gouv.fr/datasets/dvf-open-data) derived from DGFiP, by commune and transaction year. Structured total price, areas and property type; JSON/CSV/Excel exports for spreadsheets, n8n, Make and AI agents. Independent tool, unaffiliated with Cerema or DGFiP.

Rechercher les mutations officielles DVF+ du Cerema, issues de la DGFiP, par commune et année de mutation. Prix total, surfaces et type de bien structurés ; exports JSON/CSV/Excel pour tableurs, n8n, Make et agents IA. Outil indépendant, non affilié au Cerema ni à la DGFiP.

### Try / Essayer

Synthetic demo: one fictional row, no network and no `result` event. Démo synthétique : une ligne fictive, sans réseau ni événement `result`.

```json
{"mode":"demo"}
```

Existing code/year input remains supported. L’entrée existante par code/année reste compatible :

```json
{"mode":"live","communeCode":"59350","year":2025,"maxItems":10}
```

Optional commune name and local filters. Nom de commune et filtres locaux facultatifs :

```json
{"mode":"live","communeName":"Lille","year":2025,"minPrice":100000,"maxPrice":300000,"minBuiltArea":40,"maxBuiltArea":120,"propertyTypes":["111","121"],"maxItems":10}
```

Use `communeCode` OR `communeName`. The code is an INSEE code, not a postal code. A name is resolved through the official [geo.api.gouv.fr communes API](https://geo.api.gouv.fr/decoupage-administratif/communes), using an exact name match after case/accent normalization. No population-based choice: homonyms fail with INSEE choices; rerun with the chosen `communeCode`. Suggestions, abbreviations and historic commune names are not automatically accepted. Code input skips this lookup. Both routes reject departments 57, 67, 68 and 976, outside DVF coverage. A valid commune can have no available transactions.

Utiliser `communeCode` OU `communeName`. Le code est un code INSEE, pas postal. Un nom est résolu via l’API officielle des communes, par correspondance exacte après normalisation casse/accents. Aucun choix par population : les homonymes provoquent une erreur avec les codes INSEE ; relancer avec le `communeCode` choisi. Suggestions, abréviations et anciens noms ne sont pas acceptés automatiquement. Le code évite cet appel. Les deux parcours refusent les départements 57, 67, 68 et 976, non couverts par DVF. Une commune valide peut n’avoir aucune mutation disponible.

`year` is the transaction year, not publication year (2010–current year); recent data is incomplete and historic availability depends on Cerema. `maxItems`: 1–1,000 unique delivered matches, subject to budget. The demo ignores filters. `minPrice`/`maxPrice` apply to the mutation’s total `valeurfonc` in EUR; `minBuiltArea`/`maxBuiltArea` apply to its total `sbati` in m². Bounds are inclusive, finite, nonnegative numbers; minimum cannot exceed maximum. A missing value fails an active bound; zero is retained and can match zero. Omitted bounds do not exclude missing values.

`year` désigne l’année de mutation, pas de diffusion (2010–année courante) ; les années récentes sont incomplètes et l’historique dépend du Cerema. `maxItems` : 1 à 1 000 correspondances uniques livrées, sous réserve du budget. La démo ignore les filtres. Les bornes de prix portent sur la `valeurfonc` totale en euros ; celles de surface sur le `sbati` total en m². Bornes inclusives, finies, positives ou nulles ; minimum ≤ maximum. Une valeur manquante échoue à une borne active ; zéro est conservé et peut correspondre à zéro. Sans borne, les valeurs manquantes sont conservées.

### Property types / Types de bien

`propertyTypes` is an optional OR-list of exact official DVF+ `codtypbien` codes. Empty/omitted means all types. No label guessing or prefix matching. These eight selectable codes/labels were verified with bounded read-only requests to the official Cerema endpoint on 2026-10-29. Other source types remain visible without a type filter; this beta does not offer every source category. DV3F’s finer codes must not be substituted.

`propertyTypes` est une liste OU facultative de codes officiels DVF+ exacts. Vide/absente : tous les types. Aucun libellé deviné ni préfixe. Ces huit codes/libellés ont été vérifiés par requêtes officielles bornées en lecture seule le 29/10/2026. Sans filtre de type, les autres catégories restent visibles ; cette bêta ne propose pas toutes les catégories de la source. Ne pas substituer les codes plus fins de DV3F.

| Code | Official label / Libellé officiel | English meaning |
| --- | --- | --- |
| `111` | UNE MAISON | One house |
| `112` | DES MAISONS | Several houses |
| `121` | UN APPARTEMENT | One apartment |
| `131` | UNE DEPENDANCE | One outbuilding |
| `14` | ACTIVITE | Business property |
| `151` | BATI MIXTE - LOGEMENTS | Mixed residential building |
| `21` | TERRAIN DE TYPE TAB | Tax-classified building land |
| `2313` | TERRAIN DE TYPE TERRE ET PRE | Arable land and meadow |

`21` is a fiscal classification, not a guarantee that planning rules allow construction. `21` est une classification fiscale, sans garantie de constructibilité au regard de l’urbanisme.

### Output and limits / Sortie et limites

Fields: `id` (`idmutinvar`), `transactionDate`, `priceEur`, `builtAreaM2`, `landAreaM2`, `propertyTypeCode`, `propertyType`, `mutationType`, `communeCodes`, `source`, `sourceApiUrl`, `fetchedAt`, `isDemo`, `attribution`. Missing values are null or an empty list; zero is preserved. The price and areas describe a whole mutation, potentially several properties/communes. No price/m² calculation or property valuation. No owner/buyer identity, street address, parcel/local identifier or coordinates are exported. The linked official source page contains additional data. Do not reidentify people or expose DVF results to external search-engine indexing.

Champs ci-dessus : identifiant stable, date, prix total, surfaces, type, communes, source et attribution. Valeurs manquantes : null ou liste vide ; zéro conservé. Prix et surfaces décrivent la mutation entière, parfois plusieurs biens/communes. Aucun prix au m² ni estimation. Aucune identité vendeur/acheteur, adresse détaillée, parcelle, identifiant de local ou coordonnées exportés. La page officielle liée contient davantage de données. Ne pas réidentifier les personnes ni exposer les résultats DVF à l’indexation externe.

`SUMMARY`: `emitted` delivered matches, `scanned` source rows actually evaluated (including duplicates), `filtered` unique rows rejected by local filters, `duplicates`, `requests` DVF page requests, `communeResolutionRequests`, `communeCode`, `totalReported` source total before local filters, `httpAttempts` (both APIs including retries), `exhausted`, `truncated`, budget/status/timestamps. Filters run before storage and charging. Rejected rows cost no `result` event but still consume source requests/runtime. Source pages continue after filtered rows until enough matches, source exhaustion or 10,000 evaluated source rows. Unprocessed rows in a fetched final page keep `exhausted: false`, `truncated: true`; reaching 10,000 before exhaustion also truncates. No promise of 1,000 matches or a filtered grand total.

`SUMMARY` distingue correspondances livrées, lignes sources examinées (doublons inclus), lignes uniques filtrées, doublons, pages DVF, résolution commune, code choisi, total source avant filtres, tentatives HTTP des deux API, épuisement, troncature, budget et statut. Le filtrage précède stockage/facturation. Une ligne écartée ne déclenche aucun événement `result`, mais consomme requêtes et temps. La pagination continue malgré les lignes filtrées, jusqu’au plafond de correspondances, à la fin de source ou à 10 000 lignes examinées. Des lignes non examinées dans la dernière page ou l’atteinte du plafond source impliquent une troncature. Aucun total filtré exhaustif ni nombre de correspondances garanti.

Cerema’s [official client](https://github.com/CEREMA/apifoncier/blob/main/apifoncier/config.py) references `https://apidf-preprod.cerema.fr/dvf_opendata/mutations/`. This experimental API has no guaranteed availability; no third-party fallback. Both transports whitelist their exact HTTPS endpoint, reject redirects, cap bodies at 2 MiB, time out after 45 s and retry network/429/5xx failures up to three attempts with bounded backoff. One DVF request at a time; one second between pages. Arbitrary `next` URLs are ignored. Source data can change between pages; no transactional snapshot or cross-run deduplication.

Le client officiel référence cet endpoint expérimental : disponibilité non garantie, aucun repli tiers. Pour les deux API : endpoint HTTPS exact autorisé, redirections refusées, corps ≤ 2 Mio, timeout 45 s et trois tentatives maximum pour réseau/429/5xx avec attente bornée. Une page DVF à la fois, une seconde entre pages. Les URL `next` arbitraires sont ignorées. La source peut changer entre pages ; aucune photographie transactionnelle ni déduplication entre runs.

A restarted/resurrected run with an existing nonempty dataset fails before collection (`EXISTING_DATASET`), preventing replay and repeated charging. Inspect partial rows and summary before a new run. Une reprise/résurrection avec dataset non vide échoue avant collecte (`EXISTING_DATASET`) pour éviter rejeu/refacturation. Examiner lignes partielles et bilan avant un nouveau run.

Both filtered and unfiltered searches stop after at most 100 DVF pages. A truncated summary reports `stopReason`: `PAGE_LIMIT`, `SOURCE_LIMIT` or `MAX_ITEMS`. Active local filters use fixed 100-row pages even for one requested match, reducing requests spent on rejected rows.

Avec ou sans filtre, arrêt après 100 pages DVF au maximum. Un bilan tronqué précise `stopReason` : limite de pages, de lignes sources ou de résultats. Avec filtres locaux, les pages restent à 100 lignes même pour une seule correspondance demandée, afin de réduire les appels consacrés aux lignes écartées.

### Pricing and reuse / Prix et réutilisation

Target price: **USD 0.25 per 1,000 real delivered results** (`result`: USD 0.00025 each); verify the active Apify Pricing tab. No startup event. No `result` event in demo; cloud resources can still consume credit. Available PPE budget reduces the match limit before either source is called.

Tarif prévu : **0,25 USD pour 1 000 résultats réels livrés** (`result` : 0,00025 USD par ligne) ; vérifier l’onglet Pricing actif. Aucun événement de démarrage. Démo sans événement `result`, mais ressources cloud potentiellement consommatrices de crédit. Le budget PPE réduit le plafond avant tout appel source.

DVF+ / Cerema / DGFiP, Licence Ouverte 2.0: retain source, identifier, dates and attribution, respect reuse conditions and corrections. Conserver source, référence, dates et attribution, respecter conditions de réutilisation et corrections. This tool must not be used to identify a person. Ne pas utiliser cet outil pour retrouver l’identité d’une personne.

### API and n8n / API et n8n

MCP: add `https://mcp.apify.com?tools=wavy_millpond/dvf-france` to a compatible remote MCP client and authorize using OAuth. Ask for live results explicitly. The Actor tool accepts the same JSON shown above. Client setup differs between applications.

MCP : ajouter cette URL à un client compatible et autoriser l’accès par OAuth. Demander explicitement des résultats réels avec `mode: "live"`. L’outil accepte le même JSON que ci-dessus ; le réglage dépend du client.

Authenticated API example (creates a billed live run). Keep the token in an environment variable/secret, never in the JSON. Exemple API authentifiée (lance un run réel facturable). Garder le jeton dans une variable/secrète, jamais dans le JSON.

```bash
curl --fail-with-body --request POST \
  'https://api.apify.com/v2/acts/wavy_millpond~dvf-france/run-sync-get-dataset-items' \
  --header "Authorization: Bearer ${APIFY_TOKEN}" \
  --header 'Content-Type: application/json' \
  --data '{"mode":"live","communeName":"Lille","year":2025,"minPrice":100000,"maxPrice":300000,"propertyTypes":["111","121"],"maxItems":10}'
```

In n8n HTTP Request: POST the same URL, JSON body from `examples/INPUT.live.json`, bearer authentication in n8n credentials. In Make: equivalent HTTP module settings. Dans n8n HTTP Request : POST vers la même URL, corps JSON du fichier d’exemple, authentification Bearer dans les credentials n8n ; réglages équivalents dans le module HTTP de Make. Synchronous requests can time out on selective filters; for longer runs use the asynchronous `/runs` endpoint and retrieve the dataset when finished. Les appels synchrones peuvent expirer avec des filtres sélectifs ; utiliser `/runs` et récupérer le dataset à la fin pour les recherches longues.

### Local checks / Vérifications locales

From repository root / depuis la racine : `npm test`. In this folder / dans ce dossier : `npm ci`, demo INPUT / INPUT démo, `apify validate-schema`, then / puis `apify run`. Automated tests use synthetic responses. Les tests automatisés utilisent des réponses synthétiques. No token in repository. Aucun jeton dans le dépôt.

# Actor input Schema

## `mode` (type: `string`):

EN: Demo: one fictional row, no network or result event; ignores filters. Live: official APIs. FR : Démo fictive sans réseau ni événement result ; ignore les filtres. Réel : API officielles.

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

EN: 1–1000 unique delivered matches, subject to PPE budget and 10000 source-row scan cap. FR : 1–1000 correspondances uniques livrées, sous réserve du budget PPE et du plafond de 10000 lignes sources examinées.

## `communeCode` (type: `string`):

EN: Live requires communeCode OR communeName. Example: 59350 (Lille), not a postal code. Skips name resolution. 57/67/68/976 unsupported. FR : En réel, code OU nom obligatoire ; code INSEE, pas postal. Évite la résolution du nom. 57/67/68/976 non couverts.

## `year` (type: `integer`):

EN: Transaction year, not publication year. Recent years incomplete; history depends on Cerema. FR : Année de transaction, pas de diffusion. Années récentes incomplètes ; historique selon Cerema.

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

EN: Optional alternative to communeCode, exact current commune name via geo.api.gouv.fr. Ambiguous names fail with INSEE choices; rerun using communeCode. FR : Alternative au code, nom actuel exact via API officielle. Homonymes refusés avec choix INSEE ; relancer avec communeCode.

## `minPrice` (type: `number`):

EN: Optional inclusive mutation-total bound (EUR); missing source values excluded, zero retained; min ≤ max. Omit to disable. FR : Borne inclusive facultative du total de mutation (EUR) ; valeurs absentes exclues, zéro conservé ; min ≤ max. Omettre pour désactiver.

## `maxPrice` (type: `number`):

EN: Optional inclusive mutation-total bound (EUR); missing source values excluded, zero retained; min ≤ max. Omit to disable. FR : Borne inclusive facultative du total de mutation (EUR) ; valeurs absentes exclues, zéro conservé ; min ≤ max. Omettre pour désactiver.

## `minBuiltArea` (type: `number`):

EN: Optional inclusive mutation-total bound (m²); missing source values excluded, zero retained; min ≤ max. Omit to disable. FR : Borne inclusive facultative du total de mutation (m²) ; valeurs absentes exclues, zéro conservé ; min ≤ max. Omettre pour désactiver.

## `maxBuiltArea` (type: `number`):

EN: Optional inclusive mutation-total bound (m²); missing source values excluded, zero retained; min ≤ max. Omit to disable. FR : Borne inclusive facultative du total de mutation (m²) ; valeurs absentes exclues, zéro conservé ; min ≤ max. Omettre pour désactiver.

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

EN: Optional OR-list of exact verified DVF+ codes, no prefix match. Empty = all, including other source types. TAB is fiscal, not planning permission. FR : Liste OU de codes DVF+ exacts vérifiés, sans préfixe. Vide = tous, y compris les autres types. TAB fiscal, sans garantie de constructibilité.

## Actor input object example

```json
{
  "mode": "demo",
  "maxItems": 10,
  "communeCode": "",
  "year": 2025,
  "communeName": "",
  "propertyTypes": []
}
```

# Actor output Schema

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

EN: Stored rows; isDemo identifies fictional data. FR : Lignes stockées ; isDemo signale les données fictives.

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

EN: Matches, filtered rows, source scan, HTTP attempts, truncation and status. FR : Correspondances, lignes filtrées, scan source, tentatives HTTP, troncature et statut.

# 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 = {};

// Run the Actor and wait for it to finish
const run = await client.actor("wavy_millpond/dvf-france").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 = {}

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

```

## MCP server setup

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

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/KMK1Q0G4eLXxyY2ta/builds/QuwkhdUjtSquhcJ1m/openapi.json
