# OpenStreetMap Places Scraper (`scrapyx/osm-places-scraper`) Actor

Points of interest from OpenStreetMap by city, bounding box or radius. Overpass reports a timed-out query as HTTP 200 with an empty result, and gives ways no coordinates unless asked - both are handled, so an empty answer here means an empty area.

- **URL**: https://apify.com/scrapyx/osm-places-scraper.md
- **Developed by:** [Ibnu Adzim](https://apify.com/scrapyx) (community)
- **Categories:** Lead generation, Travel
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $1.26 / 1,000 results

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?

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

## OpenStreetMap Places Scraper

Points of interest from OpenStreetMap — by **city name**, **bounding box** or
**radius around a point**. No API key, no login.

### Why an empty result here means an empty area

Overpass reports a **timed-out or out-of-memory query as HTTP 200 with an
empty result set**, and puts the reason in a `remark` field most clients never
read:

```
HTTP 200
{"elements": [], "remark": "runtime error: Query timed out ..."}
```

A scraper that ignores that tells you *"there are no cafés in this area"* when
what actually happened is *"the query failed"* — and for POI data that lie is
convincing, because an empty area is a perfectly plausible answer.

This actor checks `remark` before it touches the results and reports a failed
query as a failure. If it hands you zero places, the area really is empty.

### Why the big places are actually in your results

A node in OSM carries a latitude and longitude. **A way — an outline — carries
neither**, only a list of node ids. And supermarkets, hospitals, hotels, malls
and campuses are mapped as ways far more often than as points: in one Berlin
box, hotels and supermarkets came back as **18 ways and 2 relations, and zero
nodes**.

Queried the obvious way, every one of those arrives as a plausible-looking row
with no location at all. This actor always asks Overpass for centre points, and
`coordinateSource` on each row tells you whether a coordinate is a surveyed
point or the centroid of an outline — the same thing for a shop, not the same
thing for a national park.

### Categories

Twenty presets, each a **union** of the tags that actually carry what you mean.
OSM splits things in ways nobody expects: "hotels" is `tourism=hotel` *and*
`hostel`, `guest_house`, `motel`, `apartment`; a pharmacy is
`amenity=pharmacy` but a chemist is `shop=chemist`. Picking one tag loses most
of your results.

`restaurants` · `cafes` · `fast_food` · `bars_pubs` · `hotels` · `shops` ·
`supermarkets` · `pharmacies` · `healthcare` · `education` · `banks_atms` ·
`fuel_charging` · `parking` · `attractions` · `offices` · `gyms_sports` ·
`places_of_worship` · `post_shipping` · `car_services` · `tourism_all`

Anything they miss goes in `customTags`: `amenity=veterinary` for an exact
match, or just `craft` to match any value of a key.

### Output

One `SEARCH_SUMMARY` per run and one `PLACE` row per POI, with 44 fields:

- `name`, `nameEn`, `brand`, `operator`
- `latitude`, `longitude`, **`coordinateSource`**, `hasCoordinates`
- `amenity`, `shop`, `tourism`, `office`, `leisure`, `cuisine`
- `address`, `street`, `houseNumber`, `postcode`, `city`, `state`, `country`
- `phone`, `website`, `email`, `openingHours`
- `wheelchair`, `outdoorSeating`, `takeaway`, `delivery`, `internetAccess`
- `osmType`, `osmId`, `osmKey`, `osmUrl`, `wikidata`, and the full raw `tags`

`osmKey` is `type/id` because a node and a way can share an id — the id alone
is not unique.

### The summary row tells you what you didn't get

- **`upstreamTotal`** — the true size of the result set, from Overpass's own
  count query, run *before* anything is downloaded. So a cap tells you what it
  left behind rather than quietly becoming the answer.
- `upstreamNodes` / `upstreamWays` / `upstreamRelations`
- `overpassQuery` — the exact query that ran, ready to paste into
  overpass-turbo.eu
- `connectionRetries` — Overpass sheds load by dropping TCP connections; the
  first attempt is often refused and the second succeeds in about a second.
  This is not an outage, and the count says how often it happened.
- `overpassSlotsReported` — the concurrency budget, read from Overpass's own
  status endpoint rather than assumed

### Known limits

- Overpass is **volunteer-funded infrastructure with a published budget of 2
  concurrent queries per IP**. Large areas take time; very large ones need to
  be split. The proxy is off by default deliberately — rotating addresses to
  get more slots takes capacity from a donated service.
- The public mirrors were measured **worse** than the primary instance
  (HTTP 500/502, 30-second timeouts, a certificate for the wrong host). They
  are a last resort, and `mirrorsUsed` says when one was needed.
- Unnamed features (benches, parking bays) are excluded by default — real OSM
  data, but noise in a places dataset. Turn `requireName` off to include them.
- Data quality is whatever local mappers contributed. Coverage is excellent in
  Europe and patchy elsewhere.

# Actor input Schema

## `place` (type: `string`):

A city, district or country — geocoded to a bounding box via Nominatim. Give a fuller name for a better match ('Berlin, Germany'). Use exactly one of place / boundingBox / centerPoint.

## `boundingBox` (type: `string`):

south,west,north,east — note the ORDER; it is not lat,lon pairs. Precise and free of any geocoding guess.

## `centerPoint` (type: `string`):

lat,lon — searches a radius around this point.

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

Used with Centre point. 1–50000.

## `categories` (type: `array`):

Each preset is a UNION of the OSM tags that actually carry the thing you mean — 'hotels' covers tourism=hotel, hostel, guest\_house, motel and apartment, because OSM splits them. This is where naive OSM queries lose most of their results.

## `customTags` (type: `array`):

For anything the presets miss. 'key=value' for an exact match, or just 'key' to match any value — e.g. 'amenity=veterinary', or 'craft' for every craft business.

## `osmTypes` (type: `array`):

Nodes are points. Ways and relations are outlines — supermarkets, hospitals, hotels and malls are mapped that way far more often than as points, so excluding them loses exactly the large POIs you probably want.

## `requireName` (type: `boolean`):

On by default. OSM contains vast numbers of unnamed features (benches, parking bays) that are real data but noise in a places dataset.

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

Set 0 for unlimited. The summary row reports the TRUE total from Overpass's own count query, so you can see what a cap left behind.

## `queryTimeout` (type: `integer`):

Sent to Overpass as its own server-side budget. Too low is the fastest way to get an empty result that LOOKS like an empty area — this actor detects that and reports it as a failure instead.

## `minRequestInterval` (type: `integer`):

0 uses the built-in default. Overpass publishes a budget of 2 concurrent queries per IP and this actor reads that number from its status endpoint.

## `proxyConfiguration` (type: `object`):

Off by default. Overpass is volunteer-funded and its slot budget is per IP — rotating addresses to get more of it would be taking capacity from a donated service.

## Actor input object example

```json
{
  "place": "Berlin, Germany",
  "boundingBox": "52.5100,13.3800,52.5300,13.4100",
  "centerPoint": "52.5200,13.4050",
  "radiusMeters": 1000,
  "categories": [
    "restaurants"
  ],
  "customTags": [
    "amenity=veterinary",
    "craft"
  ],
  "osmTypes": [
    "node",
    "way",
    "relation"
  ],
  "requireName": true,
  "maxResults": 500,
  "queryTimeout": 90,
  "minRequestInterval": 0,
  "proxyConfiguration": {
    "useApifyProxy": false
  }
}
```

# Actor output Schema

## `items` (type: `string`):

One row per scraped record. See the dataset's default view for field definitions.

# 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("scrapyx/osm-places-scraper").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("scrapyx/osm-places-scraper").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 scrapyx/osm-places-scraper --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,scrapyx/osm-places-scraper"
        }
    }
}
```

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/oYc09hUQHCHGDonhh/builds/h3zFUkYZT1zLuvCtj/openapi.json
