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
11
Total users
9
Monthly active users
10 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.
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. |
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, comma-separated. |
viewbox | string | — | minLon,minLat,maxLon,maxLat, e.g. Almaty 76.75,43.15,77.10,43.40. |
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.001 per run start + $0.001 per result row (not-found rows are included only when you turn emitNotFound on; failed queries and entries that were not sent are never charged). Bronze −10 %, Silver −20 %, Gold and above −30 % on the row price; the start event is the same on every plan; platform usage is included. 100 addresses at limit: 1 ≈ $0.10; a bounded POI search returning 40 rows ≈ $0.041.
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 stops sending to a server that refuses requests (401/403, or 429 after retries) or fails twice in a row (those entries are listed in
SUMMARY.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. About 700 entries fit in the default 15-minute timeout (1.1 s each). 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 → the run fails and the reason is in the status message. - 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