Skip to content

Places · POST /v1/places/search

Search

Find places by what they are and where they are. A search answers at once with the places we've saved, and collects the rest when they aren't enough.

Send a search

A search needs two things: what the places are, in keywords, and where they are, in geo. Everything else has a default. This asks for up to 50 bakeries in Curitiba rated 4 or more:

curl -X POST https://api.gmaps.dev/v1/places/search \
  -H "Authorization: Bearer $GMAPS_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "keywords": ["bakery"],
    "geo": { "location": "Curitiba, PR" },
    "minRating": 4,
    "limit": 50
  }'
FieldDefaultWhat it does
keywordsRequiredWhat to look for, such as ["bakery"]. Each term is searched on its own. Capital letters, accents and extra spaces don't matter.
geoRequiredWhere to look: exactly one of the four shapes below.
lang"pt"The language to search in, as two letters, such as en. Places we collect for you come back in it. Each language is searched on its own, so an area searched in pt is still new in en.
minRatingnullKeep only places rated at least this, from 1 to 5. Places nobody has rated are left out too. null keeps every place.
limit20How many places you want, from 1 to 120. It's also the most the search can cost, in credits.
wait0How many seconds to hold the answer while new places come in, up to 60. With 0, the answer comes at once.

A field we don't know is refused, so a typo never goes unnoticed.

Say where

geo takes exactly one of these shapes. Sending two, or mixing their fields, is refused.

ShapeWhat it covers
{ "location": "Curitiba, PR" }A Brazilian city and its two-letter state. Curitiba/PR and Curitiba - PR work too, and so does a name only one city has. We search a fixed distance around the city's centre.
{ "lat": -25.4296, "lng": -49.2713, "radiusM": 1500 }A point, and how far around it to look, in metres. It works anywhere in the world.
{ "bbox": [-49.31, -25.46, -49.23, -25.39] }A box, as west, south, east and north, in degrees. West must be less than east, and south less than north. An area that crosses the 180° line goes as two boxes.
{ "cityIbge": 4106902, "radiusKm": 8 }A Brazilian city by its seven-digit IBGE code (Curitiba's is 4106902), and how far from its centre to look, in kilometres.

location always covers the same distance, which may not reach the edge of a big city. To choose the size, send cityIbge with radiusKm, or a point with radiusM.

location only finds Brazilian cities. When we can't tell which one you mean, the search is refused with 422 GEO_UNRESOLVED:

  • Several cities share the name: add the state, such as Bom Jesus, PI. details.candidates lists the ones we found.
  • No city has that name: check the spelling. When one comes close, the message asks if you meant it, and details.candidates lists it.
  • The place is outside Brazil: send a point or a box instead.

An IBGE code that isn't a Brazilian city is refused the same way.

Areas too big for one search

We search an area in small squares, once for each term, and Coverage explains how. Some asks are too big for one search. They're refused with 400 VALIDATION_ERROR before anything starts:

  • a radius past the limit for one area;
  • an area and terms that come to more squares than one ask takes;
  • more terms than one ask takes.

The message names the limit and what you sent. Use a smaller area or fewer terms, or split the ask into several searches.

Read the answer

This is the answer for an area we'd partly searched, cut down to one place and a few fields:

{
  "data": {
    "places": [
      {
        "id": "5b0c8e0a-2f4d-4c1e-9a57-3d2b8f6e1c90",
        "identity": {
          "cid": "12345678901234567890",
          "name": "Padaria Estrela do Centro",
          "category": "Padaria",
          "mapsUrl": "https://maps.google.com/?cid=12345678901234567890"
        },
        "ratings": { "rating": 4.6, "reviewCount": 212 },
        "meta": { "cached": true }
      }
    ],
    "coverage": "partial",
    "placesKnown": 14,
    "collection": {
      "id": "8c1f5e2a-7b3d-4f60-9e14-2a6d0b9c7f31",
      "kind": "search",
      "status": "queued",
      "squaresTotal": 21,
      "squaresCovered": 9,
      "counts": {
        "placesDelivered": 14,
        "placesFresh": 0,
        "placesCached": 14,
        "creditsCharged": 0,
        "creditsRefunded": 0
      }
    },
    "collectionError": null
  }
}
  • places: up to limit places, each in the shape Places describes. With a collection, these are its places so far, and the ones we'd saved come first. Without one, the most reviewed come first.
  • coverage: how much of the area we'd searched recently. fresh is all of it, partial is some, and new is none.
  • placesKnown: how many places we'd saved in the part we'd searched, counting only those that pass minRating. Each of them is free.
  • collection: the collection finding the rest, or null when nothing needed collecting. Collections describes all of it.
  • collectionError: why a collection couldn't start, or null.
  • meta.cached, on each place: true when we'd already saved it, so it cost nothing.

When we haven't saved enough

A search starts a collection only when both are true: part of the area hasn't been searched recently, and we've saved fewer than limit places there. If we searched the whole area recently, you get what we found then, and nothing starts.

The collection hands you at most limit places, and the ones we'd saved count toward that for free. Each new place costs 1 credit as it arrives, so a search never costs more than limit credits. Starting one is free: every search answers with x-request-cost: 0, and the collection's counts.creditsCharged shows what it has taken since.

Send the same search while its collection runs, and you get that same collection back. It doesn't start a second one or cost more.

To get the new places, you can:

  • run the same search again later. The new places come back with the rest, and now they're free.
  • read the collection at GET /v1/collections/{id}, or follow its live progress.
  • send wait, as the next section shows.

Wait for new places

With wait, the answer holds until the collection ends or the seconds run out, whichever comes first. Then it answers with the collection's places so far. When nothing needs collecting, it answers at once.

In the SDK, a call with wait gets the whole wait, plus time for the answer to arrive, even when the client's timeout is shorter:

curl -X POST https://api.gmaps.dev/v1/places/search \
  -H "Authorization: Bearer $GMAPS_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "keywords": ["bakery"],
    "geo": { "location": "Curitiba, PR" },
    "wait": 60
  }'

