How to Get a City Boundary as GeoJSON and Draw It on a Map

API or service
Boundaries API
Task
A point in a city → city boundary polygon → map layer
Examples
URLMapLibre GLAI
Difficulty
Beginner
Time
10 min

Get a city boundary as GeoJSON

You need the border of a city as a polygon: to draw it on a map, to clip other data to it, or to check whether points are inside the city. The Boundaries API part-of endpoint takes a point inside the city and returns every administrative boundary that contains it, including the city itself, as GeoJSON polygons.

This guide gets the city boundary of Munich, Germany, picks the city out of the returned boundaries, and draws it with MapLibre GL. The examples use Germany because its city, district, and state levels are mapped consistently in OpenStreetMap, the data source of the Boundaries API.

To find the area that contains a point without the polygons, see How to Find Which City, District, or Neighbourhood Contains a Location.

Task flow: a point in the city → Boundaries API part-of with polygons → the city feature → a GeoJSON layer on the map.

Request the boundaries with polygons

Send a point inside the city, Marienplatz in Munich, and ask for polygon geometry:

https://api.geoapify.com/v1/boundaries/part-of?lat=48.1374&lon=11.5755&boundaries=administrative&geometry=geometry_1000&lang=de&apiKey=YOUR_API_KEY
Parameter Value Why
lat, lon 48.1374, 11.5755 Any point inside the city. A place ID from the Geocoding or Places API (id) works too
boundaries administrative The default; administrative boundaries only
geometry geometry_1000 Polygons simplified to about 1,000 m. The default point returns only a center point. geometry_10000 returns simpler, smaller polygons
lang de Names and address fields in German, so the city name in the feature matches the city field (see the next section)

The response is a GeoJSON FeatureCollection. For Marienplatz it contains 7 boundaries, from two small areas in the old town up to Germany, and every one of them has a polygon. The city boundary of Munich is a MultiPolygon with 3 polygons.

Reduced response: one of the 7 features, with its coordinates cut.

{
  "type": "Feature",
  "properties": {
    "name": "München",
    "city": "München",
    "state": "Bayern",
    "country": "Deutschland",
    "datasource": {
      "sourcename": "openstreetmap",
      "raw": { "osm_id": 62428, "osm_type": "r", "admin_level": 6, "boundary": "administrative" }
    },
    "place_id": "510c116dd2100d274059019e7abfd1134840f00101f901dcf3000000000000c002099203084dc3bc6e6368656e"
  },
  "geometry": {
    "type": "MultiPolygon",
    "coordinates": [[[[11.5560672, 48.0799449], [11.5563954, 48.0804318], "…"]], "…"]
  }
}

Credits: every 20 boundaries returned cost 1 credit, and every polygon adds 1 credit. This request returns 7 boundaries with polygons: 1 + 7 = 8 credits. A request with geometry=point costs 1 credit.

Select the city boundary

The response lists every boundary around the point, so pick the city out of it. With lang set to the local language, the city boundary is the feature whose name equals its own city field:

name city datasource.raw.admin_level What it is
Altstadt-Lehel München 9 City district
München München 6 The city
Oberbayern — 5 Administrative region
Bayern — 4 State
Deutschland — 2 Country

Without lang=de, the city field of the same feature is the English name, Munich, and no longer matches name.

The OpenStreetMap level of the city depends on the country and on the city. Munich is a city with district status (kreisfreie Stadt), so its boundary is at admin_level 6. A town that belongs to a district is one level lower: for a point in Garmisch-Partenkirchen (47.4921, 11.0955), the response contains the town Garmisch-Partenkirchen at level 8 and Landkreis Garmisch-Partenkirchen at level 6. The name = city rule picks the town in both cases. The levels used in each country are listed on the OpenStreetMap wiki page Tag:boundary=administrative.

Keep the selected feature unchanged: its geometry can be a Polygon or a MultiPolygon, and both are valid GeoJSON for map libraries and geometry tools.

