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 403 if 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/:id for findings and GET /api/audits/:id/google for 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: true if the account has a live Google OAuth grant. false and the rest of the fields are omitted if no grant exists.
  • linked: true if this audit’s URL has a GA4 property or GSC site mapped to it. false means 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 with indexed_pages and coverage_issues from 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

CodeWhenAction
UNAUTHORIZED (401)Missing or invalid Bearer tokenMint a key at app.metricspot.com/settings/api-keys
FORBIDDEN (403)Plan doesn’t include Google integrations, or audit belongs to another accountUpgrade to Pro, or use the owning account
AUDIT_NOT_FOUND (404)Audit id doesn’t existPass an audit you own
INVALID_URL (400):id not a positive integerPass an integer id
UPSTREAM_FAILED (5xx)GA4 or GSC API outageRetry; 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