Geocoding Scraper — Address ↔ Coordinates (OpenStreetMap)
Pricing
from $0.70 / 1,000 result items
Geocoding Scraper — Address ↔ Coordinates (OpenStreetMap)
Free geocoding for agents and CRMs via OpenStreetMap: address or place → lat/lon with full address breakdown, structured queries (street/city/postcode), reverse geocoding with zoom levels, OSM id lookup, bounded POI search, boundary polygons, multilingual names. Nominatim or Photon (fuzzy).
Pricing
from $0.70 / 1,000 result items
Rating
0.0
(0)
Developer
Samat Makatov
Maintained by CommunityActor stats
0
Bookmarked
18
Total users
6
Monthly active users
11 days ago
Last modified
Categories
Share
Key-less geocoding on OpenStreetMap data for agents, CRMs and data pipelines. Forward (address or place → lat/lon + full address breakdown), structured queries (street/city/postcode fields for messy CRM data), reverse (lat,lon → address at the zoom level you need), OSM id lookup, bounded POI search inside a box, boundary polygons as GeoJSON and names in any language. Two engines: Nominatim (exact, rich) and Photon (typo-tolerant). Worldwide, no API key, no proxy.
Need it tailored to your workflow? Missing a field, a filter, another source or a different output format? Open an issue and describe what you need — we read every request and adapt the actor.
Use cases
- CRM address cleaning — geocode 500 customer addresses with
structuredQueries, keepfound,lat/lon, normalizedpostcode/city; withemitNotFound: truerows stay 1:1 with input. - Store / branch enrichment —
reverseZoom: 10turns delivery GPS points into city names for territory reports. - Local lead lists —
queries: ["pharmacy"]+viewbox+bounded: truelists every mapped pharmacy in a district with street and house number;extraTagsadds opening hours and websites where mapped. - Service-area maps —
polygon: truegives district/city boundaries as GeoJSON for coverage checks. - Multilingual labels —
lang: "ru"ornameDetails: truefor Cyrillic / Kazakh / local names on the same object. - Dedupe & entity resolution — stable
osmType+osmId,placeRank,importanceandbestMatchto pick the right candidate among ambiguous names.
Input
| Field | Type | Default | Notes |
|---|---|---|---|
queries | string[] | — | Text address/place → forward; lat,lon → reverse; R62422 / N1,W2 → lookup. Points are also read in other notations: 40.7488° N, 73.9854° W, 48°51'30"N 2°17'40"E, (51.5007, -0.1246), 48,8584; 2,2945, lat: 51.5 lon: -0.12, POINT(2.2945 48.8584) (lon lat) and Google Maps / openstreetmap.org links; the log and SUMMARY.readAsCoordinates show what each was read as. |
structuredQueries | object[] | — | { amenity, street, city, county, state, country, postalcode, label } (Nominatim). |
mode | select | auto | auto, forward, reverse, lookup. |
engine | select | nominatim | nominatim or photon (structured & lookup always use Nominatim). |
lang | string | en | Accept-Language, e.g. ru, kk, de, ru,en; empty = en. Photon: en/de/fr only. |
countryCodes | string | — | kz,ru — ISO-3166 alpha-2, separated by commas (or ;, ` |
viewbox | string | — | minLon,minLat,maxLon,maxLat, e.g. Almaty 76.75,43.15,77.10,43.40 (spaces, or ; with decimal commas, work too). |
bounded | bool | false | Only results inside viewbox (enables area POI search). |
featureType | select | — | country, state, city, settlement. |
layers | multi | — | address, poi, railway, natural, manmade. |
limit | int | 1 | Results per query, 1–50. Nominatim returns at most 40 (a higher value is lowered to 40 with a warning), Photon at most 50. |
reverseZoom | int | 18 | 18 building · 17 street · 16 major street · 14 suburb · 12 town · 10 city · 8 county · 5 state · 3 country. |
addressDetails | bool | true | address object + derived columns. |
extraTags | bool | false | extraTags: website, opening_hours, wikidata, population… |
nameDetails | bool | false | nameDetails: name:ru, name:kk, official_name… |
polygon | bool | false | geojson outline. |
polygonThreshold | number | 0 | Simplification tolerance in degrees (0.005 ≈ city level). |
dedupe | bool | true | Nominatim result dedupe. |
emitNotFound | bool | false | Emit found: false rows to keep 1:1 alignment. Such rows are charged like hits, so this is opt-in; not-found queries are always listed free in SUMMARY.notFoundInputs. |
outputFields | string[] | — | Projection in your order, e.g. ["query","lat","lon","countryCode"]; query is always included. Wrong case, aliases (lng, zip) and near misses are corrected with a warning; unknown names are ignored with a warning; a list with no known column fails the run and lists the columns. |
maxItems | int | 10000 | Hard cap on rows (one entry gives up to limit rows). |
osmTag | string | — | Photon: amenity:cafe, shop, !amenity:parking. |
photonLayers | multi | — | Photon: house, street, locality, district, city, county, state, country, other. |
biasLat, biasLon | number | — | Photon: prefer results near this point. |
nominatimUrl | string | — | Base URL of your own Nominatim server (the part before /search). No 1.1 s pace and no per-run cap there. Empty = nominatim.openstreetmap.org. |
photonUrl | string | — | Base URL of your own Photon server (the part before /api). Empty = photon.komoot.io. |
Off-list values of the select fields (mode, engine, featureType, layers, photonLayers) are refused by Apify before the run starts; the allowed values are the ones listed above.
Reference
Nominatim vs Photon
| Nominatim | Photon | |
|---|---|---|
| Matching | exact tokens, structured fields | fuzzy / prefix (typos OK) |
| Address breakdown | full (address object, 20+ keys) | street, housenumber, district, city, state, postcode, country |
| Reverse | yes, zoom-controlled | yes (nearest object) |
| OSM id lookup, polygons, extra tags, name details | yes | no |
| Language | any (accept-language) | en / de / fr |
| POI filter | layers, featureType, bounded box | osm_tag, layer, bbox, location bias |
| Policy | max 1 request/s, one at a time, identify the app, cache results (the actor keeps the pace, sends one request at a time with its own User-Agent, and requests a repeated entry of a run once; it keeps nothing between runs) | fair use (the actor uses the same pace) |
reverseZoom levels
| zoom | returns |
|---|---|
| 3 | country |
| 5 | state |
| 8 | county |
| 10 | city |
| 12 | town / borough |
| 13 | village / suburb |
| 14 | neighbourhood |
| 16 | major streets |
| 17 | major and minor streets |
| 18 | building |
Common type / category values
boundary/administrative (countries, regions, cities, districts) · place/city|town|village|suburb|neighbourhood · highway/residential|primary|… (streets) · building/yes|office|apartments|… · amenity/pharmacy|cafe|bank|school|… · shop/* · tourism/* · office/*. Full tag catalogue: https://wiki.openstreetmap.org/wiki/Map_features
Examples
Batch-geocode CRM addresses (1:1 rows)
{"structuredQueries": [{ "street": "Nauryzbai Batyr 154A", "city": "Almaty", "country": "Kazakhstan", "label": "Kaspi HQ" },{ "street": "Mangilik El 55", "city": "Astana", "country": "Kazakhstan", "label": "Client 2" }],"lang": "ru","emitNotFound": true,"outputFields": ["query", "found", "lat", "lon", "displayName", "postcode", "city", "osmUrl"]}
All pharmacies in a district (POI lead list)
{ "queries": ["pharmacy"], "viewbox": "76.85,43.20,77.00,43.30", "bounded": true, "limit": 40, "extraTags": true, "lang": "ru" }
GPS points → city names for reporting
{ "queries": ["43.2363,76.9457", "51.1282,71.4307", "42.3417,69.5901"], "reverseZoom": 10, "outputFields": ["query", "city", "state", "countryCode"] }
City boundaries as GeoJSON
{ "queries": ["Almaty", "Astana"], "featureType": "city", "countryCodes": "kz", "polygon": true, "polygonThreshold": 0.005 }
Fuzzy search of user-typed input (Photon)
{ "queries": ["Almty Nauryzbai Batyr 154"], "engine": "photon", "limit": 3, "biasLat": 43.24, "biasLon": 76.95 }
Output
{"query": "Nauryzbai Batyr 154A, Almaty","mode": "forward","engine": "nominatim","rank": 1,"bestMatch": true,"found": true,"placeId": 213728564,"osmType": "way","osmId": 51267846,"osmUrl": "https://www.openstreetmap.org/way/51267846","displayName": "Kaspi.kz, 154А, Nauryzbai Batyr Street, Керемет, Көктем, Bostandıq District, Almaty, 050013, Kazakhstan","name": "Kaspi.kz","lat": 43.2399856,"lon": 76.9388247,"category": "building","type": "office","addressType": "building","placeRank": 30,"importance": 0.0000743,"boundingBox": [43.2398836, 43.2400877, 76.9383802, 76.9392699],"country": "Kazakhstan","countryCode": "KZ","state": null,"county": null,"city": "Almaty","district": "Bostandıq District","suburb": "Көктем","postcode": "050013","road": "Nauryzbai Batyr Street","houseNumber": "154А","address": { "building": "Kaspi.kz", "house_number": "154А", "road": "Nauryzbai Batyr Street", "city": "Almaty", "ISO3166-2-lvl4": "KZ-75", "postcode": "050013", "country": "Kazakhstan", "country_code": "kz" },"extraTags": { "building:levels": "7" },"nameDetails": null,"geojson": null,"licence": "Data © OpenStreetMap contributors, ODbL 1.0. https://osm.org/copyright","sourceUrl": "https://nominatim.openstreetmap.org/search?q=…","fetchedAt": "2026-09-12T23:50:01.000Z"}
| Field | Description |
|---|---|
query, mode, engine | Input label (or structured label), detected mode, engine used. |
rank, bestMatch, found | 1-based rank; rank 1 is bestMatch; found: false rows carry nulls. |
placeId, osmType, osmId, osmUrl | Stable OSM identity (use osmType+osmId as key; placeId changes between Nominatim imports). |
displayName, name | Full label and object name. |
lat, lon, boundingBox | WGS84; bbox as [minLat, maxLat, minLon, maxLon] (Nominatim order). |
category, type, addressType, placeRank, importance | OSM class/type, address level, 0–1 relevance. |
country…houseNumber | Normalized address columns; raw parts in address. |
extraTags, nameDetails, geojson | Only when requested. |
structured | Echo of the structured query object (structured rows only). |
The dataset schema describes every column (type, title, description, example) for agents and MCP clients.
SUMMARY key-value record: { queries, found, notFound, rows, errors[{query, error}], engine, errorCount, notFoundCount, notFoundInputs[], notFoundRowsEmitted, notSentOverCap, notSentRefused, notProcessed, notProcessedInputs[], requests{server: n}, reusedAnswers, refusedBy, warnings[], delivered, stoppedBy, status } — notProcessedInputs lists (up to 100) entries the run did not reach (spending limit, timeout or maxItems), so you can run just those again.
Use it from code / agents
curl -X POST "https://api.apify.com/v2/acts/yadroo~osm-geocode/run-sync-get-dataset-items?token=$APIFY_TOKEN" \-H 'Content-Type: application/json' -d '{"queries":["Almaty, Kazakhstan","43.2363,76.9457"],"lang":"ru"}'
import { ApifyClient } from 'apify-client';const client = new ApifyClient({ token: process.env.APIFY_TOKEN });const run = await client.actor('yadroo/osm-geocode').call({ structuredQueries: [{ street: 'Nauryzbai Batyr 154A', city: 'Almaty', country: 'Kazakhstan' }] });const { items } = await client.dataset(run.defaultDatasetId).listItems();
from apify_client import ApifyClientclient = ApifyClient(os.environ["APIFY_TOKEN"])run = client.actor("yadroo/osm-geocode").call(run_input={"queries": ["pharmacy"], "viewbox": "76.85,43.20,77.00,43.30", "bounded": True, "limit": 40})items = client.dataset(run["defaultDatasetId"]).list_items().items
MCP: connect https://mcp.apify.com and call the yadroo/osm-geocode tool with the same JSON.
Pricing
Pay per event: $0.005 per run start + $0.002 per result row (from 5 November 2026; until then $0.001 + $0.001) (not-found rows are included only when you turn emitNotFound on; failed queries and entries that were not sent are never charged). Bronze −5 %, Silver and above −10 % on the row price (from 5 November 2026; until then −10 / −20 / −30 %); the start event is the same on every plan; platform usage is included. From 5 November: 100 addresses at limit: 1 ≈ $0.21; a bounded POI search returning 40 rows ≈ $0.085.
The run respects your maximum cost per run: it stops when the next row would go over it and ends with "Stopped at your spending limit: N rows delivered".
Limits & FAQ
- Usage policy and speed. The public Nominatim server allows at most 1 request per second, from one machine, one request at a time, with no parallel or distributed jobs, and asks bulk users to cache results; Photon asks for fair use. The actor starts one request every 1.1 s per server, requests a repeated entry only once per run (repeats reuse the first answer while the kept answers fit in 16 MB, beyond that a repeat is asked again; nothing is kept between runs), sends at most 1,000 requests per run to each public server (a migrated or restarted run continues the count), and slows down when a server throttles: after a 429 (too many requests from this Apify server — other users share its IP), a 5xx or a timeout it pauses 30 s, 60 s, then 120 s and continues at half the pace each time (down to one request per 4.4 s); the log says so and
SUMMARY.pausesAfterThrottlingcounts it. It stops sending to a server that answers 401/403, that keeps throttling after these pauses, or that refuses from the very first request (then after one 20 s pause, so the run ends within a minute) — those entries are listed inSUMMARY.errorsas not sent, never charged. Do not start several runs in parallel against the public servers. For big batches, run your own Nominatim or Photon and setnominatimUrl/photonUrl: no pace and no per-run cap there. - Capacity per run. The default 20-minute timeout fits about 1,000 entries — the per-run cap — at 1.1 s each while the server does not throttle. When a list is longer, the log says so at the start (how many fit and the timeout that would fit them), and a bigger batch logs a progress line every 15 s (entries done, rows saved, time left) — the run is working, not stuck. The run stops starting new requests shortly before its timeout, keeps every row it has, ends SUCCEEDED with "Stopped before the run timeout: N rows saved" and lists the entries it did not reach in
SUMMARY.notProcessedInputs. A longer list: raise the timeout (up to the 1,000-request cap), or run the rest later, one run after another. - Accuracy. OSM coverage varies: exact house numbers exist for most cities in Europe/CIS/US; rural addresses may resolve to the street or village (check
addressType/placeRank). - Ambiguity. Use
countryCodes,viewbox,featureTypeorstructuredQueries; inspectimportanceandrankwhenlimit > 1. - Errors. HTTP 429/5xx and timeouts → up to 3 attempts with backoff; a failing query is listed in
SUMMARY.errors(no row, not charged) and the run continues. This also covers queries that do not fit a forcedmode(e.g. an address whilemode: "reverse") and structured queries with no address part (onlylabel): they are reported per query, the rest of the batch still runs. Nothing fetched at all (for example the public server refuses this Apify server from the first request) → the run fails within about a minute, uncharged, and the status message gives the reason once with what to do. - Licence. Data © OpenStreetMap contributors, ODbL — attribution required when you display it; Nominatim results may not be used for autocomplete-as-you-type (use Photon for that).
- Restarts. If Apify migrates or restarts the run, it continues where it stopped; rows already saved are not saved or charged again.
- Roadmap. Distance/nearest computation between rows.
Made by Yadroo · Sibling actors: open-meteo-weather · ip-intel · public-holidays · krisha-kz