Read more

Google Search Console API: Complete Developer Guide [2026]

Set up the GSC API with a service account, query 25,000 rows of search data, handle pagination, and automate daily exports with Python and Node.js.

Google Search Console API: The Developer's Complete Guide

The Google Search Console web interface caps queries at 1,000 rows and 16 months of history. For anything beyond a quick check, you hit that wall fast. The Search Console API removes it, up to 25,000 rows per call, full programmatic access, and the ability to build whatever dashboards, alerts, and pipelines your team actually needs.

This guide walks through everything: what the API exposes, how to authenticate properly in 2026, real Python and Node.js code you can copy, pagination, quotas, and the mistakes that waste most developers' first afternoon with it.


What the Search Console API Actually Gives You

The API exposes four main resources:

Resource What it does Typical use
Search Analytics Query clicks, impressions, CTR, position by query, page, country, device, date Dashboards, alerts, data warehouses
URL Inspection Check index status, last crawl date, canonical, mobile usability for any URL QA scripts, indexation monitoring
Sitemaps List, submit, delete sitemaps Deploy pipelines, multi-site management
Sites List verified properties, manage users Agency automation, onboarding

The Search Analytics resource is where 90% of integrations live. Everything in this guide focuses on that, but the auth pattern and client setup carry over to the others.


Before You Write Any Code: Three Things to Know

  1. 25,000 rows per request. That's the hard cap. For larger datasets you paginate with startRow.
  2. Two to three day data lag. The API is not real-time. Yesterday's data usually lands 36-48 hours later.
  3. Data is sampled on high-volume queries. If you ask for every query on a site with millions of impressions, Google will anonymize anything rare. Always export at the page or query level separately, then join, rather than requesting every dimension at once.

If any of those are dealbreakers, the API isn't the right tool. If not, keep going.


Step 1: Enable the API in Google Cloud

  1. Go to console.cloud.google.com
  2. Create a new project (or pick an existing one)
  3. Open APIs & Services > Library
  4. Search for Google Search Console API and click Enable

That's it for the project. The next part, authentication, is where most people get stuck.


Step 2: Create a Service Account (The Right Way)

For any automated pipeline, use a service account rather than OAuth with user consent. Service accounts don't expire, don't require refresh tokens, and work from any server without a browser.

  1. In Google Cloud Console, open IAM & Admin > Service Accounts
  2. Click Create Service Account
  3. Give it a name like gsc-reader, skip the optional roles, click Done
  4. Open the service account, go to the Keys tab
  5. Click Add Key > Create new key > JSON
  6. Save the JSON file somewhere your code can read it

Now the critical step that the Google docs bury: you must add the service account email as a user on every Search Console property you want it to read.

  1. Copy the client_email from the JSON file (looks like gsc-reader@your-project.iam.gserviceaccount.com)
  2. Open Search Console
  3. Select your property, go to Settings > Users and permissions
  4. Click Add user, paste the service account email, pick Restricted access
  5. Repeat for every property the script needs

Skipping this step is the single most common reason "my API call returns 403" questions exist on Stack Overflow.


Step 3: Query Search Analytics with Python

Install the client library:

pip install google-api-python-client google-auth

Then pull the last 30 days of top queries:

from google.oauth2 import service_account
from googleapiclient.discovery import build
from datetime import date, timedelta
import json

SCOPES = ['https://www.googleapis.com/auth/webmasters.readonly']
SERVICE_ACCOUNT_FILE = 'gsc-reader-key.json'
SITE_URL = 'sc-domain:example.com'  # or 'https://example.com/'

credentials = service_account.Credentials.from_service_account_file(
    SERVICE_ACCOUNT_FILE, scopes=SCOPES
)
service = build('searchconsole', 'v1', credentials=credentials)

end = date.today() - timedelta(days=3)
start = end - timedelta(days=30)

request = {
    'startDate': start.isoformat(),
    'endDate': end.isoformat(),
    'dimensions': ['query'],
    'rowLimit': 25000,
    'startRow': 0,
}

response = service.searchanalytics().query(
    siteUrl=SITE_URL, body=request
).execute()

for row in response.get('rows', []):
    print(row['keys'][0], row['clicks'], row['impressions'], row['position'])

A few things worth pointing out:

  • Domain properties use the sc-domain:example.com prefix. URL-prefix properties use the full URL with trailing slash.
  • End date is set to today minus 3 days because fresher data is often incomplete.
  • webmasters.readonly is the correct scope. The name is a legacy holdover from when GSC was called Webmaster Tools.

