Skip to content

Inspect site analytics

import { Aside, Steps, Tabs, TabItem } from ‘@astrojs/starlight/components’;

This guide is the practical companion to Concepts → Analytics. Use it when you want to:

  • Pull a fresh analytics snapshot from your CI / dashboard / Slack bot.
  • Embed the first-party tracker in a non-template site.
  • Wire the dashboards into a custom UI.
  • A Melbora Website with status: "active".

  • A vc_pat_* Personal Access Token. Either an admin token (sees every site) or a client token (sees only the websites it owns).

  • The SDK installed:

    ```sh npm install @vantageconnections/sdk ``` ```sh pnpm add @vantageconnections/sdk ```

vc.analytics.getSeo(websiteId, range) returns the composed SEO dashboard payload — health-scan snapshot, GEO citation rate, Search Console state, citation checklist, competitor diffs, AI quota usage.

import { VantageClient } from "@vantageconnections/sdk";
const vc = new VantageClient({ token: process.env.VANTAGE_TOKEN! });
// Default range is "30d". Valid: "7d" | "30d" | "90d" | "all".
const seo = await vc.analytics.getSeo("wb_01J7XB4MQH", "30d");
console.log("Tier:", seo.tier); // "essentials" | "growth" | "premium" | "premium_plus" | null
console.log("Health score:", seo.healthScan?.score ?? null); // null = no scan yet
console.log(
"GEO citation rate:",
seo.geo.citationRate, // 0..1, or null when totalProbes === 0
`(${seo.geo.citedProbes}/${seo.geo.totalProbes})`
);
if (seo.searchConsole.connected) {
console.log("GSC property:", seo.searchConsole.property);
console.log("GSC last fetched:", seo.searchConsole.fetchedAt);
} else {
console.log("Search Console not connected.");
}
// AI quota — null when the site has no SEO tier set.
if (seo.aiUsage) {
console.log(
"Blog drafts this month:",
`${seo.aiUsage.blogDraft.used} / ${seo.aiUsage.blogDraft.limit}`
);
}
FieldWhat it isWhen it’s null
tierSEO tier — essentials / growth / premium / premium_plus.Null when SEO isn’t enabled on the site.
healthScanMost recent scan result: score, issues, scannedAt.Null when no scan has been run.
geo.citationRateFraction of in-range probes that cited the primary domain.Null when totalProbes === 0.
searchConsole.metricsMost recent GSC pull.Null when GSC isn’t connected.
competitorsURLs tracked + diff activity in the range.Null on tiers below Premium, or when no URLs configured.
aiUsageMonthly counters vs tier quotas.Null when SEO isn’t enabled.

vc.analytics.getTraffic(websiteId, range, interval) returns the traffic dashboard — KPIs, timeline, top breakdowns, bot section.

import { VantageClient } from "@vantageconnections/sdk";
const vc = new VantageClient({ token: process.env.VANTAGE_TOKEN! });
// interval "auto" picks based on range:
// 7d → hourly
// 30d → daily
// 90d, all → weekly
const traffic = await vc.analytics.getTraffic("wb_01J7XB4MQH", "30d", "auto");
if (traffic.empty) {
// Tracker hasn't been installed yet, or no events recorded in range.
console.log("No traffic data yet — embed the tracker first.");
} else {
console.log("Pageviews:", traffic.pageviews);
console.log("Sessions:", traffic.sessions);
console.log("Avg dwell (ms):", traffic.avgDwellMs);
console.log("Bounce rate:", traffic.bounceRate); // 0..1
console.log("Top 5 pages:");
for (const p of traffic.topPages.slice(0, 5)) {
console.log(` ${p.path} ${p.pageviews} views ${p.avgDwellMs}ms avg`);
}
console.log("Top referrers:");
for (const r of traffic.topReferrers.slice(0, 5)) {
console.log(` ${r.referrer} ${r.sessions} sessions`);
}
console.log("Bot pageviews:", traffic.bots.pageviews);
}
RangeAuto-resolved intervalBuckets returned
7dhourlyup to 168
30ddailyup to 30
90dweeklyup to ~13
allweeklybounded by the 90-day event TTL

You can override interval explicitly (e.g. daily over a 7d window if you want a coarser view). The server returns the interval it actually applied in the response so the UI can format the timeline axis correctly.

The Melbora website-template ships with the tracker pre-installed — if you’re porting an existing site (see Port an existing repo) or running the tracker on a non-Vantage-hosted page, install it manually.

