Ir al contenido

Lugares · POST /v1/places/search

Búsqueda

Encuentra lugares según lo que son y dónde están. Una búsqueda responde al instante con los lugares que tenemos guardados, y recopila el resto cuando no bastan.

Haz una búsqueda

Una búsqueda necesita dos cosas: qué son los lugares, en keywords, y dónde están, en geo. Todo lo demás tiene un valor por defecto. Esta pide hasta 50 panaderías en Curitiba con calificación de 4 o más:

curl -X POST https://api.gmaps.dev/v1/places/search \
  -H "Authorization: Bearer $GMAPS_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "keywords": ["panadería"],
    "geo": { "location": "Curitiba, PR" },
    "minRating": 4,
    "limit": 50
  }'
CampoPor defectoQué hace
keywordsObligatorioQué buscar, como ["panadería"]. Cada término se busca por separado. Las mayúsculas, los acentos y los espacios de más no importan.
geoObligatorioDónde buscar: exactamente una de las cuatro formas de abajo.
lang"pt"El idioma de la búsqueda, en dos letras, como en. Los lugares que recopilamos para ti llegan en ese idioma. Cada idioma se busca por separado, así que una zona buscada en pt sigue siendo new en en.
minRatingnullDeja solo los lugares con una calificación igual o mayor que esta, de 1 a 5. Los lugares que nadie ha calificado también quedan fuera. Con null, llegan todos.
limit20Cuántos lugares quieres, de 1 a 120. También es lo máximo que puede costar la búsqueda, en créditos.
wait0Cuántos segundos retiene la respuesta mientras llegan lugares nuevos, hasta 60. Con 0, la respuesta llega al instante.

Un campo que no conocemos se rechaza, así que un error de tipeo nunca pasa inadvertido.

Indica dónde

geo acepta exactamente una de estas formas. Si envías dos, o mezclas sus campos, se rechaza.

FormaQué cubre
{ "location": "Curitiba, PR" }Una ciudad de Brasil y su estado, en dos letras. Curitiba/PR y Curitiba - PR también funcionan, y también un nombre que solo tiene una ciudad. Buscamos a una distancia fija alrededor del centro de la ciudad.
{ "lat": -25.4296, "lng": -49.2713, "radiusM": 1500 }Un punto y hasta dónde buscar a su alrededor, en metros. Funciona en cualquier lugar del mundo.
{ "bbox": [-49.31, -25.46, -49.23, -25.39] }Un rectángulo, dado como oeste, sur, este y norte, en grados. El oeste debe ser menor que el este, y el sur menor que el norte. Una zona que cruza la línea de 180° se envía como dos rectángulos.
{ "cityIbge": 4106902, "radiusKm": 8 }Una ciudad de Brasil por su código IBGE de siete dígitos (el de Curitiba es 4106902) y hasta dónde buscar desde su centro, en kilómetros.

location siempre cubre la misma distancia, que puede no llegar al borde de una ciudad grande. Para elegir el tamaño, envía cityIbge con radiusKm, o un punto con radiusM.

location solo encuentra ciudades de Brasil. Cuando no podemos saber a cuál te refieres, la búsqueda se rechaza con 422 GEO_UNRESOLVED:

  • Varias ciudades tienen ese nombre: agrega el estado, como Bom Jesus, PI. details.candidates lista las que encontramos.
  • Ninguna ciudad tiene ese nombre: revisa cómo lo escribiste. Si alguna se le parece, el mensaje pregunta si te referías a ella y details.candidates la lista.
  • El lugar está fuera de Brasil: envía un punto o un rectángulo.

Un código IBGE que no es de una ciudad de Brasil se rechaza de la misma manera.

Zonas demasiado grandes para una búsqueda

Buscamos una zona en cuadros pequeños, una vez por cada término, y Cobertura explica cómo. Algunas solicitudes son demasiado grandes para una sola búsqueda. Se rechazan con 400 VALIDATION_ERROR antes de que empiece nada:

  • un radio mayor que el límite para una zona;
  • una zona y unos términos que suman más cuadros de los que acepta una solicitud;
  • más términos de los que acepta una solicitud.

El mensaje indica el límite y lo que enviaste. Usa una zona más pequeña o menos términos, o divide la solicitud en varias búsquedas.

Lee la respuesta

Esta es la respuesta para una zona que habíamos buscado en parte, recortada a un lugar y unos pocos campos:

