Building the New OSM Features API: A Planet-Scale Overpass Alternative
How we built a production OSM Features API on PostGIS: scaling the import to the full planet, the 1.5 TB storage footprint, the indexes that make popular-tag queries fast, and real measured performance against public Overpass.

Public Overpass servers are shared community infrastructure. They are great for prototypes, but at production traffic you hit timeouts, soft rate limits, and latency spikes that have nothing to do with your query. Running your own Overpass stack is possible, but it is operationally heavy: recursive query resolution, a bespoke schema, and a lot of infrastructure to babysit. Commercial mapping vendors solve the reliability problem, but usually force a proprietary category tree and SDK, so queries, tests, and AI-generated code stop being portable.
MapLark's OSM Features API is the practical middle path: a production HTTP API, backed by PostGIS, that keeps Overpass-style tag semantics (amenity=cafe, building=yes) and returns standard GeoJSON FeatureCollections (also FlatGeobuf, GeoParquet, CSV, and TSV) behind a stable /v2/osm_features endpoint. Here is how it is actually built, how it scaled to the full planet, and how it performs against public Overpass in practice.
What MapLark returns
The OSM Features API keeps OSM semantics intact and returns GeoJSON FeatureCollections by default so that you can feed straight into Leaflet, MapLibre, OpenLayers, or any geospatial toolchain. Other formats include FlatGeobuf, GeoParquet, CSV, and TSV. The translation layer is minimal:
nodePointopen wayLineStringclosed wayPolygonrelationMultiPolygon or grouped geometries like train stations with tracks.
You filter with the same tags mappers already use (amenity=cafe, building=yes, and so on). Knowledge from Overpass, osm2pgsql, or tagging docs transfers immediately.
Scaling the import to the full planet
The OSM ingestion pipeline runs osm2pgsql with a flex-output pass using lua scripts. Features are split by geometry into dedicated point, line, and polygon tables, each with a single geom column in EPSG:4326. The import can take between 24 to 128+ hours to complete, depending on the hardware, regions, and database schema.
What planet-scale storage actually costs: 1.5 TB
Our full planet import lands at roughly 1.5 TB in PostGIS at EPSG:4326. That number would be meaningfully higher with vector-tile serving turned on: Martin-style tile serving needs a second GiST index family on a 3857 projection of every geometry, which is a significant fraction of a terabyte on its own. That is why tile serving is off by default on the planet database.
Why popular-tag queries are fast
Two things make /v2/osm_features fast at planet scale, and both are ordinary PostGIS indexing, not magic:
- Spatial prefilter: every table has a
GIST(geom)index, so abboxoraroundquery narrows to the relevant slice of the planet before anything else runs. - Indexes tuned for common tags: high-traffic keys like
building,highway, andamenityget dedicated indexing so filters such astags=buildingortags=highway=cyclewaystay cheap after the spatial prefilter. Rarer tags remain queryable; we simply optimize hardest for the ones that show up in production traffic.
Render on a free basemap, then reason about the results
Because the response is plain GeoJSON, you can drop it straight onto a free basemap like OpenFreeMap or any MapLibre style with no paid tile vendor involved. You can even display it ontop of Google Maps or Apple Maps. The point of returning full geometry and tags instead of a tile is that your application code can then compute on the result, not just draw it. A few concrete examples, each with the actual query behind it:
Example Queries
Get building polygons
Fetch building footprints as full polygons with vertex rings instead of a generalized tile outline.
curl "https://api.maplark.com/v2/osm_features?bbox=18.06,59.33,18.08,59.34&type=way&shape=polygon&tags=building&limit=200" \
-H "Authorization: Bearer swHAvOmreIm_uew6eqbn1UbIsVQT6p8PRohmqmAJiu4"Routing through a city on bike paths
Pull the cycle-permitted path network as LineStrings and build a shortest-path graph client-side.
curl "https://api.maplark.com/v2/osm_features?bbox=18.05,59.31,18.10,59.33&type=way&shape=line&or_tags=highway=cycleway&or_tags=cycleway=lane&or_tags=cycleway=track" \
-H "Authorization: Bearer swHAvOmreIm_uew6eqbn1UbIsVQT6p8PRohmqmAJiu4"Finding cafes with opening hours
The API filters by tag existence and value, so it returns every cafe that carries an opening_hours tag.
curl "https://api.maplark.com/v2/osm_features?around=18.063,59.334,500&type=node&tags=amenity=cafe&tags=opening_hours" \
-H "Authorization: Bearer swHAvOmreIm_uew6eqbn1UbIsVQT6p8PRohmqmAJiu4"Counting trees in a city
Every natural=tree node in a bounding box comes back as a FeatureCollection; the count is just features.length, and large areas page through X-Next-Cursor.
curl "https://api.maplark.com/v2/osm_features?bbox=18.05,59.32,18.10,59.35&type=node&tags=natural=tree&limit=1000" \
-H "Authorization: Bearer swHAvOmreIm_uew6eqbn1UbIsVQT6p8PRohmqmAJiu4"MapLark vs Overpass: measured
Rather than quote fabricated numbers, we ran the same queries against the production MapLark API and the public overpass-api.de instance and checked that both returned the same features before comparing timing.
Buildings in a bbox
Cafes within 1.5 km
Captured 2026-08-08, single session, against the production MapLark API (demo key) and the public overpass-api.de instance. Row counts were confirmed to match between the two APIs for each query. MapLark stayed within a tight band across every run; Overpass swung more than 6x between two successful runs of the identical query, on top of an outright request timeout. Public Overpass load varies constantly, so run the commands below yourself for a live comparison rather than trusting a chart.
Try it yourself
Both commands below time themselves with curl's own -w flag, so the numbers are consistent whether you run them from bash, zsh, or PowerShell.
curl -s -o /dev/null -w "%{http_code} in %{time_total}s\n" \
"https://api.maplark.com/v2/osm_features?bbox=18.05,59.32,18.10,59.34&type=way&shape=polygon&tags=building=yes" \
-H "Authorization: Bearer swHAvOmreIm_uew6eqbn1UbIsVQT6p8PRohmqmAJiu4"curl -s -o /dev/null -w "%{http_code} in %{time_total}s\n" \
"https://overpass-api.de/api/interpreter" \
--data-urlencode 'data=[out:json][timeout:60];way["building"="yes"](59.32,18.05,59.34,18.10);out geom;' The demo key above is shared and free-tier rate limited. If you get a 429, that is the rate limit, not a bug. Sign up for your own free key and try again.
Using it from Overpass Turbo or Ultra
Use Overpass Turbo or Ultra to see results on a map. Ultra is a newer, community-built reimplementation by Daniel Schep, using MapLibre GL JS instead of Leaflet. Same story as Turbo: it sends your query to an Overpass API server and renders results on a map. Note that these are not separate backends: they are both browser IDEs that send your query to this same instance.
Try it out in Overpass Ultra and see the example code below.
---
type: javascript
---
export async function source() {
const res = await fetch(
"https://api.maplark.com/v2/osm_features?bbox={{wsen}}&type=node&tags=amenity=restaurant&limit=10000",
{ headers: { Authorization: "Bearer swHAvOmreIm_uew6eqbn1UbIsVQT6p8PRohmqmAJiu4" } },
);
if (!res.ok) {
throw new Error(`MapLark API returned ${res.status}: ${await res.text()}`);
}
return { type: "geojson", data: await res.json() };
}Migrating from Overpass
/v2/osm_features is designed so existing Overpass-style use cases map cleanly onto it: the same OSM tags, the same node/way/relation model, and geometry shapes you already know how to work with. What changes is the operational story with production SLA instead of shared community servers.
Ready to build?
Get unlimited free access during public preview.