Step 4: Query Search Analytics with Node.js

Same thing in Node using the official client:

npm install googleapis
import { google } from 'googleapis'

const auth = new google.auth.GoogleAuth({
  keyFile: 'gsc-reader-key.json',
  scopes: ['https://www.googleapis.com/auth/webmasters.readonly'],
})

const searchconsole = google.searchconsole({ version: 'v1', auth })

async function getTopQueries(siteUrl: string) {
  const end = new Date()
  end.setDate(end.getDate() - 3)
  const start = new Date(end)
  start.setDate(start.getDate() - 30)

  const iso = (d: Date) => d.toISOString().slice(0, 10)

  const res = await searchconsole.searchanalytics.query({
    siteUrl,
    requestBody: {
      startDate: iso(start),
      endDate: iso(end),
      dimensions: ['query'],
      rowLimit: 25000,
      startRow: 0,
    },
  })

  return res.data.rows ?? []
}

const rows = await getTopQueries('sc-domain:example.com')
console.log(`Pulled ${rows.length} queries`)

What a Real Response Looks Like

The Search Analytics endpoint returns a shape like this:

{
  "rows": [
    {
      "keys": ["how to use search console"],
      "clicks": 142,
      "impressions": 3821,
      "ctr": 0.0371,
      "position": 7.4
    },
    {
      "keys": ["gsc api tutorial"],
      "clicks": 38,
      "impressions": 612,
      "ctr": 0.0621,
      "position": 4.1
    }
  ],
  "responseAggregationType": "byProperty"
}

The keys array matches the dimensions you requested, in order. Ask for ['query', 'page'] and each row's keys becomes [query, page]. Ask for ['date', 'device', 'country'] and you get three-element arrays. Always write your downstream parser against the dimensions you requested, not a hardcoded index.


Pagination: Getting More Than 25,000 Rows

The row limit is per request, not per day. For bigger pulls you loop with startRow:

def fetch_all_rows(service, site_url, body, page_size=25000):
    all_rows = []
    start_row = 0
    while True:
        body['startRow'] = start_row
        body['rowLimit'] = page_size
        resp = service.searchanalytics().query(
            siteUrl=site_url, body=body
        ).execute()
        rows = resp.get('rows', [])
        if not rows:
            break
        all_rows.extend(rows)
        if len(rows) < page_size:
            break
        start_row += page_size
    return all_rows

Two gotchas:

  1. Stop when you get fewer rows than requested. Google doesn't send an explicit "no more data" signal.
  2. Don't blindly loop forever. Add a max-page safety cap. A bad query can page for hours.

Quotas and Rate Limits You Will Hit

Limit Value What to do
Queries per 100 seconds per project 1,200 Throttle or back off
Queries per day per project 30,000 Batch, cache, don't re-fetch
Rows per request 25,000 Paginate
Dimensions per request 4 Split queries, join client-side

Hitting the per-100-seconds limit returns a 429 with quotaExceeded. The only correct response is exponential backoff. Google's own client library does this for you if you enable it:

from googleapiclient.errors import HttpError
import time

def with_backoff(fn, max_retries=5):
    delay = 1
    for attempt in range(max_retries):
        try:
            return fn()
        except HttpError as e:
            if e.resp.status in (429, 500, 503):
                time.sleep(delay)
                delay *= 2
                continue
            raise
    raise RuntimeError('Backoff exhausted')

Automating Daily Exports

