How to Find Places in a City, District, or Postcode

API or service
Geocoding API + Places API
Task
Area name → place ID → places inside boundary
Examples
URLJavaScriptPython
Difficulty
Beginner
Time
5 min

You have the name of a city, district, or postcode and need places from a category located inside that area. The Places API searches spatial filters, so first resolve the area to a Geoapify place_id, then use that identifier as a boundary filter.

This two-step workflow keeps the place query tied to the actual administrative or postcode boundary rather than an approximate centre point and radius.

Step 1: Find the area place ID

Use the Forward Geocoding API to resolve the area name. Add an appropriate result type when possible—for example, type=city for a city or type=postcode for a postcode—and request one result.

https://api.geoapify.com/v1/geocode/search?text=Caluire-et-Cuire%2C%20France&type=city&format=json&limit=1&apiKey=YOUR_API_KEY

Confirm that the returned result represents the intended area, especially when names are reused in different regions. Save its place_id.

Reduced response: The full response also contains the parsed query, bounding box, datasource, timezone, ranking, and other location metadata.

{
  "results": [
    {
      "name": "Caluire-et-Cuire",
      "city": "Caluire-et-Cuire",
      "state": "Auvergne-Rhône-Alpes",
      "postcode": "69300",
      "country": "France",
      "country_code": "fr",
      "formatted": "Caluire-et-Cuire, ARA, France",
      "lat": 45.7969952,
      "lon": 4.8423304,
      "result_type": "city",
      "place_id": "51d6b441dc8b5e134059d4884ff003e64640f00101f9017162010000000000c0020892031043616c756972652d65742d4375697265"
    }
  ]
}

For a district, geocode its complete name together with its city and country, then select the result whose address hierarchy matches the intended district. Do not substitute an OSM numeric identifier for place_id; the Places API expects the Geoapify identifier returned by Geoapify APIs.

Step 2: Search inside the boundary

Pass the selected identifier as filter=place:PLACE_ID. This request finds theatres and other cultural places inside Caluire-et-Cuire:

https://api.geoapify.com/v2/places?categories=entertainment.culture&filter=place:51d6b441dc8b5e134059d4884ff003e64640f00101f9017162010000000000c0020892031043616c756972652d65742d4375697265&limit=20&apiKey=YOUR_API_KEY

Reduced response: This example keeps the first returned place and the fields most useful for displaying or identifying it.

{
  "type": "FeatureCollection",
  "features": [
    {
      "type": "Feature",
      "properties": {
        "name": "Radiant Bellevue",
        "city": "Caluire-et-Cuire",
        "state": "Auvergne-Rhône-Alpes",
        "postcode": "69300",
        "country": "France",
        "country_code": "fr",
        "formatted": "Radiant Bellevue, Esplanade Bernard Roger-Dalbert, 69300 Caluire-et-Cuire, France",
        "categories": [
          "entertainment",
          "entertainment.culture",
          "entertainment.culture.arts_centre"
        ],
        "lat": 45.7979564,
        "lon": 4.8430111,
        "place_id": "51477a9e4b3e5f134059049e716f23e64640f00102f901dd18d3110000000092031052616469616e742042656c6c65767565"
      },
      "geometry": {
        "type": "Point",
        "coordinates": [4.8430111, 45.7979564]
      }
    }
  ]
}

The place filter constrains results to the resolved boundary. Change categories without repeating the geocoding request when searching the same area for another type of place. Use limit and offset when the focused query has more results than fit on one page.

A city, district, or postcode is not always represented by a complete polygon in the source data. Handle an empty result and verify boundary coverage when the application depends on strict containment.

Find places in an area with JavaScript

This example performs both steps: it resolves an area name to a Geoapify place_id, then uses that ID to constrain the Places API request. Use an area type for cities and postcodes; omit it for districts that do not map to one of the supported Geocoding API result types.

const geocodingUrl = "https://api.geoapify.com/v1/geocode/search";
const placesUrl = "https://api.geoapify.com/v2/places";

async function requestJson(url) {
  const response = await fetch(url);

  if (!response.ok) {
    throw new Error(`Geoapify request failed: ${response.status}`);
  }

  return response.json();
}

async function findPlacesInArea({
  area,
  areaType,
  categories,
  apiKey,
  limit = 20
}) {
  // Step 1: resolve the human-readable area to a Geoapify place ID.
  const geocodingParams = new URLSearchParams({
    text: area,
    format: "json",
    limit: "1",
    apiKey
  });

  if (areaType) geocodingParams.set("type", areaType);

  const geocoding = await requestJson(
    `${geocodingUrl}?${geocodingParams}`
  );
  const areaResult = geocoding.results[0];

  if (!areaResult) {
    throw new Error(`Area not found: ${area}`);
  }

  // Step 2: search only inside the resolved area boundary.
  const placesParams = new URLSearchParams({
    categories,
    filter: `place:${areaResult.place_id}`,
    limit: String(limit),
    apiKey
  });

  return requestJson(`${placesUrl}?${placesParams}`);
}

const places = await findPlacesInArea({
  area: "Caluire-et-Cuire, France",
  areaType: "city",
  categories: "entertainment.culture",
  apiKey: "YOUR_API_KEY"
});

console.log(places);

In an interactive application, let the user confirm the selected area when geocoding returns ambiguous names. Cache its place_id when the user searches multiple categories inside the same boundary.

Find places in an area with Python

This Python example uses the third-party requests package to resolve the boundary and search it. The returned value is the Places API GeoJSON FeatureCollection.

import requests

GEOCODING_URL = "https://api.geoapify.com/v1/geocode/search"
PLACES_URL = "https://api.geoapify.com/v2/places"


def find_places_in_area(
    api_key,
    area,
    categories,
    area_type=None,
    limit=20,
):
    geocoding_params = {
        "text": area,
        "format": "json",
        "limit": 1,
        "apiKey": api_key,
    }

    if area_type:
        geocoding_params["type"] = area_type

    with requests.Session() as session:
        # Step 1: resolve the area name to its Geoapify place ID.
        response = session.get(
            GEOCODING_URL,
            params=geocoding_params,
            timeout=30,
        )
        response.raise_for_status()
        area_results = response.json()["results"]

        if not area_results:
            raise ValueError(f"Area not found: {area}")

        place_id = area_results[0]["place_id"]

        # Step 2: constrain the place search to that boundary.
        response = session.get(
            PLACES_URL,
            params={
                "categories": categories,
                "filter": f"place:{place_id}",
                "limit": limit,
                "apiKey": api_key,
            },
            timeout=30,
        )
        response.raise_for_status()
        return response.json()


places = find_places_in_area(
    api_key="YOUR_API_KEY",
    area="Caluire-et-Cuire, France",
    area_type="city",
    categories="entertainment.culture",
)

print(f"Found {len(places['features'])} places")

Validate the first geocoding result before automating ambiguous area names. Store the resolved place_id with the input record when the same boundary will be reused.