Integrations
TypeScript SDK
Call the API from TypeScript or JavaScript: one typed method per call, plus helpers to watch a collection and check a webhook.
Install it
The package is gmaps-sdk. It has no dependencies of its own.
npm i gmaps-sdkCreate a client
Give it your key, here read from an environment variable. Each method then makes one call and returns what the API answers under data:
import { createClient } from "gmaps-sdk";
const gmaps = createClient({ apiKey: process.env.GMAPS_API_KEY });
const result = await gmaps.places.search({
keywords: ["bakery"],
geo: { location: "Curitiba, PR" }
});
console.log(result.places.map((place) => place.identity.name));What createClient takes:
| Option | Default | What it does |
|---|---|---|
apiKey | - | Your key. Without one, only attributions.list() works. |
baseUrl | https://api.gmaps.dev/v1 | Where the API is, with /v1 at the end. Change it only to call a local API. |
timeout | 30000 | How long each try at a call may take, in milliseconds, including reading the answer. A search with wait always gets its whole wait, and a little more. |
maxRetries | 2 | How many times a failed call is tried again, when that is safe. 0 turns retries off. |
fetch | globalThis.fetch | Your own fetch, for tests or a runtime that has none. |
To give one call a different time limit, or to cancel it, pass { timeoutMs, signal } as its last argument.
const usage = await gmaps.usage.get({ timeoutMs: 5000 });If a call gets no answer, or an error on our side (a 5xx), the SDK sends it again about 1 second later, and again about 2 seconds after that. It does this only for calls that are safe to run twice: reads, quotes, coverage checks, cancels, and webhook changes and deletes. A search, a new collection, a new export, a new webhook and a webhook test are never sent again this way, because the first try may already have run, and a second could cost credits, send something twice, or make a second export in your list. After a 408, 425 or 429 that asks for a wait of 10 seconds or less, any call waits and goes again.
Every method
Each method takes one object with the same fields as its API call. Methods with no fields, like usage.get(), take only the options.
| Method | API call | What it does |
|---|---|---|
places.search(params) | POST /v1/places/search | Find places by what they are and where they are. See Search. |
places.get({ place }) | GET /v1/places/{place} | Read one place we have saved, by our id, its cid, its place ID or a Maps link. See Places. |
collections.quote(params) | POST /v1/collections/quote | See what collecting an area would cost, without starting anything. |
collections.create(params) | POST /v1/collections | Collect every place in an area. See Collections. |
collections.get({ id }) | GET /v1/collections/{id} | See how far a collection has got and what it has cost. |
collections.list(params?) | GET /v1/collections | Your collections, newest first. |
collections.places({ id }) | GET /v1/collections/{id}/places | The places a collection found, one page at a time. |
collections.cancel({ id }) | DELETE /v1/collections/{id} | Stop a collection. You keep the places that already arrived, and the credits they cost stay spent. |
watchCollection(id, options?) | GET /v1/collections/{id}/stream | Get a collection's new places as they arrive. See below. |
exports.create(params) | POST /v1/exports | Get a link to a collection's places as a csv, json or xlsx file. See Exports. |
exports.get({ id }) | GET /v1/exports/{id} | Read an export again while its link still works. |
coverage.get(params) | POST /v1/coverage | See how much of an area we have searched and how many places we have there. See Coverage. |
usage.get() | GET /v1/usage | Credits used and left, today and this month. |
webhooks.create(params) | POST /v1/webhooks | Add an endpoint. Its secret comes back this once. See Webhooks. |
webhooks.list() | GET /v1/webhooks | Your endpoints, and whether each one is on. |
webhooks.update({ id, … }) | POST /v1/webhooks/{id} | Change an endpoint's events or note, or turn it off and on. |
webhooks.test({ id }) | POST /v1/webhooks/{id}/test | Send a test event to an endpoint. |
webhooks.deliveries({ id }) | GET /v1/webhooks/{id}/deliveries | What we sent to an endpoint and what it answered. |
webhooks.delete({ id }) | DELETE /v1/webhooks/{id} | Remove an endpoint and its delivery log. |
attributions.list() | GET /v1/attributions | The open-source projects we build on. Needs no key. |
Watch new places arrive
When a search can't fill limit with places we have saved, its answer includes a collection that is finding the rest. watchCollection hands you each new place as it arrives:
const result = await gmaps.places.search({
keywords: ["bakery"],
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);
}
}- Each event has a
typeand itsdata.placebrings a new place,progressbrings the counts so far, anddonecomes last. Live progress lists every type. A large collection stops sendingplaceevents after a set number, so read its places withcollections.placesonce it ends. - The loop ends by itself after
done. To stop sooner,breakout of it. - If the connection drops, it connects again and carries on after the last event it gave you, so you never get an event twice or miss one. After too many drops in a row, with no event between them, it throws a
GmapsErrorwithNETWORK_ERROR.
What its second argument takes:
| Option | Default | What it does |
|---|---|---|
lastEventId | - | Start after the event with this id, to carry on where you stopped. |
signal | - | An AbortSignal. When it fires, the loop ends quietly, with no error. |
retryMs | 2000 | How many milliseconds to wait before it connects again. |
maxRetries | 10 | How many drops in a row it allows before it gives up. Any event, even a heartbeat, starts the count again. |
You don't have to watch. You can run the same search again later, when the new places come back for free, or ask the search to wait for them. Search explains both.
When a call fails
Every failed call throws a GmapsError. It carries what the API said, so your code can tell one problem from another:
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}`);
}| Field | What it holds |
|---|---|
code | What went wrong, such as QUOTA_EXCEEDED. Errors lists every code. |
status | The HTTP status, or 0 when no answer arrived. |
message | What happened and what to do next, in English. |
requestId | Our id for the call. Include it when you write to us. |
retryAfter | How many seconds to wait before you try again, when the API says. |
details | More about the problem, such as scope on a quota error. |
messageKey | With params: a fixed name for the message and its values, so your app can show it in its own language. |
Four codes come from the SDK itself, when there is no answer it can use:
TIMEOUT: the API didn't answer withintimeout.NETWORK_ERROR: the API couldn't be reached, or the answer broke off.INVALID_RESPONSE: the status said success, but the body was not an answer from the API: it had nodata, had anerror, or was not JSON. Most often,baseUrlis missing/v1.HTTP_ERROR: an error came back that isn't in our format, most often from something between you and the API.
If you cancel a call with your own signal, the SDK throws the signal's reason instead. Options that can't work, like a baseUrl that isn't http or https, throw as soon as you create the client.
Check a webhook
verifyWebhook tells you whether a request really came from us. Give it your endpoint's secret, the raw body and the x-gmaps-signature header:
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);It returns false for every kind of failure: no header, a different secret, a changed body, or a signature more than 5 minutes old. Read the body as text and parse it only after the check: parsing it and writing it back changes its bytes. Webhooks covers the rest.
Types
Every answer is typed, and each answer type is exported by name for your own code:
import type { CollectionDto, PlaceDto, SearchResultDto } from "gmaps-sdk";Each method's parameters are exported too, such as SearchPlacesParams, so you can type a call before you make it:
import type { SearchPlacesParams } from "gmaps-sdk";
const ask: SearchPlacesParams = {
keywords: ["bakery"],
geo: { location: "Curitiba, PR" }
};Where it runs
- Node 22 or newer, Bun, or any runtime with
fetch,AbortSignal.anyandAbortSignal.timeout. - It's an ES module and ships its own type declarations. Load it with
import. - It has no dependencies. Calls use the runtime's own
fetch, andverifyWebhookuses Web Crypto. - It runs in a browser too, but keep your key on a server. In a browser, anyone can read it.