# NL Adres Deduplicatie / Normalisatie (`codeclouds/nl-adres-dedup-normalisatie`) Actor

Normalizes Dutch addresses from JSON or CSV, checks them against the PDOK Locatieserver, and groups only confirmed BAG number designation duplicates. Every input row is preserved; uncertain or unconfirmed addresses stay separate with reasons and suggestions.

- **URL**: https://apify.com/codeclouds/nl-adres-dedup-normalisatie.md
- **Developed by:** [Dennis](https://apify.com/codeclouds) (community)
- **Categories:** Developer tools, Other
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $3.00 / 1,000 address 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?

Actors are web data automations that power AI and operations. They run on the Apify platform to scrape websites, process data, connect APIs, and automate workflows.
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.
Actors are written with capital "A".

## 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.
The best way to integrate Actors is as follows.

- **AI agents and MCP clients** — the [Apify MCP server](https://docs.apify.com/integrations/mcp.md) at `https://mcp.apify.com` (remote, streamable HTTP, OAuth on first use).
- **Agentic workflows and local Actor development** — [Agent Skills](https://apify.com/.well-known/agent-skills/index.json) with the [Apify CLI](https://docs.apify.com/cli/docs.md): `npm install -g apify-cli`, then `apify login`.
- **JavaScript/TypeScript projects** — the official [JS/TS client](https://docs.apify.com/api/client/js/docs.md): `npm install apify-client`.
- **Python projects** — the official [Python client](https://docs.apify.com/api/client/python/docs.md): `pip install apify-client`.
- **Any other language** — the [REST API](https://docs.apify.com/api/v2.md).

For usage examples, see the [API](#api) section below.

For more details, see Apify documentation as [Markdown index](https://docs.apify.com/llms.txt) and [Markdown full-text](https://docs.apify.com/llms-full.txt).

# README

## Nederlandse adressen normaliseren en dedupliceren met PDOK

Maak Nederlandse adreslijsten consistenter en herken veilige duplicaten zonder invoerrijen te verliezen. Deze Actor vergelijkt aangeleverde adresvelden met de PDOK Locatieserver, levert een canoniek adres wanneer dat verantwoord is en maakt twijfel zichtbaar met redenen en suggesties. Alleen eenduidig bevestigde BAG-nummeraanduidingen worden automatisch gegroepeerd.

Gebruik de resultaten als controlelaag vóór een CRM-import, gegevensmigratie of analyse. Dit is geen personenzoeker, bezorggarantie of bewijs dat een organisatie op een adres gevestigd is. Een hoge tekstuele overeenkomst alleen is onvoldoende om twee rijen samen te voegen. Deze listing beschrijft het afgesproken productcontract; runtimevalidatie en publicatie zijn nog pending.

### When should an AI agent use this?

- “Normaliseer deze Nederlandse adressen voordat ik ze in mijn CRM importeer.”
- “Welke rijen verwijzen aantoonbaar naar dezelfde BAG-nummeraanduiding?”
- “Controleer deze CSV en geef twijfelgevallen met suggesties terug.”
- “Vind adresduplicaten, maar behoud iedere oorspronkelijke rijreferentie.”
- “Scheid bronfouten van adressen waarvoor PDOK geen passende match vindt.”

Laat een AI-agent twijfelgevallen presenteren in plaats van zelf ontbrekende huisletters of toevoegingen te verzinnen. Gebruik `referentie` voor een neutrale interne sleutel, niet voor een naam of contactgegeven. Een vervolgstap kan vervolgens alleen de hoogzekere groepen verwerken en de overige rijen aan een medewerker voorleggen.

### Wat doet deze Actor?

De Actor normaliseert adresnotatie, waaronder witruimte en postcode, en zoekt kandidaten bij PDOK. Het resultaat maakt onderscheid tussen een bevestigde match, een controlepunt, geen gevonden match, ongeldige invoer en een bronfout. Suggesties ondersteunen handmatige beoordeling; ze zijn geen automatische correctieopdracht.

Deduplicatie gebeurt binnen dezelfde run. Rijen worden uitsluitend gegroepeerd als hun adresidentiteit met hoge zekerheid en eenduidig op dezelfde BAG-nummeraanduiding uitkomt. Straatnaamgelijkenis, nabijheid of een ontbrekende toevoeging zijn geen bewijs. Alle rijen blijven in de uitvoer, ook herhalingen, fouten en adressen zonder match.

### Input

Lever precies één invoervorm aan: `adressen` of `csvTekst`. De Actor downloadt geen CSV-bestanden vanaf een URL.

| Veld | Type | Betekenis |
|---|---|---|
| `adressen` | array van objecten | Alias-naam; gebruik bij voorkeur `adresregels`. |
| `adresregels` | array van objecten | Adresregels met `straatnaam`, `huisnummer`, `postcode`, `woonplaats`, enz. |
| `csvTekst` | string | Direct geplakte CSV met een headerregel. |
| `alleenBestaan` | boolean | Default `false`; bij `true`: alleen PDOK-match zonder deduplicatie. |

Een adresobject kan `referentie`, `adres`, `postcode`, `huisnummer`, `huisletter`, `huisnummertoevoeging`, `straatnaam` en `woonplaats` bevatten. `huisnummer` mag een string of getal zijn; de overige waarden zijn strings. Dezelfde veldnamen dienen als CSV-headers. Gebruik vrije adrestekst in `adres` of geef gestructureerde onderdelen mee. Onvoldoende of tegenstrijdige informatie kan een ongeldige invoer of controlepunt opleveren.

```json
{
  "adresregels": [
    { "referentie": "dam-1", "straatnaam": "Dam", "huisnummer": 1, "postcode": "1012 JS", "woonplaats": "Amsterdam" },
    { "referentie": "dom-1", "straatnaam": "Domplein", "huisnummer": 9, "woonplaats": "Utrecht" }
  ],
  "alleenBestaan": false
}
```

Een CSV-variant voor dezelfde publieke voorbeeldlocaties:

```json
{
  "csvTekst": "straatnaam;huisnummer;postcode;woonplaats\nDam;1;1012 JS;Amsterdam\nDomplein;9;3512 JE;Utrecht",
  "alleenBestaan": false
}
```

Controleer vooraf de omvang van je batch. Splits grotere bestanden in runs van maximaal 1000 rijen; duplicaatgroepen worden niet tussen runs samengevoegd. Voeg geen kolommen met namen, telefoonnummers of e-mailadressen toe. Zet dergelijke informatie evenmin in het vrije adresveld.

### Output

Elke invoerrij krijgt één resultaat. De kernvelden zijn:

| Veld | Betekenis |
|---|---|
| `rijNummer`, `referentie` | Rijpositie en optionele eigen sleutel voor terugkoppeling. |
| `invoer` | Alleen de aangeleverde adresvelden, zonder contactgegevens of referentie. |
| `genormaliseerd` | Canoniek adres of `null` als geen adres veilig kan worden vastgesteld. |
| `status` | `gematcht`, `controleren`, `niet_gevonden`, `ongeldige_invoer` of `bronfout`. |
| `matchScore` | Heuristische score van 0 tot 100; nadrukkelijk geen kanspercentage. |
| `matchZekerheid` | `hoog`, `laag` of `geen`. |
| `redenen`, `suggesties` | Uitleg en eventuele bronkandidaten voor controle. |
| `bagNummeraanduidingId` | Vastgestelde BAG-nummeraanduiding, anders `null`. |
| `duplicaatVanRij` | Verwijzing naar de eerste bevestigde match in dezelfde groep, anders `null`. |
| `duplicaatGroep`, `groepsgrootte` | Groepsidentificatie en aantal rijen; onzekere adressen worden niet samengevoegd. |
| `dedupeHash` | Korte SHA-256-hash (`sha256(...).slice(0,16)`) over de canonieke adresidentiteit; nuttig voor AI-agenten om duplicaten te vergelijken zonder de actor opnieuw te draaien. |

Illustratie van een ongeldige rij; de redenstekst is een voorbeeld, geen vaste enumwaarde:

```json
{
  "rijNummer": 1,
  "referentie": "controle-1",
  "invoer": {},
  "genormaliseerd": null,
  "status": "ongeldige_invoer",
  "matchScore": 0,
  "matchZekerheid": "geen",
  "redenen": ["Geen bruikbare adresvelden aangeleverd."],
  "suggesties": [],
  "bagNummeraanduidingId": null,
  "duplicaatVanRij": null,
  "duplicaatGroep": null,
  "groepsgrootte": 1
}
```

Behandel `controleren` niet als mislukte bronaanroep: er kan een bruikbare kandidaat zijn, maar onvoldoende bewijs voor automatische verwerking. Bij `bronfout` kon de bronbeoordeling niet succesvol worden afgerond. Bij `niet_gevonden` is de bron wel geraadpleegd, zonder voldoende passende match.

### Praktische toepassingen

Voor CRM-opschoning kun je bevestigde duplicaatgroepen gebruiken als invoer voor je eigen samenvoegbeleid. De Actor verwijdert geen klantenrecords en bepaalt niet welke administratieve gegevens je moet bewaren. Bij een migratie kun je iedere `referentie` terugkoppelen naar het oorspronkelijke systeem en twijfelgevallen afzonderlijk controleren.

Voor analyses helpt consistente adresnotatie om onbedoelde dubbeltellingen te onderzoeken. Een BAG-nummeraanduiding identificeert echter een adres, geen persoon, huishouden of bedrijf. Meerdere organisaties op één adres zijn daarom niet automatisch zakelijke duplicaten.

### Pricing

De Actor gebruikt Pay Per Event (PPE):

- `apify-actor-start`: $0.00005 per start, het standaard platformevent.
- `adres-resultaat`: $0.003 per succesvol door de bron beoordeelde rij.

Ook `controleren`, `niet_gevonden` en een duplicaat kosten één resultaatevent wanneer de bronbeoordeling slaagt. Er is geen aparte deduplicatietoeslag. `ongeldige_invoer` en `bronfout` krijgen geen resultaatevent; het starttarief blijft gelden. Honderd succesvol beoordeelde rijen kosten zo $0.30005 inclusief één start. Een run-cache verlaagt herhaalde bronvragen, niet de prijs per beoordeelde rij.

### Bron, privacy en beperkingen

PDOK Locatieserver is de beoogde adresbron; de Actor is geen officiële PDOK- of Kadasterdienst. Bronactualiteit, beschikbaarheid en zoekgedrag kunnen veranderen. De eerste zoekhit en het totale aantal zoekresultaten bewijzen geen exacte adresmatch. Een match bevestigt geen bewoning, eigendom, bereikbaarheid of postbezorging. Huisletters en huisnummertoevoegingen worden nooit geraden om alsnog een duplicaatgroep te maken.

Alleen adresvelden worden gebruikt voor PDOK-zoekvragen: geen referenties, namen of contactvelden. Er is alleen caching binnen de run, geen named store of eigen cross-run adresarchief. Apify bewaart aangeleverde input en geproduceerde output volgens de ingestelde platformretentie. Deze gegevens worden dus niet automatisch bij procesafsluiting vernietigd; beheer zelf toegang, bewaartermijnen en verwijdering.

Adressen kunnen in combinatie met andere gegevens persoonsgegevens zijn. Zorg voor een passende grondslag en beperk invoer tot wat noodzakelijk is. De precieze AVG-rollen en afspraken hangen af van de toepassing; gebruik deze listing niet als vervanging voor een privacybeoordeling. PDOK introduceert een nieuwe Location API; definitieve API-keuze, gebruiksvoorwaarden en gevolgen van de robots.txt-beperking voor de legacy Locatieserver-route blijven pending vóór publicatie.

### FAQ

**Worden dubbele rijen verwijderd?** Nee. Iedere rij blijft beschikbaar; groepsvelden wijzen alleen op veilig bevestigde adresduplicaten.

**Is een score van 90 een kans van 90 procent?** Nee. `matchScore` is een heuristische vergelijkingsscore. Gebruik ook zekerheid, redenen en suggesties.

**Kan ik een ontbrekende huisletter laten aanvullen?** Niet door te gokken. Ontbrekende of conflicterende onderdelen kunnen handmatige controle noodzakelijk maken.

**Kan ik een bestand via een URL aanleveren?** Nee. Plak CSV-tekst in `csvTekst` of lever objecten in `adressen` aan.

**Zijn mislukte matches gratis?** Geen gevonden match na een geslaagde bronbeoordeling is betaald. Ongeldige rijen en bronfouten hebben geen resultaattarief.

**Kan ik deze Actor als formele adresvalidatie gebruiken?** Niet als wettelijke verklaring of bezorggarantie. Controleer kritieke beslissingen met passende aanvullende bronnen.

### Related Actors

- [PDOK Locatieserver](https://apify.com/codeclouds/pdok-locatieserver) — voor afzonderlijke locatiezoekvragen in plaats van batchnormalisatie met behoud van rijen en duplicaatgroepen.

*Zoekwoorden: Nederlandse adressen normaliseren, adresduplicaten, adreslijst opschonen, BAG-nummeraanduiding, PDOK, CSV-adrescontrole, CRM-adressen dedupliceren.*

### Related Actors

- `pdok-locatieserver` — bestaande live actor voor PDOK-geocoding en adreslookup; deze actor gebruikt het PDOK Locatieserver v3.1 `/search/v3_1/free`-endpoint voor matchscore en BAG-ID-validatie.
- `vastgoed-xxl` en `nl-perceel-dossier-xxl` — adres- en perceelgebaseerde analyses waarbij deze normalisatie als voorstap kan dienen.

### Keywords

netherlands, dutch addresses, address normalization, address deduplication, PDOK, BAG, CSV cleaning, CRM data quality, AI agent, adresnormalisatie, adrescontrole

### Uitbreiding: `dedupeHash`

Vanaf versie 0.1 levert elke resultaatregel een `dedupeHash`: een korte SHA-256-hash (`slice(0,16)`) over de canonieke adresidentiteit (`adresIdent`). Dit maakt het mogelijk voor een AI-agent of een downstream-proces om duplicaten te herkennen zonder de actor opnieuw te draaien of de volledige dataset te herverwerken. De hash is deterministisch: dezelfde adresgegevens leveren altijd dezelfde hash, ongeacht de invoervolgorde.

### Changelog

#### 0.1.0 — in voorbereiding

- Eerste documentatie voor PDOK-adresmatching, twee invoervormen en conservatieve BAG-deduplicatie.
- Publicatie en definitieve runtimevalidatie zijn nog niet afgerond.

# Actor input Schema

## `adresregels` (type: `array`):

Lijst met adresobjecten; elk object mag velden als straatnaam, huisnummer, postcode, woonplaats bevatten.

## `csvTekst` (type: `string`):

Ruwe comma-gescheiden tekst als alternatief voor adresregels.

## `alleenBestaan` (type: `boolean`):

Wanneer waar: alleen controleren of het adres bestaat (zonder deduplicatie).

## Actor input object example

```json
{
  "adresregels": [
    {
      "straatnaam": "Dam",
      "huisnummer": 1,
      "postcode": "1012 JS",
      "woonplaats": "Amsterdam"
    }
  ],
  "alleenBestaan": false
}
```

# Actor output Schema

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

Lijst met één resultaat per invoerregel (dataset-items).

# 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 = {
    "adresregels": [
        {
            "straatnaam": "Dam",
            "huisnummer": 1,
            "postcode": "1012 JS",
            "woonplaats": "Amsterdam"
        }
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("codeclouds/nl-adres-dedup-normalisatie").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 = { "adresregels": [{
            "straatnaam": "Dam",
            "huisnummer": 1,
            "postcode": "1012 JS",
            "woonplaats": "Amsterdam",
        }] }

# Run the Actor and wait for it to finish
run = client.actor("codeclouds/nl-adres-dedup-normalisatie").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 '{
  "adresregels": [
    {
      "straatnaam": "Dam",
      "huisnummer": 1,
      "postcode": "1012 JS",
      "woonplaats": "Amsterdam"
    }
  ]
}' |
apify call codeclouds/nl-adres-dedup-normalisatie --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,codeclouds/nl-adres-dedup-normalisatie"
        }
    }
}
```

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/x2lkuUxdNjf1B9fnh/builds/95HmIqPmbMBizfhB5/openapi.json
