# OpenStreetMap POI Extractor: Cafes, Shops, Hotels by City (`cliqtomedia/openstreetmap-poi-extractor`) Actor

Extract points of interest from OpenStreetMap by city or bounding box and OSM tags (amenity=cafe, shop=supermarket, tourism=hotel). Name, coordinates, address, website, opening hours, all tags. No login, ODbL attribution included.

- **URL**: https://apify.com/cliqtomedia/openstreetmap-poi-extractor.md
- **Developed by:** [Cliqto Media](https://apify.com/cliqtomedia) (community)
- **Categories:** Lead generation, Travel
- **Security:** Limited permissions
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $0.50 / 1,000 poi saveds

This Actor is paid per event. You are not charged for the Apify platform usage, but only a fixed price for specific events.
Since this Actor supports Apify Store discounts, the price gets lower the higher subscription plan you have.

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

Get points of interest from OpenStreetMap for a city or a map rectangle. Add a place name and one or more map tags such as `amenity=cafe`, and the Actor returns one row per place with name, coordinates, address, website, phone and opening hours.

Use it for local lead lists, travel guides, store-location research and map projects. The smallest useful run is one city and one tag, for example cafes in Pattaya. It takes about 35–60 seconds and costs about $0.019 for 20 places on the Free plan. You need no login and no API key.

![OpenStreetMap POI Extractor: add a city and a map tag, get places with coordinates, address, hours, website and phone, export to JSON or CSV](https://api.apify.com/v2/key-value-stores/OCr5Lnmj1Kqm3js1C/records/openstreetmap-poi-readme-banner-20261017.png)

### Table of contents

- [What it does](#what-it-does)
- [How it works](#how-it-works)
- [Quick start](#quick-start)
- [Use cases](#use-cases)
- [Input](#input)
- [OSM tag cheat sheet](#osm-tag-cheat-sheet)
- [Output](#output)
- [Pricing](#pricing)
- [API, export and integrations](#api-export-and-integrations)
- [Limits](#limits)
- [Troubleshooting](#troubleshooting)
- [FAQ](#faq)
- [Data licence](#data-licence)
- [Related Actors](#related-actors)
- [Support](#support)

### What it does

The Actor reads [OpenStreetMap](https://www.openstreetmap.org/) (OSM), the free world map that volunteers edit. You give it an area and one or more map tags. It returns the places in that area that have those tags.

Each row has the OSM id and link, name, type, latitude and longitude, address, website, phone, email, opening hours, cuisine, brand, operator and wheelchair access. It can also include all raw OSM tags of the place. Every row carries the OpenStreetMap credit and the data timestamp.

#### Good fit

- Lists of cafes, restaurants, hotels or shops in one city, with coordinates and contact fields.
- Store-location and competitor maps: all supermarkets, pharmacies or fuel stations in a region.
- Travel and tourism data: hotels, attractions, viewpoints and museums for a guide or an app.
- Any other map object with an OSM tag, from bicycle parking to charging stations.

#### Not a fit

- Reviews, ratings, photos or prices. OpenStreetMap does not store them.
- A full business directory. Some places are missing from the map. Many places have no phone or website.
- Many cities in one run. Run the Actor once per area, or use a bounding box.
- Live opening status. `openingHours` is the schedule that mappers wrote down.

> **👉 [Try it now: open the input form](https://console.apify.com/actors/EPxqb34VntbJeRsrW/input)**

### How it works

1. It finds your area. A place name becomes the official OSM boundary of that place. A bounding box is used as it is.
2. It asks the public [Overpass API](https://wiki.openstreetmap.org/wiki/Overpass_API) for every place inside the area that has your tags. If one server is busy, it tries the next one.
3. It saves one clean row per place in the Dataset and a short run report, `RUN_SUMMARY`, in the key-value store. You can export the rows as JSON, CSV or Excel.

> **👉 [Try it now: open the input form](https://console.apify.com/actors/EPxqb34VntbJeRsrW/input)**

### Quick start

[![Input form: Area set to Pattaya (box 1), OSM tags set to amenity=cafe (box 2), and Max POIs set to 20 (box 3)](https://api.apify.com/v2/key-value-stores/OCr5Lnmj1Kqm3js1C/records/quick-start-form-20261028.png)](https://console.apify.com/actors/EPxqb34VntbJeRsrW/input)

1. Open the **Input** tab. **Area** already has `Pattaya` (box 1 in the picture). Type your own city or a bounding box.
2. Keep `amenity=cafe` in **OSM tags**, or add your own tag (box 2).
3. Keep **Max POIs** at `20` for a first test (box 3). This is the form prefill; the runtime default is `100`.
4. Click **Start** at the bottom of the form.
5. Open the **Output** tab and check the **POIs** table: name, type, coordinates, address, website and phone (box 5).

   [![Output table: the POIs view with name, type, coordinates, address, website and phone of each cafe](https://api.apify.com/v2/key-value-stores/OCr5Lnmj1Kqm3js1C/records/output-table-20261028.png)](https://api.apify.com/v2/key-value-stores/OCr5Lnmj1Kqm3js1C/records/output-table-20261028.png)

Expected result: about 20 rows of cafes with name, type, coordinates and, when mappers added them, address, phone and opening hours.

> **👉 [Try it now: open the input form](https://console.apify.com/actors/EPxqb34VntbJeRsrW/input)**

### Use cases

| Job | Input | Key fields | Next step |
| --- | --- | --- | --- |
| Lead list of local businesses | `area` = city, `tags` = `amenity=restaurant` | `name`, `phone`, `website`, `address` | Export CSV to your CRM or spreadsheet |
| Store and competitor map | `area` = region, `tags` = `shop=supermarket` | `brand`, `lat`, `lng`, `address` | Load into a GIS tool or a map library |
| Travel guide data | `area` = city, `tags` = `tourism=hotel`, `tourism=attraction` | `name`, `type`, `website`, `openingHours` | Feed a travel app or a content plan |
| Accessibility check | `area` = district, `tags` = `amenity=restaurant` | `wheelchair`, `name`, `lat`, `lng` | Filter by `wheelchair` in a spreadsheet |

> **👉 [Try it now: open the input form](https://console.apify.com/actors/EPxqb34VntbJeRsrW/input)**

### Input

#### Input fields

| Field | Name in the form | Type | Required | Default | Allowed values | What it does |
| --- | --- | --- | --- | --- | --- | --- |
| `area` | Area (city or bounding box) | string | yes | none (form example: `Pattaya`) | A place name up to 200 characters, or `south,west,north,east` in decimal degrees, up to 5° per side | Sets where to search. A place name uses the OSM boundary of that place. |
| `tags` | OSM tags | array of strings | yes | none (form example: `amenity=cafe`) | 1–20 tags: `key=value`, `key=*`, `key` or `key~regex`, up to 120 characters each, with no quotes or backslashes | Sets which places to return. |
| `match` | Combine tags | string | no | `any` | `any`, `all` | `any`: a place needs at least one tag. `all`: a place needs every tag. |
| `maxItems` | Max POIs | integer | no | `100` (form example: `20`) | 1–10000 | The largest number of place rows. Each place row is charged. |
| `includeTags` | Include raw OSM tags | boolean | no | `true` | `true`, `false` | Adds the `tags` object with every OSM tag of the place. Turn it off for a smaller, flat table. |

Notes:

- A place name can be a city, a district or a region, for example `Berlin Mitte` or `Lisbon, Portugal`. Add the country when the name exists in many countries.
- Duplicate tags are removed. Tag keys and values are case-sensitive, as in OSM.
- When the area has more places than `maxItems`, the Actor returns `maxItems` places in the order the Overpass server sends them (grouped by map position, not by name or importance) and marks the run summary as `truncated`.

#### Settings that work together

| When | Fields | Effect | Example |
| --- | --- | --- | --- |
| Several tags, `match` = `any` | `tags`, `match` | Places with any of the tags. | `amenity=cafe` and `amenity=restaurant` return cafes and restaurants. |
| Several tags, `match` = `all` | `tags`, `match` | Only places with every tag. | `amenity=restaurant` and `cuisine=thai` return Thai restaurants. |
| A low **Maximum total charge** on the run | `maxItems`, run options | The Actor stops at the charge limit and reports a partial run. | A limit that pays for 5 rows returns 5 rows. |
| `maxItems` above 500 | `maxItems` | The run uses 256 MB memory instead of 128 MB. The price does not change. | `maxItems` = 5000 |

#### Input examples

##### Cafes in one city

```json
{
  "area": "Pattaya",
  "tags": ["amenity=cafe"],
  "maxItems": 20
}
```

Expected result: 20 cafe rows. The run summary says that more cafes exist in the area.

##### Thai restaurants

```json
{
  "area": "Berlin",
  "tags": ["amenity=restaurant", "cuisine=thai"],
  "match": "all",
  "maxItems": 500
}
```

Expected result: restaurants that have both tags, up to 500 rows.

##### Many kinds of places in a bounding box

```json
{
  "area": "12.90,100.85,12.98,100.93",
  "tags": ["tourism=hotel", "tourism=guest_house", "shop=*"],
  "maxItems": 1000,
  "includeTags": false
}
```

Expected result: hotels, guest houses and all shops inside the rectangle, as a flat table without the `tags` object.

> **👉 [Try it now: open the input form](https://console.apify.com/actors/EPxqb34VntbJeRsrW/input)**

### OSM tag cheat sheet

| You want | Tag |
| --- | --- |
| Cafes | `amenity=cafe` |
| Restaurants | `amenity=restaurant` |
| Bars and pubs | `amenity~^(bar\|pub)$` |
| Fast food | `amenity=fast_food` |
| Pharmacies | `amenity=pharmacy` |
| Fuel stations | `amenity=fuel` |
| EV charging | `amenity=charging_station` |
| Banks and ATMs | `amenity~^(bank\|atm)$` |
| Supermarkets | `shop=supermarket` |
| Any shop | `shop=*` |
| Hotels | `tourism=hotel` |
| Attractions | `tourism=attraction` |
| Gyms | `leisure=fitness_centre` |
| Dentists | `amenity=dentist` |
| Offices | `office=*` |
| One brand | `brand=Starbucks` (with `match` = `all` and a place tag) |
| Name contains a word | `name~[Cc]offee` (with `match` = `all` and a place tag; the match is case-sensitive) |

The full list of tags is on the OSM wiki page [Map features](https://wiki.openstreetmap.org/wiki/Map_features). In the input form you write `|` without the backslash. The backslash is only needed inside this table.

> **👉 [Try it now: open the input form](https://console.apify.com/actors/EPxqb34VntbJeRsrW/input)**

### Output

The Dataset has one row per place. The **POIs** view in the Output tab shows the main fields as a table. The run summary is a separate record, `RUN_SUMMARY`, in the key-value store.

#### Output fields

| Field | Meaning | Type | Can be empty |
| --- | --- | --- | --- |
| `status` | `complete` for a place, `not_found` or `error` for a free status row | string | no |
| `statusReason` | Plain reason for a `not_found` or `error` row | string | yes, null for places |
| `errorCode` | Code for an `error` row, for example `INVALID_INPUT` or `SOURCE_TIMEOUT` | string | yes |
| `osmId` | OSM element id with type, for example `node/123`. Stable across runs. | string | no for places |
| `osmType` | `node` (a point), `way` (a building or outline) or `relation` | string | no for places |
| `osmUrl` | Link to the place on openstreetmap.org | string | no for places |
| `name` | Name as mapped, in the local language | string | yes, some places have no name |
| `nameEn` | English name, when mapped | string | yes |
| `category` | Key of the matched tag, for example `amenity` | string | no for places |
| `type` | Value of the matched tag, for example `cafe` | string | no for places |
| `matchedTag` | The matched tag, for example `amenity=cafe` | string | no for places |
| `lat`, `lng` | Latitude and longitude. For buildings and outlines, the centre. | number | no for places |
| `address` | Object with `street`, `houseNumber`, `postcode`, `city`, `suburb`, `country` and `full` | object | yes, when no address is mapped |
| `website` | Business website as an http(s) link | string | yes |
| `phone` | Business phone as mapped | string | yes |
| `email` | Business email as mapped | string | yes |
| `openingHours` | Opening hours in OSM format, for example `Mo-Fr 08:00-18:00` | string | yes |
| `cuisine` | Food type, for example `thai;seafood` | string | yes |
| `brand` | Brand or chain name | string | yes |
| `operator` | Operating company | string | yes |
| `wheelchair` | `yes`, `limited` or `no` | string | yes |
| `tags` | All OSM tags of the place | object | null when `includeTags` is off |
| `area` | Your area input | string | no |
| `areaName` | The place name that the geocoder found, or the bounding box | string | yes |
| `areaOsmId` | OSM boundary used for the search | string | null for a bounding box |
| `attribution` | `© OpenStreetMap contributors` | string | no |
| `license` | `ODbL 1.0` | string | no |
| `licenseUrl` | `https://www.openstreetmap.org/copyright` | string | no |
| `source` | `overpass` | string | no |
| `sourceEndpoint` | Overpass server that answered | string | yes |
| `dataTimestamp` | Time of the OSM data on that server, UTC | string | yes |
| `fetchedAt` | Time the Actor read the data, UTC | string | no |

`null` means the map has no value for that field. It does not mean zero or "no".

#### Output example

One place from the cafes in Pattaya example. The `tags` object is shortened.

```json
{
  "status": "complete",
  "statusReason": null,
  "errorCode": null,
  "osmId": "way/1455905589",
  "osmType": "way",
  "osmUrl": "https://www.openstreetmap.org/way/1455905589",
  "name": "Espresso Love",
  "nameEn": null,
  "category": "amenity",
  "type": "cafe",
  "matchedTag": "amenity=cafe",
  "lat": 12.9049444,
  "lng": 100.869448,
  "address": {
    "street": "Thappraya Rd",
    "houseNumber": "315",
    "postcode": "20150",
    "city": null,
    "suburb": null,
    "country": null,
    "full": "Thappraya Rd 315, 20150"
  },
  "website": null,
  "phone": "+66 81 982 4727",
  "email": null,
  "openingHours": "Mo-Su 06:00-19:00",
  "cuisine": "coffee_shop",
  "brand": null,
  "operator": null,
  "wheelchair": null,
  "tags": {
    "amenity": "cafe",
    "cuisine": "coffee_shop",
    "name": "Espresso Love",
    "opening_hours": "Mo-Su 06:00-19:00",
    "outdoor_seating": "no"
  },
  "area": "Pattaya",
  "areaName": "Pattaya City, Nong Pla Lai, Bang Lamung District, Chon Buri Province, Thailand",
  "areaOsmId": "relation/12645794",
  "attribution": "© OpenStreetMap contributors",
  "license": "ODbL 1.0",
  "licenseUrl": "https://www.openstreetmap.org/copyright",
  "source": "overpass",
  "sourceEndpoint": "https://overpass-api.de/api/interpreter",
  "dataTimestamp": "2026-10-17T08:32:06Z",
  "fetchedAt": "2026-10-17T08:33:11.550Z"
}
```

In this run, most of the 20 cafes had a name and coordinates. Only a few had a phone, a website or opening hours. That is normal for OSM data.

#### Run summary

`RUN_SUMMARY` has the run `status`, the area that was found, `counts` of places and free rows, `truncated` (true when the area has more places than `maxItems`), the OSM data timestamp and request statistics.

#### Output entities and surfaces

| Situation | Where to look | Result shape | Run status | Charged | What to do |
| --- | --- | --- | --- | --- | --- |
| Places found | Dataset, **POIs** view | One row per place, `status` = `complete` | `COMPLETE` | Start fee + each place | Open, export or filter the rows |
| No place has your tags in the area | Dataset + `RUN_SUMMARY` | One free row, `status` = `not_found`, reason in `statusReason` | `NOT_FOUND` | Start fee only | Try a wider tag or check the area |
| The area name is not found | Dataset + `RUN_SUMMARY` | One free `not_found` row | `NOT_FOUND` | Start fee only | Fix the spelling or add the country |
| The charge limit stops the run early | Dataset + `RUN_SUMMARY` | The rows that fit the limit; `partialReason` = `CHARGE_LIMIT` | `PARTIAL` | Start fee + stored places | Raise **Maximum total charge** |
| The charge limit is too low for any place | `RUN_SUMMARY` | No rows | `CAPPED` | Start fee only | Raise **Maximum total charge** |
| Invalid input, for example a bad tag | Dataset + `RUN_SUMMARY` | One free `error` row with `errorCode` = `INVALID_INPUT` | `INVALID_INPUT`, run fails | Start fee only | Fix the input field named in `statusReason` |
| OSM servers fail or time out before any place is saved | Dataset + `RUN_SUMMARY` | One free `error` row with `errorCode`, for example `SOURCE_TIMEOUT` | `ERROR` | Start fee only | Wait a few minutes and run again |
| OSM servers fail after some places are saved | `RUN_SUMMARY` and the run status message | The places already saved, no `error` row; the reason is in `RUN_SUMMARY` (`partialReason` = `SOURCE_ERROR`, `errorCode`) and in the run status message | `PARTIAL` | Start fee + stored places | Run again for the full list |

A source failure is never shown as an empty result. Check `status` and `errorCode` in the row and in `RUN_SUMMARY`.

#### Use case → fields

| Buyer job | Input path | Output entity | Key fields | Next integration |
| --- | --- | --- | --- | --- |
| Local lead list | `area`, `tags` = `amenity=restaurant` | Place row | `name`, `phone`, `website`, `address.full` | CSV export to a CRM or spreadsheet |
| Store and competitor map | `area`, `tags` = `shop=supermarket` | Place row | `brand`, `lat`, `lng`, `osmId` | GIS tool or map library |
| Travel guide data | `area`, `tags` = `tourism=hotel` | Place row | `name`, `type`, `website`, `openingHours` | Travel app or content plan |
| Repeat checks of one area | the same input on a schedule | Place row | `osmId`, `dataTimestamp` | Compare runs by `osmId` |

> **👉 [Try it now: open the input form](https://console.apify.com/actors/EPxqb34VntbJeRsrW/input)**

### Pricing

#### Pricing unit

You pay per event. There is no monthly fee. The unit is one place row: a row with `status` = `complete` in the Dataset.

| Event | Free plan | Bronze | Silver | Gold and higher |
| --- | ---: | ---: | ---: | ---: |
| Actor start (per GB of run memory) | $0.0045 | $0.0045 | $0.0045 | $0.0045 |
| Place row (POI) | $0.00072 | $0.000648 | $0.000576 | $0.000504 |

- The start fee is the same on every plan. Apify charges it automatically when the run starts. It grows with run memory: one start event per GB, rounded up. This Actor uses 128 MB, or 256 MB above 500 rows, so every run has one start event.
- Only place rows with `status` = `complete` are charged. `not_found` and `error` rows are free, so a valid run that finds nothing, a filter with no matches and a failed run cost only the start fee.
- Platform usage (compute and storage) is included in these prices. You pay only the events above.

#### Cost examples

| Places returned | Free plan | Gold plan |
| ---: | ---: | ---: |
| 1 | $0.0052 | $0.0050 |
| 10 | $0.0117 | $0.0095 |
| 100 | $0.0765 | $0.0549 |
| 1000 | $0.7245 | $0.5085 |

A run that finds nothing costs only the start fee. You can set **Maximum total charge** in the run options. The limit covers everything: the start fee is counted first, and the Actor saves only as many places as the rest of the limit pays for. It never charges more than the limit.

> **👉 [Try it now: open the input form](https://console.apify.com/actors/EPxqb34VntbJeRsrW/input)**

### API, export and integrations

- **API:** start the Actor with the same input JSON through the [Apify API](https://docs.apify.com/api/v2). Read rows from the default Dataset, and `RUN_SUMMARY` from the default key-value store.
- **Export:** download the Dataset as JSON, CSV, Excel, XML or HTML from the Output tab or the API.
- **Scheduling:** save an input as a task and run it on an Apify schedule, for example once a week for a fresh list.
- **Integrations:** send the Dataset to Google Sheets, Zapier, Make or a webhook with Apify integrations.

#### Use with AI agents (MCP)

AI agents can run this Actor through the Apify MCP server. The server URL with only this Actor:

```text
https://mcp.apify.com?tools=cliqtomedia/openstreetmap-poi-extractor
```

Smallest useful input:

```json
{
  "area": "Pattaya",
  "tags": ["amenity=cafe"],
  "maxItems": 20
}
```

Key output fields:

- `name`: the place name as mapped.
- `lat`, `lng`: coordinates of the place.
- `address.full`: the address in one line, when mapped.
- `phone`, `website`, `openingHours`: contact data and hours, when mapped.
- `status`: `complete` for a place. `not_found` and `error` rows explain an empty or failed run.

A typical run with 20 rows takes 35–60 seconds and costs $0.019 on the Free plan. One run covers one area. `maxItems` is 10000 at most.

> **👉 [Try it now: open the input form](https://console.apify.com/actors/EPxqb34VntbJeRsrW/input)**

### Limits

- One area per run. A bounding box can be 5° per side at most. In a dense city, keep the box to about 2° per side: a 2° box around Paris returned 1000 restaurants in 57 seconds. A larger box only makes the servers slower; it does not add more rows than `maxItems`.
- `maxItems` is 10000 at most. A run with 10000 rows in Paris took about 90 seconds.
- 1–20 tags per run, up to 120 characters each. Tag values cannot contain quotes or backslashes.
- A place name can be 200 characters at most.
- The Actor waits for OSM servers for about 150 seconds at most. After that it stops and reports a source error.
- Data depends on OSM volunteers. Coverage is different from region to region.
- The data is as fresh as the Overpass server. `dataTimestamp` shows the time, usually a few minutes old.
- The Actor uses public OSM servers. It follows the [Nominatim usage policy](https://operations.osmfoundation.org/policies/nominatim/) and does not send heavy parallel requests.

> **👉 [Try it now: open the input form](https://console.apify.com/actors/EPxqb34VntbJeRsrW/input)**

### Troubleshooting

#### The run returned one `not_found` row

The area has no place with your tags, or the place name was not found. Check `statusReason`. Try a wider tag such as `shop=*`, check the spelling of the city, or add the country, for example `Valencia, Spain`.

#### The place is in the wrong country

Many place names exist more than once. Check `areaName` in the row. Add the region or country to `area`, or use a bounding box.

#### The run stopped with `SOURCE_TIMEOUT`, `SOURCE_UNAVAILABLE`, `RATE_LIMITED` or `DEADLINE_REACHED`

The public OSM servers were busy, refused more requests, or did not answer within about 150 seconds. The Actor already tried other Overpass servers. Wait a few minutes and run it again. A smaller area or a lower `maxItems` also helps.

#### The run stopped with `SOURCE_BAD_REQUEST`

The Overpass server rejected the query. This usually means a `key~regex` tag with a broken regular expression. Check the tag named in `statusReason`, fix the expression and run again.

#### The run stopped with `SOURCE_INCOMPLETE` or `SOURCE_SCHEMA_CHANGED`

`SOURCE_INCOMPLETE`: the Overpass servers that answered had no data for this area yet. `SOURCE_SCHEMA_CHANGED`: a server sent an answer the Actor could not read, or one larger than its safety limit. Run again later. If it repeats, open an issue on the Issues tab with the run link.

#### Fewer rows than `maxItems`

If `status` is `PARTIAL` and the summary says `CHARGE_LIMIT`, your **Maximum total charge** was too low. Raise it. If the run is `COMPLETE`, the area simply has fewer matching places.

#### Many fields are null

OSM has only what volunteers mapped. Use `tags` to see every value that exists for the place.

> **👉 [Try it now: open the input form](https://console.apify.com/actors/EPxqb34VntbJeRsrW/input)**

### FAQ

#### Do I need an OpenStreetMap account or an API key?

No. The Actor uses public OSM services. You need only an Apify account.

#### Can I use the data in my product?

Yes, under the ODbL licence. You must credit OpenStreetMap contributors. If you share a changed database, it must stay under ODbL. Read the [OSM copyright page](https://www.openstreetmap.org/copyright).

#### Is the list complete?

No map is complete. The Actor returns every place that OSM has with your tags in the area, up to `maxItems`. `truncated` in `RUN_SUMMARY` tells you when more places exist.

#### Will two runs return the same places?

Yes, if the map did not change. `osmId` stays the same, so you can compare runs by `osmId`.

#### Does the Actor collect personal data?

It returns only public map data about places, not data about people. The `phone`, `email` and `website` fields hold public business contacts that mappers added to the map, for example the phone of a cafe. Some small businesses are run by one person, so check your local rules before you use these contacts for marketing.

> **👉 [Try it now: open the input form](https://console.apify.com/actors/EPxqb34VntbJeRsrW/input)**

### Data licence

Map data © [OpenStreetMap contributors](https://www.openstreetmap.org/copyright), available under the Open Database License (ODbL) 1.0. Every row has `attribution`, `license` and `licenseUrl`. Keep the credit when you publish the data.

> **👉 [Try it now: open the input form](https://console.apify.com/actors/EPxqb34VntbJeRsrW/input)**

### Related Actors

- [Website Contact Scraper](https://apify.com/cliqtomedia/website-contacts-scraper) — add emails and social links from the `website` of each place.
- [Phone Number Validator & Formatter](https://apify.com/cliqtomedia/phone-number-validator) — check the `phone` values and turn them into one E.164 format.
- [Bulk Email Verifier](https://apify.com/cliqtomedia/email-verifier) — check the `email` values before outreach.
- [Website Tech Stack Detector](https://apify.com/cliqtomedia/tech-stack-detector) — see which CMS or shop software each place website uses.

> **👉 [Try it now: open the input form](https://console.apify.com/actors/EPxqb34VntbJeRsrW/input)**

### Support

Use the **Issues** tab of this Actor for bugs, questions and ideas. Add the run ID, your input and the `errorCode` or `statusReason`. Do not send API tokens or other private data.

> **👉 [Try it now: open the input form](https://console.apify.com/actors/EPxqb34VntbJeRsrW/input)**

# Changelog

This Actor's version history is a separate document: https://apify.com/cliqtomedia/openstreetmap-poi-extractor/changelog.md

# Actor input Schema

## `area` (type: `string`):

Where to search. A place name resolved by the OpenStreetMap geocoder (city, district, region: Pattaya, Berlin Mitte, Lisbon, Portugal) or a bounding box as south,west,north,east in decimal degrees (12.90,100.85,12.98,100.93), up to 5° per side. A named place becomes its OSM boundary, so only POIs inside the boundary are returned. Required.

## `tags` (type: `array`):

1–20 OpenStreetMap tag filters, one per line: key=value (amenity=cafe, shop=supermarket, tourism=hotel), key=\* or key for any value (shop=\*), or key~regex for a value pattern (amenity~^(cafe|restaurant)$). Common keys: amenity, shop, tourism, leisure, office, craft, healthcare. See the README for a tag cheat sheet.

## `match` (type: `string`):

any (default): a POI matches if it has at least one of the tags (union, e.g. cafes or restaurants). all: a POI must have every tag (e.g. amenity=restaurant plus cuisine=thai).

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

Maximum number of POI rows, 1–10000. Default 100. Each POI row is charged; the run summary tells you when the area holds more POIs than this limit.

## `includeTags` (type: `boolean`):

Add the full OSM tag object to every row (all keys the mappers recorded: cuisine, brand, wheelchair, levels, etc.). Turn off for a smaller, flat Dataset. Default on.

## Actor input object example

```json
{
  "area": "Pattaya",
  "tags": [
    "amenity=cafe"
  ],
  "match": "any",
  "maxItems": 20,
  "includeTags": true
}
```

# Actor output Schema

## `pois` (type: `string`):

One row per point of interest as a table.

## `runSummary` (type: `string`):

Status, resolved area, counts, truncation flag, data freshness and request statistics.

# 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 = {
    "area": "Pattaya",
    "tags": [
        "amenity=cafe"
    ],
    "maxItems": 20
};

// Run the Actor and wait for it to finish
const run = await client.actor("cliqtomedia/openstreetmap-poi-extractor").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 = {
    "area": "Pattaya",
    "tags": ["amenity=cafe"],
    "maxItems": 20,
}

# Run the Actor and wait for it to finish
run = client.actor("cliqtomedia/openstreetmap-poi-extractor").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 '{
  "area": "Pattaya",
  "tags": [
    "amenity=cafe"
  ],
  "maxItems": 20
}' |
apify call cliqtomedia/openstreetmap-poi-extractor --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,cliqtomedia/openstreetmap-poi-extractor"
        }
    }
}
```

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/EPxqb34VntbJeRsrW/builds/XwzzvQib2zJXuNyN4/openapi.json