Draw the city boundary with MapLibre GL

Use this after creating a MapLibre map and waiting for its load event. It requests the boundaries, selects the city, draws it as a translucent area with an outline, and fits the map to the boundary.

const apiKey = "YOUR_API_KEY";
const params = new URLSearchParams({
  lat: "48.1374",
  lon: "11.5755",
  boundaries: "administrative",
  geometry: "geometry_1000",
  lang: "de",
  apiKey
});

const response = await fetch(`https://api.geoapify.com/v1/boundaries/part-of?${params}`);
if (!response.ok) throw new Error(`Boundaries request failed: ${response.status}`);

const boundaries = await response.json();
// The city is the boundary whose name matches its own city field (here: München).
const city = boundaries.features.find(
  feature => feature.properties.name && feature.properties.name === feature.properties.city
);
if (!city) throw new Error("No city boundary was returned");

map.addSource("city-boundary", { type: "geojson", data: city });
map.addLayer({
  id: "city-boundary-fill",
  type: "fill",
  source: "city-boundary",
  paint: { "fill-color": "#623aff", "fill-opacity": 0.15 }
});
map.addLayer({
  id: "city-boundary-outline",
  type: "line",
  source: "city-boundary",
  paint: { "line-color": "#623aff", "line-width": 2 }
});

// Extend the bounds with every Polygon or MultiPolygon position.
const bounds = new maplibregl.LngLatBounds();
const includeCoordinates = coordinates => {
  if (typeof coordinates[0] === "number") bounds.extend(coordinates);
  else coordinates.forEach(includeCoordinates);
};
includeCoordinates(city.geometry.coordinates);
map.fitBounds(bounds, { padding: 40, animate: false });

The API returns [longitude, latitude] coordinates, the order MapLibre expects, so the feature can be added as it is. The includeCoordinates helper handles both Polygon and MultiPolygon geometry. The code was tested with MapLibre GL JS 4.7.1 and the Geoapify osm-bright map style.

Munich city boundary drawn with MapLibre GL on a Geoapify map

City boundary map with an AI coding agent

Copy this prompt into Codex, Claude Code, Cursor, or another coding agent to show a city boundary on a map in your project.

Show a city boundary on a map in my project with the Geoapify Boundaries API.

Before you start
- Inspect the existing project first. Reuse its current framework, map library, HTTP client, and conventions. Make the smallest necessary changes.
- Ask only what the project does not answer: how the city is chosen (a point, a search result, or a place ID), which map library is used, and whether browser requests use a restricted API key or a backend proxy.
- Read the Geoapify API key from the project's environment configuration. Never hardcode, print, or log it.

API request
- GET https://api.geoapify.com/v1/boundaries/part-of with lat and lon of a point inside the city (or id, a Geoapify place ID), boundaries=administrative, geometry=geometry_1000, and lang set to the city's local language.
- The response is a GeoJSON FeatureCollection with every administrative boundary that contains the point, from small districts to the country.
- Credits: 1 per 20 boundaries returned plus 1 per polygon, so a request with polygons usually costs 5–10 credits. Cache the selected boundary.

Select the city
- Pick the feature whose properties.name equals properties.city. Keep properties.datasource.raw.admin_level for debugging; levels differ by country.
- Show an error state if no feature matches.

Map
- Add the selected Feature unchanged as a GeoJSON source (coordinates are [longitude, latitude]); draw a translucent fill and an outline.
- Fit the map to all Polygon or MultiPolygon positions.
- Keep the attribution of the map tiles and OpenStreetMap visible.

Errors
- Show the API message for HTTP 400 responses, retry 429 responses a limited number of times with backoff, and add a test with a mocked response that contains a district, the city as a MultiPolygon, and the state.

Review the generated code against the requests in this guide. Check the lang parameter, the name = city selection, MultiPolygon handling, and the API-key handling.