How to Connect Your AI Assistant to Google Search Console and Analytics

How to Connect Your AI Assistant to Google Search Console and Analytics
Photo by Myriam Jessier / Unsplash

I wanted Claude to pull real Search Console and GA4 data instead of me screenshot-ting graphs into a chat every time. Turns out it's a five-minute setup once you know where the permission actually lives — most of the friction is that it's split across two completely separate systems (GCP and the product itself), and skipping either one just gets you a silent empty result, not an error. Same setup covers both GSC and GA4, you just have to grant access twice.

Setup

  1. Pick a GCP project. console.cloud.google.com → project switcher top-left → New Project (or reuse one you already have).
  2. Enable the APIs. APIs & Services → Library → search "Google Search Console API" → Enable. Do the same for "Google Analytics Admin API" and "Google Analytics Data API" if you also want GA4.
  3. Create a service account. IAM & Admin → Service Accounts → Create Service Account. No IAM roles needed on the project itself — permission comes from Search Console/GA4, not GCP IAM.
  4. Generate a key. Open the service account → Keys tab → Add Key → Create new key → JSON. Downloads a file, keep it out of git.
  5. Add the service account inside Search Console. search.google.com/search-console → pick the property → Settings → Users and permissions → Add user → paste the service account's email ([email protected]) → permission level Full. This is the step people forget — the GCP-side setup alone grants zero access, Search Console keeps its own separate permission list.
  6. Add the same service account inside GA4. analytics.google.com → Admin → Property Access Management → Add users → same email → role Viewer is enough for reading. Completely separate grant from step 5 — having GSC access tells you nothing about GA4 access, and vice versa. One property can have GSC wired up and GA4 not, or the other way around.

Or you could just do it like this: if you already have gcloud installed and authenticated, steps 1–4 collapse into a handful of commands —

gcloud projects create my-project-id
gcloud services enable searchconsole.googleapis.com analyticsadmin.googleapis.com analyticsdata.googleapis.com --project=my-project-id
gcloud iam service-accounts create gsc-reader --project=my-project-id
gcloud iam service-accounts keys create creds.json \
  [email protected]

Steps 5 and 6 still can't be skipped either way — those permissions live inside Search Console's and GA4's own systems, not GCP IAM, so there's no gcloud command for either one.

Querying Search Console

from google.oauth2 import service_account
from googleapiclient.discovery import build

creds = service_account.Credentials.from_service_account_file(
    "creds.json", scopes=["https://www.googleapis.com/auth/webmasters.readonly"])
service = build("searchconsole", "v1", credentials=creds)

body = {
    "startDate": "2026-04-27",
    "endDate": "2026-07-25",
    "dimensions": ["query"],
    "rowLimit": 25,
}
r = service.searchanalytics().query(siteUrl="sc-domain:example.com", body=body).execute()
# rows come back sorted by impressions, highest first

Same service object also gives you sitemaps().list(), urlInspection().index().inspect() for per-URL indexing status, and searchanalytics().query() sliced by page instead of query. Swap the scope to the full (non-readonly) webmasters one if you need to submit sitemaps instead of just reading.

Querying GA4

Different scope, different discovery service, different creds object — same JSON key works for both since it's the same service account.

creds = service_account.Credentials.from_service_account_file(
    "creds.json", scopes=["https://www.googleapis.com/auth/analytics.readonly"])

# admin API: which properties can this service account even see
admin = build("analyticsadmin", "v1beta", credentials=creds)
admin.accountSummaries().list().execute()
# gives you the property IDs (properties/123456789) you need for the data call below

# data API: the actual metrics
from google.analytics.data_v1beta import BetaAnalyticsDataClient
from google.analytics.data_v1beta.types import RunReportRequest, DateRange, Dimension, Metric

client = BetaAnalyticsDataClient(credentials=creds)
report = client.run_report(RunReportRequest(
    property="properties/123456789",
    date_ranges=[DateRange(start_date="2026-04-27", end_date="2026-07-25")],
    dimensions=[Dimension(name="pagePath")],
    metrics=[Metric(name="activeUsers"), Metric(name="sessions")],
))

accountSummaries().list() is worth running first any time you're not sure the grant went through — it's the fastest way to check "can this service account actually see anything yet" before debugging the real query.

Why I set this up

  • Real numbers, not screenshots. Claude can pull the actual rows instead of me describing a chart to it.
  • It catches things I'd skim past. First time I ran this on a live site, the query list had a handful of completely unrelated foreign-language queries mixed in — looked exactly like a hacked-site keyword-injection pattern. Turned out to be a much more boring bug (a route not 404ing properly on garbage URLs), but I'd have never gone looking without the raw query data in front of me.
  • Read-only by default. The .readonly scopes mean an AI assistant poking around can't accidentally submit or change anything — worth defaulting to that unless you specifically need to push a sitemap.
  • One service account, every site. Don't create a new one per property — just add the same service account email as a user in each new site's Search Console/GA4 admin. Way less key-juggling.

Downside is there's no API for "request indexing" on a URL in Search Console — that action only exists as a button in the UI (there's a separate Indexing API, but it's restricted to job-posting/livestream markup, doesn't apply to normal pages). And on the GA4 side, both the Admin API and the property itself need enabling/granting separately per project — easy to enable the API and forget the admin-panel user add, which silently gets you zero rows instead of an error. So this setup is great for reading data and spotting problems, but you'll still end up back in the browser for a few specific things.

(If you're setting up GA4 for ecommerce tracking rather than API access, I've got older notes on Enhanced Ecommerce implementation too.)

Source

Subscribe to Building software. Writing what I learn.

Don’t miss out on the latest issues. Sign up now to get access to the library of members-only issues.
[email protected]
Subscribe