Overview

The geo service serves a public MapLibre viewer at maps.replicity.ai and a versioned API at /v1/*. Create keys at replicity.ai/account. The repo copy of this contract lives in docs/maps-api/.

Method Path Auth Purpose
GET / Public Map viewer
GET /docs/ Public This hub
GET /healthz Public Process + database ping
GET /v1/cities Public City catalog
GET /v1/cities/{city_id}/layers Public Published versions
GET /v1/layers Public Layer taxonomy
GET /v1/me Optional key Identity, caps, usage
GET /v1/features maps:read Feature download
GET /v1/features/{layer}/{id} maps:read One feature plus per-vertex Z / z_source
GET /v1/features/estimate maps:read Feature count / byte estimate
GET /v1/preview/{layer}/{z}/{x}/{y}.json maps:read Simplified GeoJSON tile
GET /v1/basemap/config keyed maps:read Imagery tile template
GET /v1/basemap/imagery/{z}/{y}/{x} keyed maps:read Satellite tile bytes

Coordinates are EPSG:4326 (lon, lat). Z is NAVD88 orthometric metres on every vertex. GeoJSON includes Z even though RFC 7949 is 2D. OpenAPI / Swagger UI is not served.

Quick start

curl https://maps.replicity.ai/v1/cities
curl https://maps.replicity.ai/v1/layers

curl "https://maps.replicity.ai/v1/features?city=us-ny-nyc&layers=road_polygons&bbox=-74.01,40.75,-74.00,40.76&format=geojson"

curl -H "Authorization: Bearer rpl_live_…" \
  "https://maps.replicity.ai/v1/features?city=us-ny-nyc&layers=road_polygons&bbox=-74.01,40.75,-74.00,40.76&format=fgb" \
  -o roads.fgb

curl -H "Authorization: Bearer rpl_live_…" \
  https://maps.replicity.ai/v1/basemap/config

curl -H "Authorization: Bearer rpl_live_…" \
  "https://maps.replicity.ai/v1/basemap/imagery/17/{y}/{x}" \
  -o tile.jpg

Authentication

/v1/* accepts either no Authorization header (anonymous) or Authorization: Bearer <api_key>.

Anonymous tier

Missing key → synthetic principal tier=anonymous, scopes=["maps:read"]. GeoJSON only. format=fgb returns 401. Basemap imagery is keyed only (401 without a key). Only redistributable dataset versions. Rate limit is a salted IP hash. Kill switch: ANON_TIER_ENABLED=false.

API keys

Format rpl_live_<8-char-prefix>_<secret>. Looked up by prefix, verified with argon2id, rejected if revoked. In-process cache is 60 seconds (the revocation window).

Authorization: Bearer rpl_live_a1b2c3d4_XyZ…

Tiers and caps

Anonymous Authenticated
Max bbox 4 km² 50 km²
Max features / request 6,000 20,000
Rate limit 30 rpm / IP hash /v1/features: key rate_limit_rpm (default 60). /v1/basemap/*: 300 rpm
Formats geojson geojson, fgb
Non-redistributable Never Only internal tier or internal account

Usage is logged and summarized on /v1/me.

CORS

Applies to /v1 only. Origins are never reflected. Anonymous callers and keys with empty allowed_origins use the public allowlist: https://replicity.ai, https://www.replicity.ai, https://maps.replicity.ai, http://localhost:3000. Keys with allowed_origins set are limited to that list. Preflight: 204 allowlisted, 403 otherwise. Methods: GET, OPTIONS. Headers: Authorization, Content-Type.

Endpoints

JSON errors are {"detail":"…"} unless noted.

GET /healthz

Always HTTP 200. Read ok.

{ "ok": true, "geo_db": "ok", "app_db": "ok" }

GET /v1/cities

Public catalog. 503 if the geo database is down.

[
  {
    "id": "us-ny-nyc",
    "name": "New York City",
    "display_name": "New York, USA",
    "bbox": { "type": "Polygon", "coordinates": [[…]] },
    "center": { "type": "Point", "coordinates": [-73.98, 40.705] },
    "layers": ["road_centerlines", "road_edges", "road_polygons"],
    "freshness": "2026-08-12T00:00:00+00:00"
  }
]

GET /v1/cities/{city_id}/layers

Published versions for one city. Unknown city → 404.

[
  {
    "id": 1,
    "layer": "road_polygons",
    "version": "2026.08.0",
    "license": "NYC Open Data Terms of Use",
    "attribution": "City of New York",
    "redistributable": true,
    "vertical_crs": "NAVD88",
    "horizontal_crs": "EPSG:4326",
    "published_at": "2026-08-12T00:00:00+00:00",
    "source_name": "NYC Planimetrics 2022 + LION 26A",
    "feature_count": 18420
  }
]

GET /v1/layers

Static taxonomy: id, display_name, category, available, geometry, description, fields. Live layers have available: true. Future layers (lane_marking_observations, road_decals, landmark_buildings, water_bodies, vegetation, fields) are advertised with available: false and cannot be queried. fields is a documentation subset — full GeoJSON properties are under Data layers.

GET /v1/me

{
  "tier": "anonymous",
  "account_id": null,
  "key_prefix": null,
  "scopes": ["maps:read"],
  "is_internal": false,
  "caps": {
    "max_bbox_km2": 4.0,
    "max_features_per_request": 6000,
    "rate_limit_rpm": 30,
    "formats": ["geojson"]
  },
  "usage": {
    "area_km2": 0.0,
    "features_returned": 0,
    "period": "calendar_month_utc",
    "available": true
  }
}

GET /v1/features

Metered download. Requires maps:read (anonymous has it).

Param Required Description
city yes City id (us-ny-nyc)
layers yes Comma-separated layer ids
bbox yes minx,miny,maxx,maxy EPSG:4326
format no geojson (default) or fgb
detail no footprint (default) or recipe. recipe adds the building recipe blocks and requires format=geojson
limit no 1–50000, capped by tier
cursor no Keyset: rows with id > cursor
  • ST_Intersects the bbox envelope.
  • Same cursor applied to every requested layer. limit is a shared remaining budget in request order.
  • format=fgb needs a key and exactly one layer. Content-Type application/flatgeobuf. Encoder currently writes geometry + id only.
  • Headers: X-Replicity-Attribution, X-Replicity-Next-Cursor.

format=geojsonapplication/geo+json:

{
  "type": "FeatureCollection",
  "attribution": "City of New York",
  "features": [
    {
      "type": "Feature",
      "id": 42,
      "geometry": {
        "type": "MultiPolygon",
        "coordinates": [[[[-74.01, 40.75, 12.3]]]]
      },
      "properties": {
        "id": 42,
        "city_id": "us-ny-nyc",
        "layer": "road_polygons",
        "class": "carriageway",
        "z_source": "pavement_edge_z"
      }
    }
  ]
}

properties is every table column except geom, plus injected layer. Coordinates are [lon, lat, z_metres]. attributes.vertex_sources is omitted from collection responses; use feature detail for per-vertex provenance.

GET /v1/features/{layer}/{id}

One feature by id, with a vertices array for editor debug. Requires maps:read. Query: city (required). Unknown city or missing feature → 404. Closing ring duplicates are omitted. Each vertex has lon, lat, z_metres, and z_source (from attributes.vertex_sources when present, else the feature z_source).

{
  "type": "Feature",
  "id": 42,
  "geometry": {
    "type": "MultiPolygon",
    "coordinates": [[[[-74.01, 40.75, 12.3]]]]
  },
  "properties": {
    "id": 42,
    "city_id": "us-ny-nyc",
    "layer": "road_polygons",
    "class": "carriageway",
    "z_source": "pavement_edge_z"
  },
  "vertices": [
    {
      "index": 0,
      "part": 0,
      "ring": 0,
      "lon": -74.01,
      "lat": 40.75,
      "z_metres": 12.3,
      "z_source": "pavement_edge_z"
    }
  ]
}

GET /v1/features/estimate

Same city, layers, and bbox as features, but returns counts only. Shares the features rate bucket and bbox-area cap.

{
  "city": "us-ny-nyc",
  "layers": ["road_polygons"],
  "bbox": [-74.01, 40.70, -74.00, 40.71],
  "area_km2": 0.936,
  "feature_count": 128,
  "bytes_estimate": 450000
}

bytes_estimate is 3 × SUM(ST_MemSize(geom)), a GeoJSON-sized overestimate.

GET /v1/preview/{layer}/{z}/{x}/{y}.json

Simplified GeoJSON for a Web Mercator XYZ tile ({z}/{x}/{y}, OSM order — not the ArcGIS {z}/{y}/{x} used by basemap). Anonymous maps:read is enough. Optional city query; otherwise features must sit in a catalog city that intersects the tile.

  • Queryable layers only (road_polygons, road_centerlines, road_edges, lane_markings, lane_marking_groups). Unavailable catalog ids → 400 layer is not available.
  • ST_Simplify at about two pixels of the tile. Properties are id and class only.
  • Separate preview:{key} bucket at PREVIEW_RATE_LIMIT_RPM (default 300).
  • Tile-keyed cache shared across callers. Tile area is checked against the bbox-area cap.
{
  "type": "FeatureCollection",
  "attribution": "City of New York",
  "features": [
    {
      "type": "Feature",
      "id": 42,
      "geometry": { "type": "MultiPolygon", "coordinates": [[[[-74.01, 40.75, 12.3]]]] },
      "properties": { "id": 42, "class": "carriageway" }
    }
  ]
}

GET /v1/basemap/config

Keyed client bootstrap. Requires a minted key with maps:read. Anonymous → 401.

{
  "style": "imagery",
  "tileSize": 256,
  "minZ": 0,
  "maxZ": 19,
  "urlTemplate": "/v1/basemap/imagery/{z}/{y}/{x}",
  "attribution": "Source: Esri, Vantor, Earthstar Geographics, and the GIS User Community"
}

urlTemplate is relative to https://maps.replicity.ai. Tile indices are {z}/{y}/{x} (ArcGIS order), not {z}/{x}/{y}. Display attribution with the imagery.

GET /v1/basemap/imagery/{z}/{y}/{x}

Keyed raster proxy for Esri World Imagery. The ArcGIS token stays on the geo service. Basemap uses a separate per-key bucket (BASEMAP_RATE_LIMIT_RPM, default 300) so a viewport of tiles is not capped by the /v1/features 60 rpm limit.

  • Missing/invalid key → 401. Missing maps:read → 403.
  • z in [minZ, maxZ]; x and y in 0 .. 2^z - 1.
  • Cache key is (z, y, x), shared across keys. Origin miss fetches Esri; non-image / non-200 → 502 Basemap origin failed.
  • Every successful tile (origin and cache hit) is metered: layers=["basemap_imagery"], area_km2 = Web Mercator ground area of the tile.
  • Headers: X-Replicity-Attribution, Cache-Control: public, max-age=86400.

Data layers

Id Table Geometry Description
road_polygons geo.road_polygon MultiPolygonZ Carriageway, intersection, shoulder, driveway
road_centerlines geo.road_centerline LineStringZ Street centerlines
road_edges geo.road_edge LineStringZ Pavement, seam, structure, median edges
lane_markings geo.lane_marking LineStringZ Painted marking centerlines (continuous; dashes are attributes)
lane_marking_groups geo.lane_marking_group LineStringZ Groups binding paired markings (e.g. double yellow)
sidewalks geo.sidewalk MultiPolygonZ Sidewalks; curb snapped to PE; first-class material
road_medians geo.road_median MultiPolygonZ Median polygons; Z from nearest pavement edge
plazas geo.plaza MultiPolygonZ Public plazas (Overture pedestrian); Z from 3DEP
parks geo.park MultiPolygonZ Park / recreation; Z from 3DEP
parking_lots geo.parking_lot MultiPolygonZ Parking lots; per-vertex Z from nearest pavement edge
buildings geo.building MultiPolygonZ Footprints with procedural recipes

Partitioned by city_id (only us-ny-nyc today). No foreign keys between feature tables. Catalog also advertises unavailable layers (lane_marking_observations, road_decals, landmark_buildings, water_bodies, vegetation, fields) with available: false. Road z_source: lidar_bridge_deck, lidar_ground, pavement_edge_z, planimetric_point, interpolated, usgs_3dep_dem (road_polygon only, at_grade only), synthesized. Marking z_source: draped_road_polygon, draped_road_centerline, pavement_edge_z, interpolated, synthesized. Surface z_source: pavement_edge_z, usgs_3dep_dem, interpolated, synthesized.

road_polygons

Property Type Notes
idintegerAlso Feature id
city_idstringPartition key
dataset_version_idinteger
sourcestring
source_idstring?
classstringcarriageway | intersection | shoulder | driveway
functionstring?CityGML TrafficArea function
levelinteger0 = at grade
structurestringat_grade | bridge | viaduct | tunnel
structure_refstring?NBI number when matched
primary_centerline_idinteger?Not an FK
centerline_idsinteger[]Default []
z_sourcestringSee list above
z_accuracy_mnumber?
elevation_minnumber?
elevation_maxnumber?
surfacestring?
attributesobjectDefault {}. May include vertex_sources, z_conflict_m (pavement-edge vs. centerline disagreement, metres), z_synthesized (true only when nothing on the polygon resolved to a measured/DEM Z)
layerstringInjected in GeoJSON

road_centerlines

Property Type Notes
idinteger
city_idstring
dataset_version_idinteger
sourcestring
source_idstring?
from_node_idinteger?
to_node_idinteger?
level_fromintegerDefault 0
level_tointegerDefault 0
structurestringDefault at_grade
rw_typestring?
street_namestring?
z_sourcestring
z_accuracy_mnumber?
attributesobject
layerstringInjected in GeoJSON

road_edges

Property Type Notes
idinteger
city_idstring
dataset_version_idinteger
polygon_idintegerParent polygon, not an FK
adjacent_polygon_idinteger?
edge_typestringpavement_edge | seam | structure_edge | median_edge
levelintegerDefault 0
z_sourcestring
attributesobject
layerstringInjected in GeoJSON

lane_markings

One continuous LineStringZ per marking (contract change from advertised MultiLineStringZ). Dash gaps are attributes. Gold-set rows are redistributable: false.

Property Type Notes
idinteger
city_idstring
stable_idstringULID, carried across versions
marking_typestringcenterline | lane_divider | edge_line | …
colorstringwhite | yellow | …
patternstringsolid | broken | …
group_idinteger?Not an FK
dash_length_mnumber?With gap_length_m, dash_phase_m
confidencenumber
intersection_clippedboolean
z_sourcestringLift vocabulary (see above)
layerstringInjected in GeoJSON

lane_marking_groups

Property Type Notes
idinteger
city_idstring
stable_idstringULID
group_typestringsingle | double | …
colorstring
member_countinteger
member_stable_idsstring[]
z_sourcestringLift vocabulary
layerstringInjected in GeoJSON

sidewalks

Dedicated sidewalk schema (not the shared surface-polygon column set). Curb vertices snap to Pavement Edge. material is a closed pavement vocab: concrete | asphalt | paving_stones | sett | brick | rubber | gravel | wood | metal | dirt | grass | unknown. NYC Planimetrics has no pavement-type field, so every NYC row is concrete with attributes.material_source = inferred_default.

Property Type Notes
idintegerAlso Feature id
city_idstringPartition key
classstringsidewalk
materialstringClosed pavement vocab (see above)
levelintegerDefault 0
structurestringat_grade | bridge | viaduct | tunnel
z_sourcestringSurface vocabulary
attributesobjectNYC leftovers; material_source is inferred_default | tag_mapping | manual
layerstringInjected in GeoJSON

Surface polygons

road_medians, plazas, parks, and parking_lots share one column set (class, subtype, name, level, structure, z_source, elevation range, attributes). Each is its own MultiPolygonZ table. City leftovers stay in attributes.

Layer class Z
road_medians median Nearest Pavement Edge (XY unchanged)
plazas pedestrian USGS 3DEP 1 m
parks park | recreation USGS 3DEP 1 m
parking_lots parking Per-vertex nearest Pavement Edge

buildings

Ground-level footprints (MultiPolygonZ) carrying procedural recipes. Height is the height_m column, not the geometry. Geometry and lidar heights come from NYC DOITT Building Footprints, tags are joined from OpenStreetMap on nycdoitt:bin, and ground_elev_m is sampled from USGS 3DEP 1 m bare-earth DEM.

The nine recipe blocks — identity, classification, massing, facade_rules, materials, roof_system, pedestrian_plane, appurtenances, and provenance — are returned only with detail=recipe. They are several kilobytes each, so the default detail=footprint omits them.

Property Type Notes
idinteger
city_idstring
stable_idstringULID
binstring?NYC Building Identification Number
bblstring?Borough-Block-Lot
namestring?
addressstring?
primary_usestringresidential | office | retail | … | unknown
building_formstring?tower | slab | rowhouse | …
year_builtinteger?
height_mnumber?Lidar roof height above ground
min_height_mnumber?Base of the massing
storeys_aboveinteger?
footprint_area_m2number?
roof_formstring?flat | gable | mansard | …
lod_supportedstring?lod0lod4
completeness_overallnumber?0..1; how much of the recipe is present
confidence_overallnumber?0..1; how much of it can be trusted
ground_elev_mnumber?NAVD88 metres at the anchor
z_sourcestringusgs_3dep_dem
layerstringInjected in GeoJSON

Errors

{ "detail": "bbox exceeds 4.0 km2 cap for this tier" }
Code When
400Bad bbox/layers/format; bbox over cap; fgb with multiple layers; tile zoom/index out of range; unavailable preview layer
401Bad/missing key; anonymous fgb; anonymous basemap
403Missing maps:read; CORS preflight denied
404Unknown city; feature not found
429Rate limit; Retry-After seconds
502Basemap origin failed
503Geo DB down, or key lookup down with empty cache