Geocoding Scraper — Address ↔ Coordinates (OpenStreetMap) avatar

Geocoding Scraper — Address ↔ Coordinates (OpenStreetMap)

Pricing

from $0.70 / 1,000 result items

Go to Apify Store
Geocoding Scraper — Address ↔ Coordinates (OpenStreetMap)

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

Samat Makatov

Maintained by Community

Actor stats

0

Bookmarked

11

Total users

9

Monthly active users

10 days ago

Last modified

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, keep found, lat/lon, normalized postcode/city; with emitNotFound: true rows stay 1:1 with input.
  • Store / branch enrichment — reverseZoom: 10 turns delivery GPS points into city names for territory reports.
  • Local lead lists — queries: ["pharmacy"] + viewbox + bounded: true lists every mapped pharmacy in a district with street and house number; extraTags adds opening hours and websites where mapped.
  • Service-area maps — polygon: true gives district/city boundaries as GeoJSON for coverage checks.
  • Multilingual labels — lang: "ru" or nameDetails: true for Cyrillic / Kazakh / local names on the same object.
  • Dedupe & entity resolution — stable osmType+osmId, placeRank, importance and bestMatch to pick the right candidate among ambiguous names.

Input

FieldTypeDefaultNotes
queriesstring[]—Text address/place → forward; lat,lon → reverse; R62422 / N1,W2 → lookup.
structuredQueriesobject[]—{ amenity, street, city, county, state, country, postalcode, label } (Nominatim).
modeselectautoauto, forward, reverse, lookup.
engineselectnominatimnominatim or photon (structured & lookup always use Nominatim).
langstringenAccept-Language, e.g. ru, kk, de, ru,en; empty = en. Photon: en/de/fr only.
countryCodesstring—kz,ru — ISO-3166 alpha-2, comma-separated.
viewboxstring—minLon,minLat,maxLon,maxLat, e.g. Almaty 76.75,43.15,77.10,43.40.
boundedboolfalseOnly results inside viewbox (enables area POI search).
featureTypeselect—country, state, city, settlement.
layersmulti—address, poi, railway, natural, manmade.
limitint1Results per query, 1–50. Nominatim returns at most 40 (a higher value is lowered to 40 with a warning), Photon at most 50.
reverseZoomint1818 building · 17 street · 16 major street · 14 suburb · 12 town · 10 city · 8 county · 5 state · 3 country.
addressDetailsbooltrueaddress object + derived columns.
extraTagsboolfalseextraTags: website, opening_hours, wikidata, population…
nameDetailsboolfalsenameDetails: name:ru, name:kk, official_name…
polygonboolfalsegeojson outline.
polygonThresholdnumber0Simplification tolerance in degrees (0.005 ≈ city level).
dedupebooltrueNominatim result dedupe.
emitNotFoundboolfalseEmit 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.
outputFieldsstring[]—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.
maxItemsint10000Hard cap on rows (one entry gives up to limit rows).
osmTagstring—Photon: amenity:cafe, shop, !amenity:parking.
photonLayersmulti—Photon: house, street, locality, district, city, county, state, country, other.
biasLat, biasLonnumber—Photon: prefer results near this point.
nominatimUrlstring—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.
photonUrlstring—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

NominatimPhoton
Matchingexact tokens, structured fieldsfuzzy / prefix (typos OK)
Address breakdownfull (address object, 20+ keys)street, housenumber, district, city, state, postcode, country
Reverseyes, zoom-controlledyes (nearest object)
OSM id lookup, polygons, extra tags, name detailsyesno
Languageany (accept-language)en / de / fr
POI filterlayers, featureType, bounded boxosm_tag, layer, bbox, location bias
Policymax 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

zoomreturns
3country
5state
8county
10city
12town / borough
13village / suburb
14neighbourhood
16major streets
17major and minor streets
18building

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"
}
FieldDescription
query, mode, engineInput label (or structured label), detected mode, engine used.
rank, bestMatch, found1-based rank; rank 1 is bestMatch; found: false rows carry nulls.
placeId, osmType, osmId, osmUrlStable OSM identity (use osmType+osmId as key; placeId changes between Nominatim imports).
displayName, nameFull label and object name.
lat, lon, boundingBoxWGS84; bbox as [minLat, maxLat, minLon, maxLon] (Nominatim order).
category, type, addressType, placeRank, importanceOSM class/type, address level, 0–1 relevance.
country…houseNumberNormalized address columns; raw parts in address.
extraTags, nameDetails, geojsonOnly when requested.
structuredEcho 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 ApifyClient
client = 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.errors as 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 set nominatimUrl / 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, featureType or structuredQueries; inspect importance and rank when limit > 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 forced mode (e.g. an address while mode: "reverse") and structured queries with no address part (only label): 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