{
  "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: hasta limit lugares, cada uno con la forma que describe Lugares. Si hay una recopilación, son los lugares que lleva hasta ahora, y los que ya teníamos guardados van primero. Si no, van primero los que tienen más reseñas.
  • coverage: cuánto de la zona habíamos buscado hace poco. fresh es toda la zona, partial es una parte y new es nada.
  • placesKnown: cuántos lugares teníamos guardados en la parte que habíamos buscado, contando solo los que cumplen minRating. Todos son gratis.
  • collection: la recopilación que busca el resto, o null cuando no hizo falta recopilar nada. Recopilaciones lo explica todo.
  • collectionError: por qué no pudo empezar una recopilación, o null.
  • meta.cached, en cada lugar: true cuando ya lo teníamos guardado, así que no costó nada.

Cuando aún no tenemos suficientes lugares guardados

Una búsqueda inicia una recopilación solo cuando se cumplen las dos cosas: parte de la zona no se ha buscado hace poco, y tenemos menos de limit lugares guardados ahí. Si buscamos toda la zona hace poco, recibes lo que encontramos entonces, y no empieza nada.

La recopilación te entrega como máximo limit lugares, y los que ya teníamos guardados cuentan para ese número sin costo. Cada lugar nuevo cuesta 1 crédito cuando llega, así que una búsqueda nunca cuesta más de limit créditos. Iniciarla es gratis: toda búsqueda responde con x-request-cost: 0, y counts.creditsCharged de la recopilación muestra lo que ha cobrado desde entonces.

Si envías la misma búsqueda mientras su recopilación sigue en curso, recibes esa misma recopilación. No empieza una segunda ni cuesta más.

Para recibir los lugares nuevos, puedes:

  • repetir la misma búsqueda más tarde. Los lugares nuevos llegan junto con los demás, y ahora son gratis.
  • leer la recopilación en GET /v1/collections/{id}, o seguir su progreso en vivo.
  • enviar wait, como muestra la siguiente sección.

Espera los lugares nuevos

Con wait, la respuesta se retiene hasta que la recopilación termine o se acaben los segundos, lo que ocurra primero. Luego responde con los lugares que la recopilación lleva hasta ese momento. Cuando no hay nada que recopilar, responde al instante.

En el SDK, una llamada con wait recibe la espera completa, más un margen para que llegue la respuesta, aunque el timeout del cliente sea menor:

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

Para esperar más de 60 segundos, sigue el progreso en vivo de la recopilación.

Cuando una recopilación no puede empezar

Si se te acabaron los créditos, o ya tienes tantas recopilaciones en curso como permite tu plan, la búsqueda igual responde con los lugares que tenemos guardados. collection es null y collectionError dice por qué. Así termina una respuesta de ese tipo:

{
  "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
    }
  }
}

El código siempre es QUOTA_EXCEEDED, y messageKey distingue los tres motivos:

  • serverErrors.quotaDay: llegaste al límite de hoy. Se reinicia a la medianoche UTC, y los créditos comprados no lo aumentan.
  • serverErrors.quotaMonth: usaste los créditos de este mes y los de cualquier paquete de créditos. Los créditos de tu plan vuelven el día 1 a la medianoche UTC, o compra un paquete de créditos ahora.
  • serverErrors.concurrency: ya tienes tantas recopilaciones en curso como permite tu plan. Intenta de nuevo cuando termine una.

La búsqueda en sí termina bien. Los números de cada plan están en Límites.

Cuando se rechaza una búsqueda

Una búsqueda rechazada no inicia nada y no cuesta nada. La respuesta dice por qué:

Estado HTTPCódigoMotivoQué hacer
400VALIDATION_ERRORFalta un campo, un valor está fuera de rango o hay un campo desconocido, geo tiene más de una forma, o la zona es demasiado grande.Lee message y details, corrige el campo y envíala de nuevo.
422GEO_UNRESOLVEDNo podemos saber a qué ciudad de Brasil se refiere location, o el código IBGE no es de una ciudad.Agrega el estado, corrige el código, o envía un punto o un rectángulo.
429RATE_LIMIT_EXCEEDEDDemasiadas llamadas en este minuto.Espera los segundos indicados en retry-after y envíala de nuevo.

Una clave que falta o es incorrecta se rechaza como describe Autenticación. Quedarte sin créditos nunca hace que se rechace una búsqueda. Errores lista todos los códigos.