To wait longer than 60 seconds, follow the collection's live progress instead.

When a collection can't start

When you're out of credits, or already running as many collections as your plan allows, the search still answers with the places we've saved. collection is null, and collectionError says why. This is the end of such an answer:

{
  "collection": null,
  "collectionError": {
    "code": "QUOTA_EXCEEDED",
    "message": "You reached your daily limit of 250 credits. Retry after midnight UTC. Buying credits does not raise this limit.",
    "messageKey": "serverErrors.quotaDay",
    "params": { "limit": 250 },
    "details": {
      "scope": "day",
      "limit": 250,
      "resetAt": "2026-09-25T00:00:00.000Z",
      "retryAfterSecs": 3600
    }
  }
}

The code is always QUOTA_EXCEEDED, and messageKey tells the three reasons apart:

  • serverErrors.quotaDay: you've reached today's limit. It starts over at midnight UTC, and bought credits don't raise it.
  • serverErrors.quotaMonth: you've used this month's credits and any from credit packs. Your plan's credits come back on the 1st at midnight UTC, or buy a credit pack now.
  • serverErrors.concurrency: you're already running as many collections as your plan allows. Try again when one ends.

The search itself still succeeds. Limits has each plan's numbers.

When a search is refused

A refused search starts nothing and costs nothing. The answer says why:

StatusCodeWhyWhat to do
400VALIDATION_ERRORA field is missing, out of range or unknown, geo has more than one shape, or the area is too big.Read message and details, fix the field, and send it again.
422GEO_UNRESOLVEDWe can't tell which Brazilian city location means, or the IBGE code isn't a city.Add the state, fix the code, or send a point or a box.
429RATE_LIMIT_EXCEEDEDToo many calls this minute.Wait the number of seconds in retry-after, then send it again.

A missing or wrong key is refused as Authentication describes. Running out of credits never refuses a search. Errors lists every code.