The most common production use case is a nightly pull into a warehouse. The pattern:

  1. Run a cron job at 02:00 UTC
  2. For each property, pull yesterday-minus-3 data at both [query] and [page] dimension granularity (two separate calls — don't combine)
  3. Upsert into BigQuery, Postgres, or Parquet files keyed on (site, date, dimension_key)
  4. Alert on failure via PagerDuty/Slack

A minimal cron-safe Python runner looks like this:

import os, sys, json
from datetime import date, timedelta
from google.oauth2 import service_account
from googleapiclient.discovery import build

SCOPES = ['https://www.googleapis.com/auth/webmasters.readonly']
KEY = os.environ['GSC_KEY_PATH']
SITES = json.loads(os.environ['GSC_SITES'])  # ["sc-domain:a.com", ...]

creds = service_account.Credentials.from_service_account_file(KEY, scopes=SCOPES)
svc = build('searchconsole', 'v1', credentials=creds)

target = (date.today() - timedelta(days=3)).isoformat()

for site in SITES:
    try:
        for dims in (['query'], ['page']):
            body = {'startDate': target, 'endDate': target,
                    'dimensions': dims, 'rowLimit': 25000}
            data = svc.searchanalytics().query(siteUrl=site, body=body).execute()
            # write to warehouse here
    except Exception as exc:
        print(f'FAIL {site}: {exc}', file=sys.stderr)
        sys.exit(1)

URL Inspection API: Checking Indexation at Scale

The URL Inspection API is the programmatic version of the URL Inspection Tool in the web UI. It's rate-limited hard (2,000 URLs per property per day) so it's for targeted checks, not bulk crawls.

body = {
    'inspectionUrl': 'https://example.com/blog/post',
    'siteUrl': 'sc-domain:example.com',
}
resp = svc.urlInspection().index().inspect(body=body).execute()
index_status = resp['inspectionResult']['indexStatusResult']
print(index_status['verdict'])       # PASS / FAIL / PARTIAL
print(index_status['coverageState']) # "Submitted and indexed", etc.
print(index_status['lastCrawlTime'])

Useful for deploy-time QA: after publishing 50 new pages, loop through them the next day and alert on any that aren't indexed yet.


When the API Is the Wrong Answer

Building and maintaining an API pipeline takes real engineering time. For many teams, the ROI is negative.

  • You manage one or two sites and want better reporting? Use the GSC UI plus a tool that handles the data layer for you. HeySeo connects to GSC, handles the 16-month retention problem by storing historical data for you, and lets you query it in plain English.
  • You want long-term historical data? Start exporting to BigQuery via the GSC bulk data export — it's free, runs natively, and sidesteps the 25,000-row cap entirely.
  • You need real-time data? The API can't give you that. No tool can. GSC is always 2-3 days behind.
  • You have engineers and want custom dashboards or ML models on top of search data? The API is the right call. Everything in this guide applies.

Common Errors and What They Actually Mean

Error Real cause
403 User does not have sufficient permission Service account not added as a user on the GSC property
400 Invalid value at 'request.start_date' Date is outside the 16-month retention window, or end date is later than 3 days ago on an empty property
429 quotaExceeded Per-100-seconds limit; back off
404 siteUrl not found Wrong property format — sc-domain: prefix missing, or URL-prefix missing trailing slash
Empty rows array Valid response, just no data for that date/filter combination

Going Further

The GSC API is the foundation, but it's rarely the whole stack. Most teams that start with the API eventually layer:

  • BigQuery for long-term storage beyond 16 months
  • dbt for transforming raw rows into dashboards
  • An AI layer for turning "what changed this week" into a human-readable summary

That last piece is where HeySeo's AI assistant earns its keep. You can ask questions like "which queries dropped more than 5 positions this week" without writing the SQL yourself.

For the basics, see What is Google Search Console. For the non-code version of everything above, read how to use Google Search Console with AI and how to analyze Search Console data with AI.


Frequently Asked Questions

How far back does the Search Console API go? Sixteen months, the same as the web UI. For anything longer you need to export data to your own storage continuously. BigQuery bulk export is the cleanest solution.

Is the API free? Yes. There's no charge for using the GSC API. You're limited by rate and row quotas, not billing.

Do I need OAuth or a service account? Use a service account for any automated pipeline. Use OAuth only if you're building a user-facing tool where each user authenticates with their own GSC account.

Can I combine multiple dimensions in one request? Up to four. But requesting [query, page] will return fewer rows than requesting each separately, because Google anonymizes combinations with low volume. Pull dimensions separately and join client-side for the most complete data.

Why does my row count differ from the web UI? The web UI shows a sampled, deduplicated view. The API returns the raw rows. Small differences between UI totals and API totals are normal and expected.

Can I use the API to submit URLs for indexing? Not with the Search Console API. Indexing submission lives in the separate Indexing API, which is limited to job postings and broadcast events for most sites.


Conclusion

The Search Console API is straightforward once you get past two things: the service account permission quirk (you have to add it as a user inside GSC itself) and the pagination pattern. Everything else is a normal REST API.

Start with a single property, the Python snippet above, and a daily cron. Once that's stable, add pagination, retries, and warehouse storage. Don't build the whole pipeline on day one.

Related Articles


Skip the pipeline-building and try HeySeo free. Connect your GSC property in 30 seconds, keep your data past the 16-month limit, and ask questions of your search data in plain English.

Want to know why your website isn't growing?

Connect your site in 30 seconds. HeySeo tells you exactly what to fix, in plain English. No SEO knowledge required.

Start Free Trial No credit card required