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 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
- # Temporary Holding Version
1
+ # GeoGridAPI Node.js SDK
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ [![npm](https://img.shields.io/npm/v/geogridapi.svg)](https://www.npmjs.com/package/geogridapi)
4
+ [![license](https://img.shields.io/npm/l/geogridapi.svg)](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)