Integration guide
From an empty workspace to a scan you can act on, then into your own systems.
Setting up
- 1
Connect your Google Business Profile
Authorise through Google's OAuth consent screen. We request read access to reviews, posts and insights, and write access to review replies. Nothing else — we cannot change your hours, name or address.
- 2
Set your brand voice
Two sliders and two word lists. This takes about five minutes and every generated reply is bound by it afterwards, so it is worth doing properly rather than accepting defaults.
- 3
Add the search terms you care about
Start with what customers would actually type, not what you would like to rank for. Six to ten terms is plenty for a single location.
- 4
Run your first grid scan
A 7×7 grid at 500m spacing covers most city-centre service areas. Widen the spacing for rural or drive-to businesses.
Your first API call
Create a key in Settings, then confirm it works. Every endpoint on this deployment answers against sample data, so this returns real JSON immediately.
curl "https://api.enterrank.io/v1/overview" \ -H "X-API-Key: erk_live_your_key_here"
Client examples
JavaScript
const res = await fetch("https://api.enterrank.io/v1/scans?keyword_id=kw_02", {
headers: { "X-API-Key": process.env.ENTERRANK_KEY },
});
const { data } = await res.json();
// Points where you are not in the top 3 — the ones worth doing something about.
const weak = data.points.filter((p) => p.rank === null || p.rank > 3);
console.log(`${weak.length} of ${data.points.length} points need work`);PHP
$ch = curl_init("https://api.enterrank.io/v1/reviews?status=needs_reply");
curl_setopt($ch, CURLOPT_HTTPHEADER, ["X-API-Key: " . getenv("ENTERRANK_KEY")]);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
$payload = json_decode(curl_exec($ch), true);
foreach ($payload["data"] as $review) {
echo $review["author"] . " — " . $review["rating"] . "\n";
}Python
import os, requests
r = requests.get(
"https://api.enterrank.io/v1/ai-visibility",
headers={"X-API-Key": os.environ["ENTERRANK_KEY"]},
timeout=10,
)
r.raise_for_status()
for probe in r.json()["data"]:
missed = [x["engine"] for x in probe["results"] if not x["mentioned"]]
if missed:
print(probe["prompt"]["en"], "-> not named by", ", ".join(missed))Verifying webhooks
Compare the signature header against an HMAC of the raw request body. Compare in constant time — a naive string equality check leaks timing information.
import crypto from "node:crypto";
export function verify(rawBody, signatureHeader, secret) {
const expected =
"sha256=" + crypto.createHmac("sha256", secret).update(rawBody).digest("hex");
const a = Buffer.from(expected);
const b = Buffer.from(signatureHeader ?? "");
return a.length === b.length && crypto.timingSafeEqual(a, b);
}