geogridapi 0.0.0-stage → 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +179 -2
- package/dist/index.cjs +540 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +560 -0
- package/dist/index.d.ts +560 -0
- package/dist/index.js +495 -0
- package/dist/index.js.map +1 -0
- package/package.json +77 -4
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Localnord, LLC (GeoGridAPI)
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
CHANGED
|
@@ -1,3 +1,180 @@
|
|
|
1
|
-
#
|
|
1
|
+
# GeoGridAPI Node.js SDK
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
[](https://www.npmjs.com/package/geogridapi)
|
|
4
|
+
[](LICENSE)
|
|
5
|
+
|
|
6
|
+
The official Node.js and TypeScript client for [GeoGridAPI](https://geogridapi.io), the geo-grid rank tracking API for Google Maps (local SEO).
|
|
7
|
+
|
|
8
|
+
Send a Google Business Profile and a keyword: GeoGridAPI measures the listing's rank in the Google Maps results at every point of a grid around it, and returns the rank matrix, the competitors ranking ahead, the change since the previous grid and a PNG heatmap. 1 credit = 1 grid point.
|
|
9
|
+
|
|
10
|
+
- Fully typed, zero dependencies, ESM and CommonJS
|
|
11
|
+
- Node.js 20+, Bun, Deno, Cloudflare Workers and edge runtimes (uses `fetch` and Web Crypto)
|
|
12
|
+
- Automatic retries with an idempotency key: a retried request never creates or charges a second grid
|
|
13
|
+
- `grids.run()` starts a grid and waits for the result
|
|
14
|
+
- Webhook signature verification
|
|
15
|
+
|
|
16
|
+
## Install
|
|
17
|
+
|
|
18
|
+
```bash
|
|
19
|
+
npm install geogridapi
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
## Quickstart
|
|
23
|
+
|
|
24
|
+
Create an account on [geogridapi.io](https://geogridapi.io) (500 free credits), then an API key in the console. A `gga_live_` key measures real grids and spends credits. A `gga_test_` key returns free sample grids, handy while you build.
|
|
25
|
+
|
|
26
|
+
```ts
|
|
27
|
+
import { GeoGridAPI } from "geogridapi";
|
|
28
|
+
|
|
29
|
+
const gga = new GeoGridAPI(); // reads process.env.GEOGRIDAPI_API_KEY
|
|
30
|
+
|
|
31
|
+
const grid = await gga.grids.run({
|
|
32
|
+
query: "Joe's Pizza, New York", // or place_id: "ChIJ…"
|
|
33
|
+
keyword: "pizza",
|
|
34
|
+
size: 7, // 7 × 7 = 49 credits
|
|
35
|
+
radius_km: 3,
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
console.log(grid.summary);
|
|
39
|
+
// { average_rank: 4.3, average_rank_with_misses: 4.6, top3_points: 28, found_points: 48 }
|
|
40
|
+
console.log(grid.ranks); // 7 × 7 matrix, row 0 = north, null = not in the top 20
|
|
41
|
+
console.log(grid.competitors); // who ranks ahead
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
A grid takes 20 seconds to 2 minutes. `grids.run()` polls until it is `completed` or `failed` (a failed grid is refunded). To start it without waiting, use `grids.create()` then `grids.get()`, or pass a `webhook_url`.
|
|
45
|
+
|
|
46
|
+
## Usage
|
|
47
|
+
|
|
48
|
+
```ts
|
|
49
|
+
// Find a listing and its place_id (free)
|
|
50
|
+
const { data: places } = await gga.places.search("Joe's Pizza, New York");
|
|
51
|
+
|
|
52
|
+
// Exact cost before running (free)
|
|
53
|
+
const estimate = await gga.grids.estimate({ place_id: places[0].place_id, keyword: "pizza", shape: "circle", density: "medium" });
|
|
54
|
+
console.log(estimate.credits, estimate.balance_after);
|
|
55
|
+
|
|
56
|
+
// Start a grid without waiting
|
|
57
|
+
const pending = await gga.grids.create({ place_id: "ChIJ…", keyword: "pizza", webhook_url: "https://example.com/hooks/geogrid" });
|
|
58
|
+
const grid = await gga.grids.wait(pending.id);
|
|
59
|
+
|
|
60
|
+
// PNG heatmap of a completed grid (free)
|
|
61
|
+
import { writeFile } from "node:fs/promises";
|
|
62
|
+
await writeFile("map.png", await gga.grids.image(grid.id, { lang: "en" }));
|
|
63
|
+
|
|
64
|
+
// Ranking trend of the same map over time (free)
|
|
65
|
+
const history = await gga.history.get({ place_id: "ChIJ…", keyword: "pizza" });
|
|
66
|
+
console.log(history.change?.average_rank); // negative = better
|
|
67
|
+
|
|
68
|
+
// Up to 50 grids in one call: one listing × several keywords, or several listings × one keyword
|
|
69
|
+
const batch = await gga.batches.run({
|
|
70
|
+
size: 5,
|
|
71
|
+
items: [
|
|
72
|
+
{ place_id: "ChIJ…", keyword: "pizza" },
|
|
73
|
+
{ place_id: "ChIJ…", keyword: "pizza delivery" },
|
|
74
|
+
],
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
// Repeat a grid every week or every month
|
|
78
|
+
const schedule = await gga.schedules.create({ grid_id: grid.id, frequency: "weekly", weekday: 1 });
|
|
79
|
+
await gga.schedules.update(schedule.id, { status: "paused" });
|
|
80
|
+
|
|
81
|
+
// Every grid, page after page
|
|
82
|
+
for await (const g of gga.grids.listAll({ keyword: "pizza" })) console.log(g.id, g.summary?.average_rank);
|
|
83
|
+
|
|
84
|
+
// Credits left
|
|
85
|
+
const { points } = await gga.balance.get();
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
### Grid formats
|
|
89
|
+
|
|
90
|
+
| Parameter | Values |
|
|
91
|
+
|---|---|
|
|
92
|
+
| `shape` | `square` (default), `circle`, `diamond`, `hexagon`, `cross`, `ellipse`, `rectangle` |
|
|
93
|
+
| `size` | Square only: `3`, `5`, `7` (default) or `9` → 9, 25, 49 or 81 credits |
|
|
94
|
+
| `density` | Other shapes: `low`, `medium` (default) or `high`, 13 to 49 points |
|
|
95
|
+
| `radius_km` | 0.5 to 30 km from center to edge (other shapes: from 1 km) |
|
|
96
|
+
| `language`, `country` | 2-letter codes |
|
|
97
|
+
|
|
98
|
+
Points that fall at sea are neither measured nor charged.
|
|
99
|
+
|
|
100
|
+
## Errors
|
|
101
|
+
|
|
102
|
+
Every API error is a `GeoGridAPIError` with `status`, `code` and `message`, and a subclass per status:
|
|
103
|
+
|
|
104
|
+
```ts
|
|
105
|
+
import { InsufficientCreditsError, NotFoundError, RateLimitError } from "geogridapi";
|
|
106
|
+
|
|
107
|
+
try {
|
|
108
|
+
await gga.grids.run({ query: "Unknown place", keyword: "pizza" });
|
|
109
|
+
} catch (error) {
|
|
110
|
+
if (error instanceof InsufficientCreditsError) console.log("Top up at https://geogridapi.io");
|
|
111
|
+
else if (error instanceof NotFoundError) console.log(error.code); // place_not_found
|
|
112
|
+
else throw error;
|
|
113
|
+
}
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
| Class | Status |
|
|
117
|
+
|---|---|
|
|
118
|
+
| `InvalidRequestError` | 400 |
|
|
119
|
+
| `AuthenticationError` | 401 |
|
|
120
|
+
| `InsufficientCreditsError` | 402 |
|
|
121
|
+
| `NotFoundError` | 404 |
|
|
122
|
+
| `ConflictError` | 409 (`grid_not_ready`, `already_scheduled`…) |
|
|
123
|
+
| `UnprocessableEntityError` | 422 (`schedule_limit`) |
|
|
124
|
+
| `RateLimitError` | 429 |
|
|
125
|
+
| `APIError` | 5xx |
|
|
126
|
+
| `APIConnectionError` | network error or timeout |
|
|
127
|
+
| `WaitTimeoutError` | `run()`/`wait()` gave up; the grid keeps running |
|
|
128
|
+
|
|
129
|
+
Network errors, 429 and 5xx are retried twice with backoff (`Retry-After` is honored). Grid and batch creations send an `Idempotency-Key`, so these retries never create or charge a second grid. The key is new for each call: when your own code retries a creation (a queue job, a cron), pass your own `idempotencyKey`, for example an order or job id:
|
|
130
|
+
|
|
131
|
+
```ts
|
|
132
|
+
await gga.grids.run({ place_id: "ChIJ…", keyword: "pizza" }, { idempotencyKey: `weekly-report-${jobId}` });
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
While `run()` or `wait()` waits, network errors, 429 and 5xx are waited out until the timeout. If waiting still fails, the error carries `gridId` (or `batchId`): the grid exists, read it later with `grids.get()` instead of running it again. A failed grid has `status: "failed"` and `error: { code, message }`.
|
|
136
|
+
|
|
137
|
+
Set `maxRetries` and `timeoutMs` on the client or per call.
|
|
138
|
+
|
|
139
|
+
## Webhooks
|
|
140
|
+
|
|
141
|
+
When a grid ends, GeoGridAPI posts a `grid.completed` or `grid.failed` event to its `webhook_url`, signed with your `whsec_` secret (console → Webhook). Verify it against the raw body:
|
|
142
|
+
|
|
143
|
+
```ts
|
|
144
|
+
import { constructEvent } from "geogridapi";
|
|
145
|
+
|
|
146
|
+
// Express: app.post("/hooks/geogrid", express.raw({ type: "application/json" }), handler)
|
|
147
|
+
async function handler(req, res) {
|
|
148
|
+
try {
|
|
149
|
+
const event = await constructEvent(req.body, req.get("GeoGridAPI-Signature"), process.env.GEOGRIDAPI_WEBHOOK_SECRET);
|
|
150
|
+
if (event.type === "grid.completed") console.log(event.data.summary);
|
|
151
|
+
res.sendStatus(200);
|
|
152
|
+
} catch {
|
|
153
|
+
res.sendStatus(400);
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
`verifyWebhookSignature()` returns a boolean instead. Signatures older than 5 minutes are refused (`toleranceSeconds`).
|
|
159
|
+
|
|
160
|
+
## Configuration
|
|
161
|
+
|
|
162
|
+
```ts
|
|
163
|
+
const gga = new GeoGridAPI({
|
|
164
|
+
apiKey: "gga_live_…", // default: process.env.GEOGRIDAPI_API_KEY
|
|
165
|
+
timeoutMs: 60_000, // per HTTP request
|
|
166
|
+
maxRetries: 2,
|
|
167
|
+
fetch: customFetch, // optional
|
|
168
|
+
});
|
|
169
|
+
```
|
|
170
|
+
|
|
171
|
+
## Also available
|
|
172
|
+
|
|
173
|
+
- **MCP server** for Claude, ChatGPT, Cursor and any MCP client: `https://api.geogridapi.io/mcp`
|
|
174
|
+
- **Python**: `pip install geogridapi`, and `pip install langchain-geogridapi` for LangChain agents
|
|
175
|
+
- **No-code**: n8n node, Apify Actor, Make, Zapier ([guide](https://geogridapi.io/en/docs/no-code))
|
|
176
|
+
- [API reference](https://geogridapi.io/en/docs) · [OpenAPI specification](https://geogridapi.io/openapi.json)
|
|
177
|
+
|
|
178
|
+
## License
|
|
179
|
+
|
|
180
|
+
[MIT](LICENSE)
|