# Plaza API > Geospatial data API serving the complete OpenStreetMap planet dataset. Plaza provides geocoding, routing, search, isochrones, elevation, map matching, route optimization, and raw OSM element queries — all from a single API. Spatial data is indexed using a proprietary H3 hexagonal tiling system for fast lookups at any scale. **Authentication**: Pass your API key via `Authorization: Bearer `, `x-api-key: ` header, or `api_key=` query parameter. **Response format**: Spatial endpoints return GeoJSON (`application/geo+json`). Paginated responses put metadata in HTTP headers (`X-Limit`, `X-Has-More`, `X-Next-Cursor`, `Link`), not the body. ## Endpoints - Geocoding: Forward and reverse geocoding, autocomplete, and batch - Routing & Directions: Point-to-point directions, isochrones, distance matrices, turn-by-turn - Place Search: Full-text search across all OSM entities - Spatial Predicates: Query features by spatial relationship: near, within, intersects, contains, crosses, touches (and negations) - Output Shaping: Control response geometry: simplify, buffer, centroid, precision, field filtering, computed extras (bbox, distance, center) - JSON Requests: Query endpoints take a POST with a JSON body; feature lookups by ID, tiles and optimize job status are GET - Geometry Input: All spatial inputs are GeoJSON geometry objects (RFC 7946), coordinates in [longitude, latitude] order - PlazaQL: Hosted PlazaQL for complex spatial queries against the full OSM dataset - Elevation: Point lookups and path elevation profiles - Map Matching: Snap GPS traces to the road network - Route Optimization: Best visiting order for 2 to 50 waypoints for one vehicle, with optional time windows, service times and capacity - EV Routing: Range-aware directions with charging stop planning - Departure-Time Routing: ETAs for a given departure time from hourly weekday/weekend speed profiles per road class and urban density (no live traffic data) - Reverse Geocoding: Convert coordinates to human-readable addresses - Autocomplete: Type-ahead address suggestions with sub-100ms latency - Distance Matrix: Travel times and distances between multiple origins and destinations - Isochrone: Areas reachable from a point within one or more travel-time budgets - Vector Tiles: Mapbox Vector Tiles of OSM in the Shortbread schema at GET /api/v1/tiles/{z}/{x}/{y}, filtered per request by tags, layers, properties and element types (PlazaQL: search(...).tile(z, x, y)); MapLibre style at /api/v1/tiles/style.json and TileJSON at /api/v1/tiles.json need no API key - Custom Datasets: Upload GeoJSON feature collections, query alongside OSM data via REST or PlazaQL dataset() source ## Pricing - Free: 500 standard/day + 50 premium/day, no credit card required - Standard rate: $0.50/1K requests - Premium rate: $1.50/1K requests - Premium endpoints count as 4x against included allocation - Custom data storage: Free 1MB, Pro 100K 1GB, Pro 300K 5GB, Pro 1M 20GB, overage $5/GB/month - Free (free): $0/mo, 500 std/day + 50 prm/day included, 10 req/min - Pro (pro_100k): $50/mo, 100K included, 300 req/min - Pro (pro_300k): $150/mo, 300K included, 1000 req/min - Pro (pro_1m): $500/mo, 1M included, 2000 req/min - Enterprise (enterprise): Custom/mo, 100M included, 10000 req/min ## Key Facts - Data: OpenStreetMap (ODbL) — cache and redistribute with attribution - Formats: GeoJSON (default), NDJSON and CSV streaming via the format parameter, Mapbox Vector Tiles - Query languages: REST and PlazaQL - 8 billion nodes, 1 billion ways, 15 million relations - Coverage: First region: North America, loading now. More regions on request. https://plaza.fyi/coverage ## Links - Docs: https://docs.plaza.fyi - API reference: https://docs.plaza.fyi/api - OpenAPI spec (JSON): https://plaza.fyi/api/openapi - Pricing: https://plaza.fyi/pricing - Coverage: https://plaza.fyi/coverage - Playground: https://plaza.fyi/playground ## SDKs - TypeScript, Python and Go SDKs are generated from the OpenAPI spec above. - Until they are published, point any OpenAPI client generator at https://plaza.fyi/api/openapi, or call the REST API directly with an API key in the x-api-key header. ## API Reference ### POST /api/v1/centroid > Find the least-cost meeting point for several locations Returns the point on the road network that minimises the total travel time (or distance) from every location, with each location's travel time and distance to it. Each location travels to the meeting point, so one-way streets count in that direction, and so do OSM turn restrictions. Locations must lie within 2 degrees of each other, and no location's trip may exceed 2 hours (150 km with `metric: distance`). Counts as one premium request. PlazaQL: `centroid(locations: [...], mode:, metric:, exclude:, avoid_datasets:)`. ### GET /api/v1/datasets > List datasets Lists datasets the caller can query: Plaza-curated datasets (synced from public sources, with license, vintage, refresh schedule and last sync) and the caller's own uploads. Filter with `scope=plaza` or `scope=user`. Parameters: scope, include_pending ### POST /api/v1/datasets > Create a new dataset Creates an empty user-owned dataset. Upload GeoJSON to it with `POST /datasets/{dataset_id}/upload` or add features individually. Counts against the plan's dataset limit. ### GET /api/v1/datasets/{dataset_id}/features > List features in a dataset Lists features of a visible dataset in feature id order. When more features follow, the response carries an opaque `next_cursor` (in the body and the `X-Next-Cursor` header); pass it as `cursor` to fetch the next page. A cursor from another dataset or request is a 400 `invalid_cursor`. `format=geojson`, `ndjson` or `csv` stream the page with chunked transfer encoding, reading the features from the database as they are sent; the next cursor is then in the `X-Next-Cursor` header only. PlazaQL: `search(dataset("slug"))` on POST /query takes the same `format`. Parameters: dataset_id, limit, cursor, format ### POST /api/v1/datasets/{dataset_id}/features > Add features to a dataset Adds one or more GeoJSON features to a dataset you own. Each feature needs a unique `id`. Parameters: dataset_id ### GET /api/v1/datasets/{dataset_id}/features/{id} > Get a single feature Returns one feature of a visible dataset by its feature id. Parameters: dataset_id, id ### DELETE /api/v1/datasets/{dataset_id}/features/{id} > Delete a feature Deletes one feature from a dataset you own. Parameters: dataset_id, id ### PATCH /api/v1/datasets/{dataset_id}/features/{id} > Update a feature Updates the geometry and/or properties of one feature in a dataset you own. Parameters: dataset_id, id ### POST /api/v1/datasets/{dataset_id}/upload > Upload data into a dataset Uploads a GeoJSON file (multipart `file`) into a dataset you own. `mode` is `replace` (atomic swap), `merge` (upsert by feature id) or `append`. Processing is asynchronous: the response returns the dataset in `processing` status; poll `GET /datasets/{id}` until it is `ready` or `error`. Parameters: dataset_id ### GET /api/v1/datasets/{id} > Get dataset by ID Returns metadata for one dataset: counts, storage, detected schema and processing status. Plaza-curated datasets also carry source metadata and the most recent sync. Parameters: id ### DELETE /api/v1/datasets/{id} > Delete a dataset Permanently deletes a dataset you own and all its features, addresses and routing edges. Plaza-curated datasets cannot be deleted. Parameters: id ### PATCH /api/v1/datasets/{id} > Update a dataset Updates the name, description or strict mode of a dataset you own. Plaza-curated datasets are read-only. Parameters: id ### GET /api/v1/datasets/{id}/dictionary > Get a dataset's field dictionary Describes each property a dataset's features carry: type, description and, for Plaza-curated datasets, the source field it comes from. Parameters: id ### GET /api/v1/datasets/{id}/syncs > List a dataset's source sync history Most recent syncs of a Plaza-curated dataset from its upstream source, newest first. Empty for user datasets. Parameters: id, limit ### POST /api/v1/elevation > Look up elevation at one or more points Returns ground elevation in meters for one point or a batch of points, sampled from the Copernicus 30m DEM. Parameters: format ### POST /api/v1/elevation/profile > Elevation profile along coordinates Samples elevation along a line and returns the profile with total ascent, descent, and min/max elevation. ### POST /api/v1/expansion > Show the road edges the router visited (debug) Runs a route, isochrone or matrix search and returns every road edge it settled as a GeoJSON FeatureCollection, with the computed path marked `on_path`. For debugging why a route was chosen. Requires the `X-Plaza-Debug: true` header or an API key with the `admin` scope (403 `debug_access_required` otherwise), and is limited to 10 requests per minute on every plan, on top of the plan's rate limit. Counts as one premium request. Responses are capped at 50,000 edges and 10 MB of geometry. PlazaQL: `expansion(action: ..., ...)`. Parameters: X-Plaza-Debug ### POST /api/v1/features > Query features by spatial predicate, bounding box, or H3 cell Returns OSM features that match the spatial predicates in the request body (`around`, `within`, `intersects`, and the others listed on SpatialPredicate; several predicates all apply), optionally filtered by element type. Results are ordered by `output.sort` (`osm_id` by default; `name`, `qt`, or `distance` from an `around` point) and keyset-paginated: when more results follow, the response carries `next_cursor` (body and `X-Next-Cursor` header), which you send back as `cursor` with the same request. With `datasets` in the body (for example `["census-counties"]` or `["plaza"]`), the request queries those datasets instead, the REST twin of PlazaQL `search(dataset(...))`: the spatial predicate becomes optional (unless `type` also adds OSM element types), `tags` filter feature properties, results page with the same `cursor`, and `output` can be `count` or an aggregate (`sum`/`min`/`max`/`avg` with `expr`, optional `group_by`, and `weight=overlap_fraction` with `sum`). Aggregates answer `{value}`, grouped results `{groups, total}`, counts `{counts, total}`. Parameters: type, h3, limit, cursor, offset, format ### POST /api/v1/features/batch > Fetch multiple features by type and ID Fetches up to 100 features in one request by type and OSM ID. IDs that do not exist are left out of the FeatureCollection instead of failing the request. ### GET /api/v1/features/{type}/{id} > Get feature by type and ID Returns one OpenStreetMap node, way, or relation as a GeoJSON Feature. Ways and relations include their assembled geometry. Parameters: type, id ### POST /api/v1/geocode > Forward geocode an address Converts an address or place name into ranked GeoJSON Features with coordinates and structured address fields. Parameters: format ### POST /api/v1/geocode/autocomplete > Autocomplete a partial address Suggests completions for a partial address or place name as the user types. Parameters: format ### POST /api/v1/geocode/batch > Batch geocode multiple addresses Geocodes a list of addresses in one request. Each input returns its own result list, in input order. Each address counts as one standard request (one request against the per-minute rate limit). PlazaQL: `geocode(addresses: [...])`. ### POST /api/v1/geocode/reverse > Reverse geocode a coordinate Finds the addresses and places closest to a coordinate, nearest first. Parameters: format ### POST /api/v1/geocode/reverse/batch > Batch reverse geocode multiple coordinates Reverse geocodes up to 1000 points in one request. Each point returns its own FeatureCollection of nearest addresses and places, in input order; a point with nothing nearby gets an empty collection. `radius`, `limit`, `layer` and `lang` apply to every point, as on POST /geocode/reverse; `include` and `datasets` are single-point only. Each point counts as one standard request. PlazaQL: `reverse_geocode(points: [...])`. ### POST /api/v1/geocode/structured > Geocode structured address components Geocodes pre-parsed address components (street address, locality, region, postal code, country) without free-text parsing. Results are ranked by how many components they match. When no address matches, falls back to the street, postal code, then the most specific admin area given (`match_type: "fallback"`). PlazaQL: `geocode(address: ..., locality: ..., region: ...)`. Parameters: format ### POST /api/v1/isochrone > Calculate an isochrone from a point Returns the area reachable from a point for each travel-time budget in `time`, or each distance along the roads in `contours_meters`, one GeoJSON polygon per budget, for the chosen travel mode. The search obeys the OSM turn restrictions that bind the mode at a single junction, as routes do; restrictions over a via way are not applied to isochrones. With `depart_at`, motor modes are timed by that hour's traffic profile and conditional road limits apply then, as on POST /route. `mode: transit` combines walking with public transit from the GTFS feeds Plaza has loaded, leaving at `depart_at`. Parameters: format ### POST /api/v1/map-match > Match GPS coordinates to the road network Snaps a noisy GPS trace to the road network and returns the matched route geometry and per-point tracepoints. With `mode` (and that mode's `truck`, `bicycle`, `pedestrian`, `motorcycle` or `motor_scooter` options) only roads that mode may use are candidates, so a scooter's trace never snaps to a motorway; without it any road matches. `datasets` lets the trace match the roads of your datasets too; a matched dataset road is named by its `name` property. `avoid_datasets` keeps it off roads that touch the polygons of the named datasets. PlazaQL: `map_match(points: [...], mode:, radiuses:, exclude:, datasets:, avoid_datasets:)`. ### POST /api/v1/matrix > Calculate a distance matrix between points Computes travel durations and distances between every origin and every destination. Routes obey OSM turn restrictions and pay the same turn costs as `POST /route` does; a pair reachable only through a prohibited turn has no route, and turn costs choose the route but never add to `duration_s`. With `depart_at`, motor modes are timed by that hour's traffic profile and conditional road limits apply then, as on POST /route. ### POST /api/v1/nearest > Snap a coordinate to the nearest road Snaps a coordinate to the closest point on the routable road network and returns the snapped point and its distance. ### POST /api/v1/optimize > Optimize route through waypoints Solves the visiting order for 2 to 50 waypoints for one vehicle, from travel times that obey OSM turn restrictions. Without constraints it is the traveling salesman problem, each pair of waypoints priced by its faster direction. With `time_windows`, `service_s` or `demands` and `capacity`, travel times are directed and each waypoint also gets its arrival, wait, service, departure and load; a problem no order can meet, or a load that needs more than one vehicle, is a 422 `infeasible`. Up to 10 waypoints return the optimized route directly; more return a `job_id` to poll with get_optimize_job. Parameters: format ### GET /api/v1/optimize/{job_id} > Get async optimization result Returns the status of an asynchronous optimization job, and the optimized route once it has completed. Parameters: job_id ### GET /api/v1/place > Look up places by GID Returns up to 50 places by Plaza GID (`osm:node:123`, `osm:way:456`, `osm:relation:789`, `osm:address:`, `osm:place:`) as a GeoJSON FeatureCollection in request order, each with its admin hierarchy and categories. Every geocoding result carries its `gid`. GIDs that match nothing are left out; 404 when none match. PlazaQL: `place_lookup("osm:node:123", ...)`. Parameters: ids, lang, format ### POST /api/v1/query > Execute a PlazaQL query Runs a PlazaQL query and returns the result as a GeoJSON FeatureCollection, or the same body as the matching REST endpoint for a computation (a route Feature, a matrix object, ...). A search counts as one standard request; each computation the query runs (an output, a set-operation operand, or a variable used as a geometry) counts once as the REST endpoint it stands in for, premium where that endpoint is. An `optimize(...)` over more than 10 waypoints returns `{status: "processing", job_id}`, polled with get_optimize_job, like POST /optimize. Datasets are selected in the query text with `dataset("slug")` (or `dataset("plaza")`, `dataset("all")`), the PlazaQL side of the REST `datasets` parameter on POST /features; there is no separate body parameter. A query with several named outputs (`$$.cafes = ...; $$.parks = ...;`) runs every output and returns `{"outputs": {name: result}}` as JSON; only `format=json` is accepted, and an optimize that would run as a job must be its own query. `search(...).tile(z, x, y)` returns one Mapbox Vector Tile (`application/vnd.mapbox-vector-tile`), the same tile, cache and metering as GET /api/v1/tiles/{z}/{x}/{y} with the search's tag filters, element types, `.fields(...)` as `properties` and `layers:`; it must be the query's only output. A search with `.limit(n)` (or `text_search(...)`) that has more results returns `next_cursor` in its FeatureCollection (and the `X-Next-Cursor` header for a single output); add `.cursor("")` to the same query for the next page. Each named output pages on its own. A cursor from another query, or an edited one, is a 400 `invalid_cursor`, and `.cursor()` cannot be combined with `.offset()`. Parameters: format ### POST /api/v1/route > Calculate a route between two points Computes a route between an origin and a destination over the OSM road network for the chosen travel mode. Routes obey OSM turn restrictions (`no_left_turn`, `only_straight_on` and the rest, at a node or via ways): cars, trucks, motorcycles and scooters follow plain restrictions unless `except` exempts them, bicycles only `restriction:bicycle`, walkers only `restriction:foot`; conditional restrictions apply at `depart_at` local time and are ignored without it. A route may turn round at a dead end to keep to a restriction, and a waypoint does not reset them: the leg leaving it obeys the turns the route arrived for. When the only way to the destination is a prohibited turn, there is no route. Cars, trucks, motorcycles and scooters also pay small turn costs that steer the route away from left turns across oncoming traffic (right turns where traffic keeps left), turns onto a busier road at a junction without signals, and U-turns at junctions, so a route may go round a block to avoid one; they never add to `duration_s`, and `shortest` leaves them out. Ferries (`route=ferry`) are part of the network, timed by their `duration` tag (20 km/h without one), and carry each mode their access tags allow. Barrier nodes close a road to the modes they stop: bollards and similar to motor vehicles, stiles and turnstiles to everyone but walkers, locked or private gates to everyone; motor vehicles avoid other gates with a 300 s penalty that leaves `duration_s` alone. Request `alternatives` to get several candidate routes as a FeatureCollection. Add `ev` to plan charging stops; it combines with `waypoints` or with `alternatives` (each alternative is planned separately). `waypoints` and `alternatives` cannot be combined. Options such as `depart_at`, `traffic_model`, `exclude`, `avoid_datasets` and `datasets` apply to every leg and alternative. `datasets` adds the roads of your datasets to the graph (a campus or private estate OSM lacks), joined to OSM where their ends meet an OSM junction. With `mode: truck` and a `truck` vehicle the route avoids roads whose height, width, length, weight, axle-load or hazmat restrictions the vehicle exceeds; with `ev` it plans an electric truck (consumption by its `weight`, CCS and MCS chargers); with `mode: motorcycle` or `motor_scooter` `ev` plans an electric motorcycle or scooter (its own consumption, and only sockets a two-wheeler can use unless `connector_types` says otherwise). `mode: bicycle` takes a `bicycle` object (bike type, speed, and how much to avoid main roads, hills and rough surfaces) and `mode: foot` a `pedestrian` object (walking speed, hills, lighting, steps, hiking difficulty, wheelchair); preferences change which way the route goes, not its duration. `mode: motorcycle` takes a `motorcycle` object (touring or adventure style, tracks, curvy roads, lane splitting) and `mode: motor_scooter` a `motor_scooter` object (main roads, hills); scooters never use motorways and ride at most 45 km/h unless `top_speed` is set. Both report `curviness_avg` and `surface_breakdown`. With `mode: transit` the route walks and rides public transit from the GTFS feeds Plaza has loaded, leaving at `depart_at` or arriving by `arrive_by` (the latest departure that arrives in time): the response lists `legs` (walks and vehicles with stops, route names and times). `wheelchair: true` rides only accessible trips between accessible stops. A journey can change between feeds where their stops are a short walk apart; the search covers up to 4 hours of travel. Costing options (`use_highways`, `use_tolls`, `use_ferries`, `use_living_streets`, `use_tracks`, `shortest`, `top_speed`, `fixed_speed`, `maneuver_penalty`, `service_penalty`, `gate_cost`, `toll_booth_cost`, `country_crossing_cost`) tune which route is chosen; `duration_s` stays the travel time. Per-point `location_types`, `headings`, `heading_tolerances`, `preferred_sides` and `approaches` (`curb` arrives on the driving side) set how each point is passed, and `recostings` reports the route's time and cost under other costing models. Parameters: format ### POST /api/v1/search > Search OSM features by name Full-text search over OSM feature names, best match first (trigram similarity, boosted for places with a Wikipedia article). When the place index has no match and the request has a spatial filter (`within`, `around`, `intersects`, ...), names containing `q` are matched over all OSM features inside it; without a spatial filter only the place index is searched. When more hits follow, the response carries `next_cursor` (body and `X-Next-Cursor` header); send it back as `cursor` with the same `q` for the next page. PlazaQL: `text_search(query: ...).cursor("...")`. Datasets are not searched here; to find dataset features by name, use POST /features with `datasets` and `tags: {"name": "~pattern"}`. Parameters: q, format, limit, cursor, offset, output[precision], output[fields], output[include] ### GET /api/v1/tiles.json > Get TileJSON for the vector tiles TileJSON 3.0.0 describing GET /api/v1/tiles/{z}/{x}/{y}: the tile URL template, zoom range, bounds, attribution and each layer's fields. Takes the same `tags[...]`, `layers`, `properties` and `element_types` parameters as the tile endpoint and carries them in the tile URL. Needs no API key and is not metered; the tile URL carries no key, so send yours with each tile request. Parameters: tags, layers, properties, element_types ### GET /api/v1/tiles/style.json > Get a MapLibre style for the vector tiles A MapLibre style (spec version 8) that draws the Shortbread layers from GET /api/v1/tiles.json. With `tags[...]` filters it adds a `plaza-filtered` source and draws the matching elements (the `features` layer) on top. Needs no API key and is not metered. No URL in the style carries a key: add yours to tile requests, for example with MapLibre's `transformRequest`. The basemap layers are dynamic tiles too, metered as standard requests. Parameters: tags, layers, properties, element_types ### GET /api/v1/tiles/{z}/{x}/{y} > Get a vector tile Returns one Mapbox Vector Tile (gzip-encoded when the client accepts it) rendered live from OpenStreetMap data in the Shortbread 1.0 schema: `water_polygons`, `land`, `buildings`, `streets`, `place_labels`, `pois` and the other Shortbread layers, each feature carrying `kind`, `osm_type`, `osm_id` and its layer's attributes. With `tags[key]=value` (or `tags[key]=*`) filters the tile holds a `features` layer with every matching element and its tags instead. Zoom 2-22; data is complete at zoom 14, so clients overzoom beyond it. Counts as one standard request toward a paid plan's monthly allocation; on the free plan tiles have their own daily budget (5,000 tiles/day) instead of using the 500 standard requests/day. Tile requests have their own per-minute rate limit, higher than the plan's general one. Tiles larger than 8 MB uncompressed are refused with a 400. The PlazaQL equivalent is `search(...).tile(z, x, y)` on POST /query. Parameters: z, x, y, tags, layers, properties, element_types