api
GET /api/audits/:id/google
Fetch the organic-traffic snapshot for an audit, pulled from linked GA4 + GSC. Returns sessions, top queries, indexed pages. Cached 24h.
What this endpoint does
GET /api/audits/:id/google returns the organic-traffic snapshot for an audit, pulled from the user’s linked Google Analytics 4 property and Google Search Console site.
- Returns
{ "connected": false }when the user hasn’t linked Google yet. - Returns
{ "connected": true, "linked": false }when Google is connected but the audit’s URL has no GA4 property or GSC site mapped to it. - When both connected and linked, returns the full snapshot: GA4 traffic, GSC queries, and indexing extras.
- Cached server-side for 24 hours per
(user_id, url), so repeat calls within a day are cheap and idempotent. - Pro-only and gated on Google OAuth scope: returns
403if the token lacks Google access or the user’s plan doesn’t include the integration.
Why it matters
Audits without traffic data tell agents what to fix, not what to fix first. The organic-traffic snapshot lets an agent rank failing pages by real organic value so the recommendations land on URLs that actually move revenue.
Concrete workflows:
- A “where do I start?” agent calls
GET /api/audits/:idfor findings andGET /api/audits/:id/googlefor sessions, then drafts a fix list ordered by 28-day sessions per landing page. - A query-gap agent reads
gsc.top_queries(high impressions, low CTR) and rewrites the matching page’s title and meta description to lift CTR.
How to use it
The user must connect Google Analytics 4 and Search Console inside the MetricSpot dashboard first, then explicitly map each audit URL to a GA4 property and a GSC site. When unmapped, the endpoint still succeeds with linked: false so agents can degrade gracefully.
Request
GET /api/audits/12345/google HTTP/1.1
Host: app.metricspot.com
Authorization: Bearer ms_live_xxxxxxxxxxxxxxxxxxxxxxxx
curl
curl https://app.metricspot.com/api/audits/12345/google \
-H "authorization: Bearer ms_live_xxxxxxxxxxxxxxxxxxxxxxxx"
Node fetch
const res = await fetch("https://app.metricspot.com/api/audits/12345/google", {
headers: { authorization: "Bearer ms_live_xxxxxxxxxxxxxxxxxxxxxxxx" },
});
const data = await res.json();
if (!data.connected) {
console.log("Link Google at app.metricspot.com/settings/integrations");
} else if (!data.linked) {
console.log("Map this URL to a GA4 property and GSC site in the dashboard");
} else {
console.log(`28d sessions: ${data.ga4?.sessions_28d}`);
for (const q of data.gsc?.top_queries?.slice(0, 5) ?? []) {
const ctr = q.impressions ? q.clicks / q.impressions : 0;
console.log(`${q.query}: CTR ${(ctr * 100).toFixed(1)}%`);
}
}
Python httpx
import httpx
r = httpx.get(
"https://app.metricspot.com/api/audits/12345/google",
headers={"authorization": "Bearer ms_live_xxxxxxxxxxxxxxxxxxxxxxxx"},
timeout=30.0,
)
data = r.json()
if not data.get("connected"):
print("Link Google at app.metricspot.com/settings/integrations")
elif not data.get("linked"):
print("Map this URL to a GA4 property and GSC site")
else:
print("Sessions 28d:", (data.get("ga4") or {}).get("sessions_28d"))
Response
Connected and linked
200 OK:
{
"connected": true,
"linked": true,
"ga4": {
"sessions_28d": 14328,
"sessions_trend": [
{ "date": "2026-04-16", "sessions": 482 },
{ "date": "2026-04-17", "sessions": 511 }
],
"top_landing_pages": [
{ "url": "https://example.com/", "sessions": 5104 },
{ "url": "https://example.com/pricing", "sessions": 2871 }
]
},
"gsc": {
"top_queries": [
{ "query": "example pricing", "clicks": 612, "impressions": 8412, "position": 4.2 },
{ "query": "example alternative", "clicks": 387, "impressions": 5101, "position": 7.8 }
],
"indexing": {
"indexed_pages": 184,
"coverage_issues": 3
}
},
"ga_property_used": {
"property_id": "properties/123456789",
"display_name": "Example - GA4"
},
"gsc_site_used": {
"site_url": "https://example.com/"
}
}
Fields:
connected:trueif the account has a live Google OAuth grant.falseand the rest of the fields are omitted if no grant exists.linked:trueif this audit’s URL has a GA4 property or GSC site mapped to it.falsemeans the user must map it in the dashboard.ga4.sessions_28d: total organic sessions in the last 28 days.ga4.sessions_trend: per-day series of organic sessions, oldest first.ga4.top_landing_pages: array of{ url, sessions }, sorted by sessions desc.gsc.top_queries: array of{ query, clicks, impressions, position }, sorted by clicks desc.gsc.indexing: optional object withindexed_pagesandcoverage_issuesfrom GSC.ga_property_used/gsc_site_used: the mapping the audit used, for display.
Connected but not linked
{ "connected": true, "linked": false }
Not connected
{ "connected": false }
Re-auth required
When the stored OAuth token can’t be refreshed (revoked or expired beyond refresh), the connection row is deleted and the endpoint returns:
{ "connected": false, "reason": "reauth_required" }
Common errors
| Code | When | Action |
|---|---|---|
UNAUTHORIZED (401) | Missing or invalid Bearer token | Mint a key at app.metricspot.com/settings/api-keys |
FORBIDDEN (403) | Plan doesn’t include Google integrations, or audit belongs to another account | Upgrade to Pro, or use the owning account |
AUDIT_NOT_FOUND (404) | Audit id doesn’t exist | Pass an audit you own |
INVALID_URL (400) | :id not a positive integer | Pass an integer id |
UPSTREAM_FAILED (5xx) | GA4 or GSC API outage | Retry; the response is cached 24h once successful |
Frequently asked questions
Why is linked: false even though I see traffic in GA4?
The link is per-URL on the MetricSpot side. After connecting Google at app.metricspot.com/settings/integrations, you also have to map each audit URL to a specific GA4 property and GSC site. MetricSpot does this because one Google account often has many GA4 properties; explicit mapping prevents picking the wrong one.
Why a 28-day window?
It matches how GSC reports its top queries by default and is short enough to reflect recent changes (post-deploy, post-launch) while long enough to smooth weekly seasonality. The window is not configurable in v1.
How fresh is the data?
GA4 data is typically 24 to 48 hours behind real time, per Google’s processing window. GSC data lags 2 to 3 days. The endpoint caches successful responses 24 hours per (user_id, url), so repeat calls in the same day return the same snapshot.
Does this consume audit allowance?
No. This endpoint is unmetered beyond the user’s plan and Google API quota. The 24-hour cache also means a CI loop hammering this endpoint won’t blow through Google’s daily limits.
Sources
Last updated 2026-05-14