The tracker POSTs AnalyticsIngestPayload events via navigator.sendBeacon() to https://<your-api-host>/public/v1/c. The payload shape is documented in Concepts → Analytics; the only required fields are websiteId, sessionId, path, referrer, dwell, and event.

<!-- Drop into the <head> of every page you want to track. -->
<script>
(function () {
var API = "https://qpcjiogcm2.execute-api.us-east-1.amazonaws.com";
var WEBSITE_ID = "wb_01J7XB4MQH"; // your websiteId
var INGEST = API + "/public/v1/c";
// Daily-rotated session id. Stored in sessionStorage so the same
// tab keeps one id across navigations; rotates on the next UTC day.
function getSessionId() {
var key = "vc_sid";
var today = new Date().toISOString().slice(0, 10);
var raw = sessionStorage.getItem(key);
if (raw) {
try {
var p = JSON.parse(raw);
if (p.day === today) return p.id;
} catch (e) {}
}
var id = crypto.randomUUID();
sessionStorage.setItem(key, JSON.stringify({ id: id, day: today }));
return id;
}
// Best-effort 2-letter country from browser locale.
function getCountry() {
try {
var r = new Intl.Locale(navigator.language).region;
if (r && /^[A-Z]{2}$/.test(r)) return r;
} catch (e) {}
return undefined;
}
function send(event, dwell) {
var body = JSON.stringify({
websiteId: WEBSITE_ID,
sessionId: getSessionId(),
path: location.pathname,
referrer: document.referrer || null,
dwell: dwell || 0,
event: event,
country: getCountry(),
});
// sendBeacon survives page-unload; falls back to fetch for older browsers.
if (navigator.sendBeacon) {
navigator.sendBeacon(INGEST, body);
} else {
fetch(INGEST, { method: "POST", body: body, keepalive: true });
}
}
var pageviewAt = Date.now();
send("pageview", 0);
// Fire "pageend" with dwell on tab-hide / unload. pagehide is the
// bfcache-safe choice; visibilitychange catches tab switches.
function flush() {
send("pageend", Date.now() - pageviewAt);
}
addEventListener("pagehide", flush);
document.addEventListener("visibilitychange", function () {
if (document.visibilityState === "hidden") flush();
});
})();
</script>

Replace WEBSITE_ID with the wb_* id of the Website you want to attribute traffic to. The API host is the production Melbora API; for a custom deployment, point at your own.

After embedding, open the site in a browser, navigate around, then:

const traffic = await vc.analytics.getTraffic("wb_01J7XB4MQH", "7d");
console.log(traffic.empty, traffic.pageviews);
// → false, <count> if the tracker is firing

If empty: true persists after a real pageview, check the browser’s network tab for /public/v1/c. A net::ERR_BLOCKED_BY_CLIENT means an ad blocker is dropping it — the tracker is working, the blocker is just doing its job.

If you’re not on Node, the SDK methods map 1:1 to:

Terminal window
# SEO / GEO snapshot
curl -H "Authorization: Bearer vc_pat_..." \
"https://qpcjiogcm2.execute-api.us-east-1.amazonaws.com/v1/websites/wb_01J7XB4MQH/analytics/seo?range=30d"
# Traffic dashboard
curl -H "Authorization: Bearer vc_pat_..." \
"https://qpcjiogcm2.execute-api.us-east-1.amazonaws.com/v1/websites/wb_01J7XB4MQH/analytics?range=30d&interval=auto"

Both endpoints honor the same auth as the SDK (admin OR the website’s owning client). 401 means the token is missing/invalid; 404 means the website doesn’t exist or isn’t accessible to your token.

SymptomLikely cause
getTraffic returns empty: true despite real pageviewsTracker not embedded on the page, or WEBSITE_ID mismatches the website you’re querying. Check the network tab for /public/v1/c 200s.
seo.healthScan is nullNo scan has been run yet. Trigger with vantage seo scan <websiteId>.
seo.geo.citationRate is nullNo GEO probes in the window. Run a probe with vantage seo geo-probe <websiteId> --prompt "...".
seo.searchConsole.metrics is nullGSC OAuth not granted, or the metrics cron hasn’t run since the grant. Connect GSC in the portal, wait one cron cycle.
seo.aiUsage is nullSEO isn’t enabled. Run vantage seo set-tier <websiteId> --tier growth.
Country breakdown is mostly ZZTracker isn’t sending country, and the request isn’t flowing through CloudFront/Vercel edge — fall through to the unknown sentinel. Add country to the tracker payload.