# NL POI Finder + CBS Buurtverrijking (`codeclouds/nl-poi-finder-cbs-buurtverrijking`) Actor

Vind bedrijven/voorzieningen (business & POI search) rond elke NL-locatie — restaurants, apotheken, scholen, supermarkten — verrijkt met CBS-buurtstatistieken (inwoners, inkomen, stedelijkheid). Bespaart 3 losse API's (OSM, PDOK, CBS) + regiocode-matching in 1 call. Vanaf $0,004/POI, PPE.

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

## Pricing

from $4.00 / 1,000 poi records

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

## NL POI Finder + CBS Buurtverrijking

Find businesses and amenities (restaurants, pharmacies, supermarkets, schools, and more) within a radius of
any Dutch location, sourced live from OpenStreetMap — automatically enriched with CBS neighbourhood
statistics (population, income, urbanisation level) for the neighbourhood each result sits in.

### When should an AI agent use this?

- "Find all pharmacies within 1km of Utrecht city centre."
- "List supermarkets in Rotterdam-Zuid with the neighbourhood's average income."
- "Which restaurants are near this address, and what's the population density of that area?"
- "Give me all gyms in Amsterdam postcode 1012 with neighbourhood demographics attached."
- "Compare the urbanisation level of neighbourhoods with the most pharmacies per location."

### What this Actor does

- Resolves a location (city, postcode, full address, or `lat,lon`) to coordinates via the official PDOK
  Locatieserver — no API key needed.
- Queries OpenStreetMap (via the public Overpass API) for businesses/amenities matching a category within a
  configurable radius (nodes, ways and relations all supported).
- Accepts either a human-friendly category (`restaurant`, `pharmacy`, `supermarket`, `school`, ...) or a raw
  OSM tag (`shop=bakery`) for full flexibility.
- Returns results sorted by distance to the search location (closest first), each annotated with
  `afstand_meter` — not Overpass' arbitrary order.
- Optionally resolves the CBS neighbourhood (buurt/wijk/gemeente) for each result and attaches CBS StatLine
  statistics (population, average income, urbanisation) — dynamically matched against the chosen table's
  column titles, so it keeps working across CBS table redesigns.
- Supports multiple locations in one run, processed concurrently.
- Retries transient Overpass/PDOK/CBS failures automatically; one failed location never stops the rest of
  the run.

### Input

| Field | Type | Description |
|---|---|---|
| `location` | string | City, postcode, full address (with house number) or `"lat,lon"`. Avoid informal landmark names like "Utrecht Centraal" — see Legal/FAQ below. |
| `locations` | array | Multiple locations in one run (overrides `location`). |
| `radiusMeters` | integer | Search radius in meters (50–20,000). Default 1000. |
| `category` | string | Human-friendly category or a raw OSM tag (`key=value`). Default `restaurant`. |
| `maxResults` | integer | Cap on results per location (1–500). Default 50. |
| `enrichWithCbs` | boolean | Attach CBS neighbourhood statistics. Default `true`. |
| `cbsTableId` | string | CBS StatLine table ID. Default `86165NED` (Kerncijfers wijken en buurten). |
| `cbsFields` | array | Search terms matched against the CBS table's column titles. |
| `concurrency` | integer | Locations processed in parallel (1–20). Default 5. |

### Output

One flat JSON record per POI:

```json
{
  "osm_id": 5040305805,
  "osm_type": "node",
  "naam": "Doctor & Pharmacy",
  "categorie": "apotheek",
  "osm_tag": "amenity=pharmacy",
  "adres": "De Ruijterkade 24A",
  "postcode": "1012AA",
  "woonplaats": "Amsterdam",
  "telefoon": "+31 202357823",
  "website": "https://www.centraldoctors.nl",
  "lat": 52.3800745,
  "lon": 4.8992326,
  "afstand_meter": 340,
  "buurtcode": "BU0363AD01",
  "buurtnaam": "Stationsplein e.o.",
  "wijkcode": "WK0363AD",
  "gemeentecode": "0363",
  "gemeente": "Amsterdam",
  "cbs": {
    "_bron": "CBS StatLine 86165NED (CC-BY, cbs.nl)",
    "_regiocode": "BU0363AD01",
    "aantal inwoners": 0,
    "gemiddeld inkomen per inwoner": null,
    "stedelijkheid": 1
  }
}
```

### Use cases

- Lead generation: find businesses by category and region, with neighbourhood income/density as a
  qualifying filter.
- Site-selection research for franchises and retail chains — assess nearby amenities and demographics
  before committing to a location.
- Market research: compare business density and neighbourhood demographics across cities or postcodes.

### Pricing

This Actor uses Apify's Pay-Per-Event (PPE) pricing model.

- **Actor Start:** $0.00005 (Apify default)
- **poi-record:** $0.004 per returned POI (name, address, contact info)
- **poi-cbs-verrijking:** $0.003 per POI where CBS neighbourhood data was actually found and attached

