Last week, MapQuest briefly became the most-downloaded app in America. TechCrunch reported it hit No. 1 on the U.S. App Store after it refused to follow an August 27 executive order renaming Lake Ontario as “Lake America.” Google and Apple quietly adopted the change. MapQuest said no.
Whatever you make of the politics, the story exposed something most of us never think about: the place names on your map are just rows in a database, updated by whoever controls the pipeline. And that is exactly where OpenStreetMap differs. The underlying data is not owned by one company. It is crowd-sourced, openly licensed, and best of all, queryable by anyone.
This tutorial is about the tool that does the querying: the Overpass API. No API key, no account, no billing form. Just you, curl, and a query language that turns the whole planet’s map into a database you can ask questions of. I tested every command below against the live API on a stock Ubuntu box.

What the Overpass API actually is
OpenStreetMap’s main API (api.openstreetmap.org) exists for editing and stays deliberately small. The Overpass API is the read-only analytical engine. You send a query, and it returns exactly the elements that match, filtered by tags, location, proximity, or all three.
There is no token and no signup. The official wiki lists several free public instances; the main one at overpass-api.de is the default. It asks you to send a custom User-Agent header so operators can identify your app. Do that.
The data behind it is ODbL licensed — free for most uses. If you build something commercial, read the attribution rules first. They are short and reasonable.
Your first query: cafes near a point
The workhorse of Overpass is the around: filter, which grabs everything within a radius of a coordinate. Here is a request that finds cafes within 500 meters of Berlin’s Alexanderplatz:
curl -s -G "https://overpass-api.de/api/interpreter" \
-H "User-Agent: my-bleuken-tutorial/1.0" \
--data-urlencode 'data=[out:json][timeout:60];node["amenity"="cafe"](around:500,52.5219,13.4132);out center;'
Run that and you get a JSON array. When I ran it, it returned 28 cafes, including Segafredo, Tudo, Blixen, Espresso House, and Coffee Fellows. Let me unpack the query, because every piece will recur in every query you write:
[out:json]— output format. Use[out:csv(...)]for spreadsheets.[timeout:60]— give the server up to 60 seconds to finish. Complex queries need more.node[...]— the element type. Useway,relation, or the catch-allnwr.["amenity"="cafe"]— the tag filter. This is the whole game: map objects are described by key/value tags.(around:500,52.5219,13.4132)— search within 500 meters of that latitude, longitude.out center;— return coordinates. For nodes this gives lat/lon directly; for ways and relations it returns a representative center point.
One trap that bites everyone: a bounding-box filter uses south, west, north, east order, not north-first. Get the order wrong and your query silently returns nothing, which is the most confusing no-data bug Overpass has.
Query inside a city boundary with Nominatim
around: is great for a point, but what if you want “every pharmacy in a city”? You geocode the city once, then run your search inside its boundary.
First get the city’s OpenStreetMap relation with Nominatim, the geocoder that powers OSM’s search:
curl -s "https://nominatim.openstreetmap.org/search?q=Roxas+City,+Capiz,+Philippines&format=jsonv2&limit=1"
The response includes osm_type and osm_id. For Roxas City I got relation 3477345. Then the Overpass area filter needs a magic number: 3600000000 + the relation id, which gives 3603477345. Now your query becomes:
[out:json][timeout:60];
area(3603477345)->.a;
nwr["amenity"="pharmacy"](area.a);
out center;
That returned 17 pharmacies in my hometown, including the Mercury Drug stores and a couple of local names like Grace Pharmacy and Farmacia Benjamin. Useful when you want to open a shop and check the competition, or audit coverage before planning a route.
Two notes. First, area queries are noticeably slower than around: — this one took about 28 seconds on the live server. Cache the area id once you have it. Second, respect Nominatim’s usage policy: it is a shared service, so keep your request rate low and include a proper User-Agent.
CSV output and a real data-quality check
When you want the results in a spreadsheet, switch the output format. This returns rows with id, type, coordinates, name, and amenity, separated by pipes:
[out:csv(::id,::type,::lat,::lon,name,amenity;true;"|")]
node["amenity"="cafe"](around:300,52.5219,13.4132);
out;
The ;true;"|" means “include the header row, and use | as the separator.”
Here is where Overpass starts paying for itself: you can aggregate what the map actually contains instead of trusting what an app chooses to show you — the same instinct behind measuring local LLMs with real numbers instead of hype. I pulled every electric-vehicle charging station in central Berlin and totalled the operator tag with a few lines of Python:
from collections import Counter
rows = open("chargers.csv").read().strip().split("\n")
ops = Counter(r.split("|")[1] for r in rows[1:])
print(ops.most_common(5))
My run found 1,156 charging-station nodes in that bounding box. The top brands were Berliner Stadtwerke (192), Allego (161), Qwello (113), and EZE (50). But 185 rows had an empty operator tag — nobody says who runs a sixth of the chargers. That is a live data gap you can find in thirty seconds, and it is exactly the kind of thing a business would want to know before staking a route or a partnership on the map.
Read the edit history: provenance is built in
Every element in OpenStreetMap carries its own provenance, and you can read it with out meta;. This finds the Berlin “Brandenburger Tor” station node and shows who changed it last:
[out:json][timeout:60];
nwr["name"="Brandenburger Tor"](52.50,13.36,52.53,13.40);
out meta center;
When I ran it, node 30353086 (the S-Bahn station) came back at version 41, last touched on 2026-03-30 by user Patrick1977Bln, in changeset 180650403. I looked that changeset up — 158 changes, and its comment reads: “Aktualisierung Betreiber DB Netz AG -> DB InfraGO AG”. The rail operator for that station changed nine months ago, and the whole trail is public. That transparency is the quiet superpower of crowd-sourced data.
Even better, the main Overpass instance supports historical queries. Pin a date and ask what the map looked like then:
[out:json][timeout:60][date:"2026-01-01T00:00:00Z"];
node(30353086);
out meta;
That returned version 40, from 2023-01-21. So I can prove the node stood at v40 at the start of this year and moved to v41 in March — nobody is taking a label’s word for it, which brings me back to the map-naming story with a wink.
Turn results into GeoJSON
If you are feeding a mapping library, convert the Overpass JSON to GeoJSON. For ways, request the full geometry with out geom;, then do the small translation in Python:
features = []
for el in results["elements"]:
if el["type"] == "way" and el.get("geometry"):
features.append({
"type": "Feature",
"id": f"way/{el['id']}",
"properties": el.get("tags", {}),
"geometry": {"type": "LineString",
"coordinates": [[p["lon"], p["lat"]] for p in el["geometry"]]},
})
geojson = {"type": "FeatureCollection", "features": features}
I tested that on a small set of primary roads and got a valid FeatureCollection of LineString features that plotted cleanly. For nested or heavily tagged data, the osmtogeojson library handles everything you would not want to hand-roll.
Etiquette, errors, and the rules of the road
Overpass is a shared, volunteer-run resource, and the servers are strict about it for good reason. The main instance’s usage policy is roughly 10,000 queries and 1 GB of downloaded data per day. I hit an HTTP 429 twice while testing this tutorial; the fix was to pause for about 30 seconds and slow my loop down. When you see 429, do exactly that.
A few more habits that will save you pain:
- Always send a descriptive User-Agent so the operators can reach you if you misbehave.
- Scope your query with a
(around:...)or a bounding box. A bare name search across the whole planet is slow and gets you timed out. - A 504 gateway timeout usually means your query was too heavy. Narrow the area, shorten
[timeout:], or use a precomputed area id. - Use Overpass Turbo to prototype a query visually before you script it.
- For bulk offline work, skip Overpass and download a regional extract from Geofabrik once.
- Never point scripts at the editing API for reads. Overpass is the read layer; the Overpass QL reference is your language guide.
Where all this leads
Once you can ask OpenStreetMap questions, a bunch of genuinely useful things get easy: a walkability audit of a neighborhood, a store-locator feed you actually control, a before-and-after diff of some area you care about, or just an honest answer to “how many pharmacies are really in my city?” None of it requires a vendor account or a credit card.
And if a map label ever starts to feel like someone else’s decision forced on you, you can now look under the hood yourself. The same instinct that made people push back when one company quietly decided what they see is at the heart of open data — the map, like the AI overviews I wrote about recently, is only as trustworthy as the source you can inspect. With Overpass, you can inspect all of it.
Query it, cross-check it with a tool of your own. That is the whole habit, and I’ve leaned on it repeatedly here — whether building a detector for AI-written text, or telling real signals from a phishing message. Dig, verify, and only then trust. When the label in question is a cafe rating, an API claim, or a whole lake, the answer is always the same: pull the data yourself and see.