Integrações
SDK para TypeScript
Use a API em TypeScript ou JavaScript: um método tipado para cada chamada, e funções prontas para acompanhar uma coleta e validar um webhook.
Instale
O pacote é o gmaps-sdk, sem nenhuma dependência.
npm i gmaps-sdkCrie um cliente
Passe sua chave, que aqui vem de uma variável de ambiente. Cada método faz uma chamada e devolve o que a API responde em data:
import { createClient } from "gmaps-sdk";
const gmaps = createClient({ apiKey: process.env.GMAPS_API_KEY });
const result = await gmaps.places.search({
keywords: ["padaria"],
geo: { location: "Curitiba, PR" }
});
console.log(result.places.map((place) => place.identity.name));O que o createClient aceita:
| Opção | Padrão | Para que serve |
|---|---|---|
apiKey | - | Sua chave. Sem ela, só o attributions.list() funciona. |
baseUrl | https://api.gmaps.dev/v1 | O endereço da API, terminando em /v1. Só mude se for chamar uma API local. |
timeout | 30000 | Quanto tempo cada tentativa de uma chamada pode levar, em milissegundos, contando a leitura da resposta. Uma busca com wait sempre ganha a espera inteira, e um pouco mais. |
maxRetries | 2 | Quantas vezes uma chamada que falhou é tentada de novo, quando isso é seguro. Com 0, o SDK nunca tenta de novo. |
fetch | globalThis.fetch | Seu próprio fetch, para testes ou para um ambiente que não tenha um. |
Para dar outro limite de tempo a uma chamada, ou cancelá-la, passe { timeoutMs, signal } como último argumento.
const usage = await gmaps.usage.get({ timeoutMs: 5000 });Se uma chamada fica sem resposta ou recebe um erro do nosso lado (um 5xx), o SDK manda de novo depois de cerca de 1 segundo, e mais uma vez cerca de 2 segundos depois. Isso só vale para chamadas que podem rodar duas vezes sem problema: leituras, estimativas, consultas de cobertura, cancelamentos e mudanças e exclusões de webhook. Uma busca, uma coleta nova, uma exportação nova, um webhook novo e um teste de webhook nunca são repetidos assim, porque a primeira tentativa pode já ter rodado, e uma segunda poderia gastar créditos, enviar algo duas vezes ou criar uma segunda exportação na sua lista. Depois de um 408, 425 ou 429 que peça uma espera de até 10 segundos, qualquer chamada espera e vai de novo.
Todos os métodos
Cada método recebe um objeto com os mesmos campos da chamada da API. Os que não têm campos, como usage.get(), recebem só as opções.
| Método | Chamada da API | Para que serve |
|---|---|---|
places.search(params) | POST /v1/places/search | Encontra lugares pelo que são e onde ficam. Veja Busca. |
places.get({ place }) | GET /v1/places/{place} | Lê um lugar que já salvamos, pelo nosso id, pelo cid, pelo place ID ou por um link do Maps. Veja Lugares. |
collections.quote(params) | POST /v1/collections/quote | Mostra quanto custaria coletar uma região, sem começar nada. |
collections.create(params) | POST /v1/collections | Coleta todos os lugares de uma região. Veja Coletas. |
collections.get({ id }) | GET /v1/collections/{id} | Mostra até onde a coleta chegou e quanto já custou. |
collections.list(params?) | GET /v1/collections | As suas coletas, das mais novas para as mais antigas. |
collections.places({ id }) | GET /v1/collections/{id}/places | Os lugares que uma coleta encontrou, uma página por vez. |
collections.cancel({ id }) | DELETE /v1/collections/{id} | Para uma coleta. Os lugares que já chegaram continuam seus, e os créditos gastos com eles não voltam. |
watchCollection(id, options?) | GET /v1/collections/{id}/stream | Entrega os lugares novos de uma coleta à medida que chegam. Veja abaixo. |
exports.create(params) | POST /v1/exports | Gera um link com os lugares de uma coleta em csv, json ou xlsx. Veja Exportações. |
exports.get({ id }) | GET /v1/exports/{id} | Lê uma exportação de novo, enquanto o link ainda funciona. |
coverage.get(params) | POST /v1/coverage | Mostra quanto de uma região já buscamos e quantos lugares temos lá. Veja Cobertura. |
usage.get() | GET /v1/usage | Créditos usados e restantes, no dia e no mês. |
webhooks.create(params) | POST /v1/webhooks | Adiciona um endpoint. O segredo de assinatura dele aparece só nessa resposta. Veja Webhooks. |
webhooks.list() | GET /v1/webhooks | Os seus endpoints, e se cada um está ligado. |
webhooks.update({ id, … }) | POST /v1/webhooks/{id} | Muda os eventos ou a anotação de um endpoint, ou desliga e liga. |
webhooks.test({ id }) | POST /v1/webhooks/{id}/test | Manda um evento de teste para um endpoint. |
webhooks.deliveries({ id }) | GET /v1/webhooks/{id}/deliveries | O que mandamos para um endpoint e o que ele respondeu. |
webhooks.delete({ id }) | DELETE /v1/webhooks/{id} | Remove um endpoint e o histórico de entregas dele. |
attributions.list() | GET /v1/attributions | Os projetos de código aberto que usamos. Não precisa de chave. |
Acompanhe a chegada dos lugares novos
Quando os lugares que já salvamos não chegam ao limit, a resposta da busca traz uma collection, a coleta que está buscando o resto. O watchCollection entrega cada lugar novo assim que ele chega:
const result = await gmaps.places.search({
keywords: ["padaria"],
geo: { location: "Curitiba, PR" }
});
if (result.collection) {
for await (const frame of gmaps.watchCollection(result.collection.id)) {
if (frame.type === "place") console.log(frame.data);
}
}- Cada evento tem um
typee os dados dele emdata. Oplacetraz um lugar novo, oprogresstraz as contagens até ali e odonevem por último. Progresso ao vivo lista todos os tipos. Uma coleta grande para de mandar eventosplacedepois de um certo número, então leia os lugares dela comcollections.placesquando ela terminar. - O loop termina sozinho depois do
done. Para parar antes, usebreak. - Se a conexão cair, ele conecta de novo e continua depois do último evento que entregou, então nenhum evento chega duas vezes nem se perde. Depois de quedas demais seguidas, sem nenhum evento entre elas, ele lança um
GmapsErrorcomNETWORK_ERROR.
O que o segundo argumento aceita:
| Opção | Padrão | Para que serve |
|---|---|---|
lastEventId | - | Começa depois do evento com este id, para continuar de onde você parou. |
signal | - | Um AbortSignal. Quando ele dispara, o loop termina sem erro. |
retryMs | 2000 | Quantos milissegundos esperar antes de conectar de novo. |
maxRetries | 10 | Quantas quedas seguidas ele aceita antes de desistir. Qualquer evento, até um heartbeat, zera a contagem. |
Você não precisa acompanhar. Dá para fazer a mesma busca mais tarde, quando os lugares novos voltam de graça, ou pedir que a busca espere por eles com wait. Busca explica os dois jeitos.
Quando uma chamada falha
Toda chamada que falha lança um GmapsError. Ele traz o que a API disse, para o seu código saber qual foi o problema:
import { GmapsError } from "gmaps-sdk";
try {
await gmaps.usage.get();
} catch (error) {
if (!(error instanceof GmapsError)) throw error;
console.error(error.status, error.code, error.message);
console.error(`Request id: ${error.requestId}`);
}| Campo | O que traz |
|---|---|
code | O que deu errado, como QUOTA_EXCEEDED. Erros lista todos os códigos. |
status | O status HTTP, ou 0 quando nenhuma resposta chegou. |
message | O que aconteceu e o que fazer agora, em inglês. |
requestId | O nosso id da chamada. Mande junto quando falar com a gente. |
retryAfter | Quantos segundos esperar antes de tentar de novo, quando a API informa. |
details | Mais sobre o problema, como o scope num erro de cota. |
messageKey | Com params: um nome fixo para a mensagem e os valores dela, para o seu app mostrar a mensagem no idioma dele. |
Quatro códigos vêm do próprio SDK, quando não há uma resposta que ele consiga usar:
TIMEOUT: a API não respondeu dentro dotimeout.NETWORK_ERROR: não deu para chegar à API, ou a resposta foi cortada no meio.INVALID_RESPONSE: o status veio como sucesso, mas o corpo não era uma resposta da API: veio semdata, com umerrorou nem era JSON. Quase sempre falta o/v1nobaseUrl.HTTP_ERROR: veio um erro fora do nosso formato, quase sempre de algo no caminho entre você e a API.
Se você cancelar uma chamada com o seu próprio signal, o SDK lança o reason dele no lugar do erro. Opções que não funcionam, como um baseUrl que não é http nem https, dão erro assim que você cria o cliente.
Valide um webhook
O verifyWebhook diz se uma requisição veio mesmo da gente. Passe o segredo de assinatura do endpoint, o corpo bruto e o cabeçalho x-gmaps-signature:
import { verifyWebhook } from "gmaps-sdk";
const body = await request.text();
const signed = await verifyWebhook(
process.env.GMAPS_WEBHOOK_SECRET,
body,
request.headers.get("x-gmaps-signature"),
);
if (!signed) return new Response(null, { status: 400 });
const event = JSON.parse(body);Ele devolve false para qualquer falha: sem cabeçalho, outro segredo, corpo alterado ou assinatura com mais de 5 minutos. Leia o corpo como texto e só faça o parse do JSON depois de validar: fazer o parse e serializar de novo muda os bytes. Webhooks explica o resto.
Tipos
Toda resposta é tipada, e cada tipo de resposta é exportado pelo nome para você usar no seu código:
import type { CollectionDto, PlaceDto, SearchResultDto } from "gmaps-sdk";Os parâmetros de cada método também são exportados, como o SearchPlacesParams, para você tipar uma chamada antes de fazê-la:
import type { SearchPlacesParams } from "gmaps-sdk";
const ask: SearchPlacesParams = {
keywords: ["padaria"],
geo: { location: "Curitiba, PR" }
};Onde roda
- Node 22 ou mais novo, Bun, ou qualquer ambiente com
fetch,AbortSignal.anyeAbortSignal.timeout. - É um módulo ES e já vem com as declarações de tipo. Carregue com
import. - Não tem dependências. As chamadas usam o
fetchdo próprio ambiente, e overifyWebhookusa a Web Crypto. - Também roda no navegador, mas deixe sua chave num servidor. No navegador, qualquer pessoa consegue ler a chave.