OpenStreetMap Places Scraper: Geocoding & POI Search
Pricing
from $0.74 / 1,000 place scrapeds
OpenStreetMap Places Scraper: Geocoding & POI Search
Geocode addresses, reverse-geocode coordinates and harvest points of interest from OpenStreetMap. Address components, bounding boxes and raw OSM tags. No key.
Pricing
from $0.74 / 1,000 place scrapeds
Rating
0.0
(0)
Developer
Arman Hossain
Maintained by CommunityActor stats
0
Bookmarked
2
Total users
1
Monthly active users
13 days ago
Last modified
Categories
Share

Does three things with the world's open map data: turns addresses into coordinates, turns coordinates back into addresses, and harvests every point of interest inside an area you draw.
It talks to the two OpenStreetMap endpoints built for exactly these jobs over plain HTTP: Nominatim for geocoding, Overpass for bulk area queries. There is no browser, no proxy, no credentials, no account and no per-request billing from a commercial geocoder. Rate limiting is built in, not left to you.
Agent skill: SKILL.md
https://api.apify.com/v2/key-value-stores/t7YoTxpZEJOWvw4Ug/records/openstreetmap-places-scraper.md
What you get
| Field | What it holds |
|---|---|
query, queryType | The input that produced this row, and whether it was a search, a reverse or a poi lookup |
source | nominatim or overpass, whichever endpoint answered |
name, displayName | Object name, and the full human-readable label |
latitude, longitude | WGS84 decimal degrees |
osmType, osmId, osmUrl | The underlying node, way or relation and a link straight to it on openstreetmap.org |
class, type | OSM classification pair, for example amenity / cafe |
addressType, placeRank, importance | Nominatim's own ranking signals, useful for picking between candidate matches |
boundingBox | { south, north, west, east } extent of the object |
houseNumber, road, city, state, postcode, country, countryCode | Broken-out address components |
extraTags | Raw OSM tags: opening hours, website, phone, wheelchair access, brand, cuisine |
placeId | Nominatim's internal ID. null for Overpass rows, which have no equivalent |
scrapedAt | Run timestamp |
RUN_SUMMARY in the key-value store holds per-run counts, the filters you used, and any query that failed.
Use cases
- Bulk address geocoding. Put a customer address list in, get coordinates and standardised components out.
- Address validation. Compare what you hold against
displayNameand the broken-out components. - Store locators and competitor mapping. Sweep a city bounding box for
shop=supermarketoramenity=fuel. - Reverse geocoding telemetry. Turn GPS traces into street, city and postcode.
- Enriching a places database. Pull opening hours, phone and website from
extraTags.
Quick start
Geocode a few addresses:
{"queries": ["Brandenburg Gate, Berlin", "10 Downing Street, London"],"contactEmail": "you@example.com"}
Reverse-geocode coordinates and break out the components:
{"coordinates": ["48.8584,2.2945", "52.5163,13.3777"],"includeAddressDetails": true,"contactEmail": "you@example.com"}
Every cafe and bakery in central Berlin, with opening hours and phone numbers:
{"poiTags": ["amenity=cafe", "shop=bakery"],"areaBoundingBox": { "south": 52.50, "west": 13.36, "north": 52.53, "east": 13.42 },"includeExtraTags": true,"maxResults": 2000,"contactEmail": "you@example.com"}
Input
| Field | Type | Default | Notes |
|---|---|---|---|
queries | array | [] | Free-text addresses or place names. Sent to Nominatim /search. |
coordinates | array | [] | "lat,lon" strings. Sent to Nominatim /reverse. An unparseable pair fails the run immediately rather than silently dropping a row. |
poiTags | array | [] | Tag filters like amenity=cafe, or a bare key like amenity to match any value. Sent to Overpass. Requires areaBoundingBox. |
areaBoundingBox | object | none | { south, west, north, east } in degrees. Only read when poiTags is set. |
countryCodes | array | [] | Two-letter ISO codes restricting forward geocoding. Ignored for reverse and POI lookups. |
includeAddressDetails | boolean | true | Break out house number, road, city, state, postcode and country. |
includeExtraTags | boolean | false | Attach raw OSM tags. Always present on POI rows regardless. |
resultsPerQuery | integer | 1 | Candidate matches kept per forward-geocoding query. Nominatim's own ceiling is 40. |
maxResults | integer | 1000 | Cap on total places saved across the whole run. |
contactEmail | string | none | Goes into the User-Agent. Strongly recommended, for the reasons below. |
You can combine all three modes in one run. Results are de-duplicated on osmType and osmId, so a place found by both a geocode and a POI sweep is saved once.
Output example
{"query": "Brandenburg Gate, Berlin","queryType": "search","source": "nominatim","placeId": 143634177,"name": "Brandenburger Tor","displayName": "Brandenburger Tor, 1, Pariser Platz, Friedrich-Wilhelm-Stadt, Mitte, Berlin, 10117, Deutschland","latitude": 52.5162699,"longitude": 13.3777034,"osmType": "way","osmId": 518071791,"osmUrl": "https://www.openstreetmap.org/way/518071791","class": "building","type": "gate","addressType": "building","placeRank": 30,"importance": 0.5504719991549523,"boundingBox": { "south": 52.516117, "north": 52.5164328, "west": 13.3775798, "east": 13.3778251 },"houseNumber": "1","road": "Pariser Platz","city": "Berlin","state": null,"postcode": "10117","country": "Deutschland","countryCode": "de","extraTags": {"height": "26","tourism": "attraction","historic": "monument","wheelchair": "yes","opening_hours": "24/7","wikidata": "Q82425"},"scrapedAt": "2026-08-06T12:00:00.000Z"}
Country and city names come back in the local language: Deutschland, not Germany. countryCode is the stable field to key on.
Nominatim versus Overpass
The two endpoints answer different questions, and using the wrong one is how people get blocked:
| You want | Endpoint | Input to use |
|---|---|---|
| An address turned into coordinates | Nominatim /search | queries |
| Coordinates turned into an address | Nominatim /reverse | coordinates |
| Every object of a given kind in an area | Overpass | poiTags plus areaBoundingBox |
Nominatim's usage policy allows one request per second and explicitly asks that systematic and bulk queries go to Overpass instead. This Actor enforces the one-second gap in code, sends an identifying User-Agent on every request, and routes area sweeps to Overpass automatically. You do not have to remember any of that.
Overpass has no fixed per-second rate. It allocates query slots and will answer 429 or 504 when it is busy. Those are retried three times with linear backoff, and a 504 on the first attempt is normal for a large box and usually succeeds on the second.
API example
curl -X POST "https://api.apify.com/v2/acts/arman-bd~openstreetmap-places-scraper/run-sync-get-dataset-items?token=YOUR_TOKEN" \-H "Content-Type: application/json" \-d '{"queries": ["Brandenburg Gate, Berlin"],"includeAddressDetails": true,"contactEmail": "you@example.com"}'
JavaScript example
import { ApifyClient } from 'apify-client';const client = new ApifyClient({ token: 'YOUR_TOKEN' });const run = await client.actor('arman-bd/openstreetmap-places-scraper').call({poiTags: ['amenity=cafe'],areaBoundingBox: { south: 52.5, west: 13.36, north: 52.53, east: 13.42 },includeExtraTags: true,maxResults: 500,});const { items } = await client.dataset(run.defaultDatasetId).listItems();for (const place of items) {console.log(`${place.name ?? '(unnamed)'} at ${place.latitude}, ${place.longitude} (${place.extraTags.opening_hours ?? 'hours unknown'})`);}
Notes
- Nominatim gets one request per second, always. The gap is applied after every call including failed ones, so a run cannot burst past the policy even when queries are erroring.
- A
contactEmailis not decoration. OpenStreetMap uses the User-Agent to reach heavy users before blocking them, and without one you are anonymous traffic, which gets throttled first. The Actor logs a warning when it is missing. - Ways and relations get a centroid. Overpass returns no coordinates for non-node objects, so the query asks for
out centerand the centroid lands inlatitudeandlongitude.boundingBoxisnullfor POI rows because centroids carry no extent. - Overpass errors arrive as HTML. A malformed tag or a timeout produces an HTML page rather than JSON, so the Actor digs the real message out of the markup and reports it instead of throwing a parse error.
- One bad query won't kill the run. Failures land in
RUN_SUMMARY.failures, and the Actor only errors out if every query fails. - Public data only. OpenStreetMap data is published under the ODbL, and the attribution "© OpenStreetMap contributors" is required if you publish anything derived from it.
FAQ
Do I need an API key? No. You supply no credentials.
How fast can I geocode a list? One address per second, by policy. Ten thousand addresses is a little under three hours. If you need it faster, run your own Nominatim instance, because the public one is a volunteer service.
Why did my POI sweep return nothing? Either the box is empty, or the tag is wrong. Check the tag on the OpenStreetMap wiki: it is shop=supermarket, not amenity=supermarket.
Why did Overpass time out? The box was too large for the tag. Split it into quadrants and run them as separate queries, because a city-wide amenity=restaurant sweep is a lot of geometry.
Can I get opening hours and phone numbers? Yes, in extraTags, when mappers have recorded them. Coverage varies enormously by country and by category.
Can I use this commercially? Yes, under the ODbL: attribute OpenStreetMap and share alike if you redistribute a derived database. Read the licence before you build a product on it.