### Legal

- Business/amenity data: OpenStreetMap (© OpenStreetMap contributors, ODbL license) — this Actor performs
  on-demand queries per run, not bulk redistribution of the OSM database.
- Geocoding: PDOK Locatieserver (Kadaster/BZK), CC0/CC-BY, no API key required.
- Neighbourhood statistics: CBS StatLine, CC-BY 4.0.
- No personal data: results describe businesses and amenities, not individuals.

### FAQ

**Q: Why did "Utrecht Centraal" not return the train station's immediate surroundings?**
A: PDOK's address search is a fuzzy street/address matcher, not a landmark database — Dutch train stations
aren't in the official address register. Bare, digit-free location names are resolved to the nearest
municipality/city instead of a specific street match, to avoid landing on an unrelated address that happens
to share a word with the landmark's name. For precise control, use a postcode, a full address with house
number, or `lat,lon` coordinates.

**Q: Can I search for a category that isn't in the built-in list?**
A: Yes — pass any raw OpenStreetMap tag as `category`, e.g. `shop=bakery` or `leisure=park`.

**Q: How current is the OpenStreetMap data?**
A: Queried live against the public Overpass API on every run — as current as OpenStreetMap itself.

### Related Actors

- **[PDOK Locatieserver](https://apify.com/codeclouds/pdok-locatieserver)** — for direct address-to-
  coordinate geocoding and standalone CBS-buurtverrijking without the POI-discovery layer.

***

*Zoekwoorden: bedrijven zoeken per locatie, POI finder Nederland, buurtdata verrijking, vestigingslocatie
onderzoek, Overpass API Nederland, Google Maps alternatief met CBS-data.*

### Keywords

netherlands, poi finder, openstreetmap, overpass api, cbs, buurtdata, lead generation, site selection,
business search, google maps alternative, location intelligence

### Changelog

#### 0.1.0

- Initial release: OSM/Overpass POI discovery by category + radius, optional CBS neighbourhood enrichment,
  multi-location support.

# Actor input Schema

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

Plaatsnaam, postcode, volledig adres (met huisnummer) of "lat,lon". Gebruik geen informele landmarknamen zoals "Utrecht Centraal" — die vallen terug op de dichtstbijzijnde gemeente/woonplaats, niet het exacte station.

## `locations` (type: `array`):

Array van locatie-strings (zelfde formaat als 'location'). Overschrijft 'location' als beide zijn opgegeven.

## `radiusMeters` (type: `integer`):

Zoekstraal rond de locatie, in meters (50–20.000).

## `category` (type: `string`):

Mens-leesbare categorie (bv. "restaurant", "apotheek", "supermarkt", "school") of een letterlijke OSM-tag (bv. "shop=bakery").

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

Bovengrens op het aantal POI's per locatie (1–500). Resultaten worden gesorteerd op afstand tot de locatie (dichtstbijzijnde eerst) vóórdat deze grens wordt toegepast.

## `enrichWithCbs` (type: `boolean`):

Voeg per POI de CBS-buurtstatistieken toe (inwoners, inkomen, stedelijkheid) op basis van de buurt waarin de POI ligt.

## `cbsTableId` (type: `string`):

StatLine-tabel voor de buurtverrijking. Standaard: Kerncijfers wijken en buurten (86165NED).

## `cbsFields` (type: `array`):

Zoektermen die gematcht worden tegen de kolomtitels van de gekozen CBS-tabel.

## `concurrency` (type: `integer`):

Aantal locaties dat gelijktijdig verwerkt wordt (1–20).

## Actor input object example

```json
{
  "location": "Utrecht",
  "locations": [],
  "radiusMeters": 1000,
  "category": "restaurant",
  "maxResults": 50,
  "enrichWithCbs": true,
  "cbsTableId": "86165NED",
  "cbsFields": [
    "aantal inwoners",
    "gemiddeld inkomen per inwoner",
    "stedelijkheid"
  ],
  "concurrency": 5
}
```

# Actor output Schema

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

Alle resultaten in het default dataset.

# 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 = {
    "location": "Utrecht",
    "category": "restaurant"
};

// Run the Actor and wait for it to finish
const run = await client.actor("codeclouds/nl-poi-finder-cbs-buurtverrijking").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 = {
    "location": "Utrecht",
    "category": "restaurant",
}

# Run the Actor and wait for it to finish
run = client.actor("codeclouds/nl-poi-finder-cbs-buurtverrijking").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 '{
  "location": "Utrecht",
  "category": "restaurant"
}' |
apify call codeclouds/nl-poi-finder-cbs-buurtverrijking --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,codeclouds/nl-poi-finder-cbs-buurtverrijking"
        }
    }
}

```

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/EtRs6mb65IPNYPi4c/builds/kqkPbL3Vb1h7H6u1f/openapi.json
