🔑
Google Account
Access to Google Search Console with at least Restricted User permissions on at least one verified property.
☁️
Google Cloud Project
A Google Cloud project (free tier is sufficient). You'll create one in Section 5 if you don't have one.
📋
JavaScript Basics
Apps Script uses JavaScript. Familiarity with variables, functions, and JSON is helpful but all code is fully annotated.

1 What Is the Google Search Console API?

The Google Search Console API is a REST API that provides programmatic, authenticated access to the search performance data available in the Google Search Console interface. It enables SEO teams to retrieve clicks, impressions, click-through rate (CTR), and average position data at the query, page, country, device, and date dimension — without manual CSV exports.

The API is formally called the Google Search Console API v3 (webmasters API v3) and exposes several distinct endpoint categories. The most commonly used is the Search Analytics API, which returns the same underlying data as the Performance report in GSC but with programmatic control over dimensions, filters, date ranges, and row counts.

Why This Matters for SEO Teams: The GSC interface limits data export to 1,000 rows and requires manual date range selection. The API removes both constraints — returning up to 25,000 rows per request, with pagination to access millions of rows, and supporting any date range within the 16-month data window. This unlocks query-level analysis at scale that is impossible through the web interface alone.

The Google Apps Script layer is what makes this accessible without a dedicated server. Apps Script is a cloud-based JavaScript runtime built into Google Workspace — it runs on Google's infrastructure, is triggered on schedules, and outputs directly to Google Sheets. The combination of the Search Console API and Apps Script is effectively a free, serverless SEO data pipeline.

2 What Can You Build?

The following use cases represent the most common and highest-value implementations of the Search Console API and Apps Script combination. Each is covered in this guide with working code.

Use Case 01
Automated Weekly Report
Pull the last 7 days of top queries, pages, and performance metrics into a Google Sheet automatically every Monday morning.
Use Case 02
CTR Drop Alert
Compare CTR for top queries week-over-week and send a Gmail alert when any high-impression query drops more than a defined CTR threshold.
Use Case 03
Position Tracking Sheet
Build a running historical position tracker for a defined keyword set — updated daily, stored row-by-row in Sheets, charted automatically.
Use Case 04
Page-Level Performance Audit
Pull clicks and impressions per URL for the last 90 days, identify pages with high impressions but low clicks (strong title/meta candidates).
Use Case 05
Device & Country Breakdown
Dimension performance by device type (mobile/desktop/tablet) and country to identify mobile CTR gaps or market-specific ranking opportunities.
Use Case 06
Multi-Property Dashboard
Aggregate GSC data across multiple site properties into a single Sheets dashboard — critical for agencies managing multiple client domains.

3 Search Console API Endpoints Reference

The Search Console API exposes five endpoint categories. This guide focuses primarily on Search Analytics, which is the most data-rich and most commonly automated endpoint.

POST

Search Analytics — Query

POST https://searchconsole.googleapis.com/webmasters/v3/sites/{siteUrl}/searchAnalytics/query

Returns clicks, impressions, CTR, and average position. Accepts dimensions (query, page, country, device, date, searchAppearance), date ranges, filters, and aggregation type. The primary endpoint for all performance data.

GET

Sites — List

GET https://searchconsole.googleapis.com/webmasters/v3/sites

Returns all verified site properties the authenticated user has access to. Useful for multi-property scripts to enumerate domains dynamically.

GET

URL Inspection

POST https://searchconsole.googleapis.com/v1/urlInspection/index:inspect

Returns index status, crawl status, canonical URL, and mobile usability verdict for a specific URL. Requires a separate scope: https://www.googleapis.com/auth/webmasters

GET

Sitemaps — List

GET https://searchconsole.googleapis.com/webmasters/v3/sites/{siteUrl}/sitemaps

Returns all sitemaps submitted for a property with submission date, last download date, and warning/error counts.

GET

Index Coverage — Issues

GET https://searchconsole.googleapis.com/webmasters/v3/sites/{siteUrl}/indexStatusResults

Returns index coverage issue categories (Crawled – currently not indexed, Discovered – currently not indexed, etc.) with issue counts.

Search Analytics Dimensions Reference

DimensionDescriptionExample ValueNotes
queryThe search query string"search console api"Some queries anonymized as "(not provided)"
pageThe URL that appeared in resultshttps://yourdomain.com/page/Full canonical URL including protocol
countryISO 3166-1 alpha-3 country codeUSA, GBR, DEUBased on user location
deviceDevice categoryMOBILE, DESKTOP, TABLETValues are uppercase strings
dateCalendar date2026-07-01YYYY-MM-DD format
searchAppearanceSearch feature typeRICH_SNIPPET, AMP, VIDEOCannot combine with other dimensions

4 API Quotas and Rate Limits

Understanding the Search Console API quota system prevents failed scripts and unexpected errors in production. All quotas are per Google Cloud project unless otherwise noted.

Requests per day (per project)
2,000 requests / day
Requests per minute (per user)
1,200 req / min
Max rows per Search Analytics request
25,000 rows
Historical data window
~16 months
URL Inspection requests per day
2,000 / day

Quota Strategy for Large Sites: A site with 100,000+ unique query-page combinations will exceed the 25,000-row limit per request. Use the startRow parameter to paginate through results in 25,000-row batches. Each batch consumes one API request. A full pull of 200,000 rows requires 8 requests — well within the 2,000/day daily quota but important to account for when running multiple scripts or multiple properties under one project.

5 Google Cloud Console Setup

The Search Console API requires a Google Cloud project with the API enabled and OAuth credentials configured. This section walks through setup from scratch — estimated time: 10 minutes.

  1. Create or select a Google Cloud project

    Navigate to console.cloud.google.com. In the top navigation bar, click the project selector dropdown and choose New Project. Name it something recognizable (e.g., gsc-appscript-automation). Click Create. Wait for project creation to complete.

  2. Enable the Search Console API

    In the left sidebar, navigate to APIs & Services → Library. Search for Google Search Console API. Click the result, then click Enable. The API status will change to "Enabled".

  3. Configure the OAuth consent screen

    Navigate to APIs & Services → OAuth consent screen. Select Internal if your Google account is a Google Workspace account (recommended for agency use). Select External if using a personal Gmail account. Fill in the App name, user support email, and developer contact email. Click Save and Continue through the Scopes and Test Users screens.

  4. Add the required OAuth scope

    On the Scopes screen, click Add or Remove Scopes. In the filter field, enter webmasters. Check the scope https://www.googleapis.com/auth/webmasters.readonly for read-only data access. If you need URL inspection write access, also add https://www.googleapis.com/auth/webmasters. Click Update then Save and Continue.

6 OAuth 2.0 Credentials Configuration

OAuth 2.0 is the authentication standard the Search Console API requires. You'll create a client ID and client secret that your Apps Script uses to obtain access tokens on behalf of your Google account.

  1. Create OAuth 2.0 credentials

    In Google Cloud Console, navigate to APIs & Services → Credentials. Click + Create Credentials → OAuth Client ID. Set Application type to Web application. Give it a name (e.g., GSC Apps Script Client).

  2. Add the Apps Script redirect URI

    Under Authorized redirect URIs, click Add URI and paste the following URI:

    Redirect URIURI
    https://script.google.com/macros/d/{SCRIPT_ID}/usercallback
  3. Copy your Client ID and Client Secret

    After clicking Create, a modal displays your Client ID and Client Secret. Copy both — you'll paste them into Apps Script in the next section.

Security Note: Never hardcode your Client ID or Client Secret directly in Apps Script code. Store credentials in Script Properties and reference them via PropertiesService.getScriptProperties().getProperty('CLIENT_ID').

7 Apps Script Project Setup

  1. Create a new Apps Script project

    Navigate to script.google.com and click New Project. Rename the project to something descriptive — e.g., GSC Search Analytics Automation.

  2. Note your Script ID and update the redirect URI

    In the Apps Script editor, click Project Settings (the gear icon). Your Script ID is shown at the top. Copy it, then return to Google Cloud Console → Credentials → your OAuth client → Edit, and replace the placeholder with your actual Script ID.

    Updated Redirect URIURI
    https://script.google.com/macros/d/1BxiMV...AbCdEfGhIjKlMnOpQrStUv/usercallback
  3. Link your Google Cloud project

    In Apps Script Project Settings, under Google Cloud Platform (GCP) Project, click Change project. Enter the Project Number of the Cloud project you created earlier.

  4. Store credentials in Script Properties

    In Apps Script Project Settings, scroll to Script Properties. Add two properties:

    Script PropertiesKEY/VALUE
    CLIENT_ID       your-client-id.apps.googleusercontent.com
    CLIENT_SECRET   your-client-secret-value
  5. Install the OAuth2 library

    In the Apps Script editor, click the + icon next to Libraries. Paste the following Script ID: 1B7FSrk5Zi6L1rSxxTDgDEUsPzlukDsi4KGuTMorsTQHhGBzBkMun4iDF. Select the latest version, set the identifier to OAuth2, and click Add.

8 The Authentication Function

With the OAuth2 library installed and credentials stored in Script Properties, the following three functions establish and manage authentication.

Authentication — Code.gs
Apps Script / JS
/**
 * Returns an authenticated OAuth2 service for the Search Console API.
 * Call this function from any other function that needs API access.
 */
function getSearchConsoleService() {
  const props = PropertiesService.getScriptProperties();

  return OAuth2.createService('SearchConsole')
    .setAuthorizationBaseUrl('https://accounts.google.com/o/oauth2/auth')
    .setTokenUrl('https://accounts.google.com/o/oauth2/token')
    .setClientId(props.getProperty('CLIENT_ID'))
    .setClientSecret(props.getProperty('CLIENT_SECRET'))
    .setPropertyStore(props)
    .setCallbackFunction('authCallback')
    .setScope('https://www.googleapis.com/auth/webmasters.readonly')
    .setParam('access_type', 'offline')
    .setParam('prompt', 'consent');
}

/**
 * Handles the OAuth callback.
 */
function authCallback(request) {
  const service = getSearchConsoleService();
  const authorized = service.handleCallback(request);
  return HtmlService.createHtmlOutput(
    authorized
      ? '✅ Authorized. You can close this tab and return to Apps Script.'
      : '❌ Authorization denied.'
  );
}

/**
 * Run this function once to trigger the OAuth authorization flow.
 */
function authorizeScript() {
  const service = getSearchConsoleService();
  if (!service.hasAccess()) {
    const authUrl = service.getAuthorizationUrl();
    Logger.log('Open this URL to authorize:\n%s', authUrl);
  } else {
    Logger.log('✅ Already authorized.');
  }
}

Run authorizeScript once, open the URL from the log, sign in, and click Allow.

Verification: Run authorizeScript again. The log should now print ✅ Already authorized.

9 Your First Search Analytics API Call

With authentication in place, the following function makes your first call to the Search Analytics API — pulling the top 10 queries by clicks for the last 28 days.

First API Call — top queries, last 28 days
Apps Script / JS
/**
 * Fetches the top 10 queries by clicks for the last 28 days.
 */
function fetchTopQueries() {
  const SITE_URL  = 'https://www.yourdomain.com/';
  const service   = getSearchConsoleService();

  if (!service.hasAccess()) {
    Logger.log('Not authorized. Run authorizeScript() first.');
    return;
  }

  const today     = new Date();
  const endDate   = formatDate(new Date(today - 2 * 86400000));
  const startDate = formatDate(new Date(today - 29 * 86400000));

  const payload = {
    startDate, endDate,
    dimensions: ['query'],
    rowLimit: 10
  };

  const endpoint =
    'https://searchconsole.googleapis.com/webmasters/v3/sites/' +
    encodeURIComponent(SITE_URL) +
    '/searchAnalytics/query';

  const response = UrlFetchApp.fetch(endpoint, {
    method: 'POST',
    headers: {
      'Authorization': 'Bearer ' + service.getAccessToken(),
      'Content-Type': 'application/json'
    },
    payload: JSON.stringify(payload),
    muteHttpExceptions: true
  });

  const data = JSON.parse(response.getContentText());

  if (data.error) {
    Logger.log('API Error: %s', JSON.stringify(data.error));
    return;
  }

  (data.rows || []).forEach(row => {
    Logger.log(
      '%-40s | clicks: %-5s | impr: %-6s | ctr: %s% | pos: %s',
      row.keys[0],
      row.clicks,
      row.impressions,
      (row.ctr * 100).toFixed(1),
      row.position.toFixed(1)
    );
  });
}

// Utility: format a Date object as YYYY-MM-DD
function formatDate(date) {
  return Utilities.formatDate(date, 'GMT', 'yyyy-MM-dd');
}

API Response Structure

Example API ResponseJSON
{
  "rows": [
    {
      "keys": ["search console api apps script"],
      "clicks":      142,
      "impressions": 1840,
      "ctr":         0.0771,
      "position":    3.2
    }
  ]
}

10 Writing GSC Data to Google Sheets

The following script extends the basic query to write data directly into a named Google Sheet tab — creating headers if they don't exist, clearing the previous run's data, and writing fresh rows each time it executes.

Export to Sheets — full query + page report
Apps Script / JS
/**
 * Pulls top 500 query+page combinations from GSC (last 28 days)
 * and writes them to a sheet named 'GSC_Report'.
 */
function exportGscToSheets() {
  const SITE_URL   = 'https://www.yourdomain.com/';
  const SHEET_NAME = 'GSC_Report';
  const ROW_LIMIT  = 500;

  const service = getSearchConsoleService();
  if (!service.hasAccess()) {
    Logger.log('Not authorized.');
    return;
  }

  const today     = new Date();
  const endDate   = formatDate(new Date(today - 2  * 86400000));
  const startDate = formatDate(new Date(today - 29 * 86400000));

  const payload = {
    startDate, endDate,
    dimensions: ['query', 'page'],
    rowLimit: ROW_LIMIT
  };

  const endpoint =
    'https://searchconsole.googleapis.com/webmasters/v3/sites/' +
    encodeURIComponent(SITE_URL) +
    '/searchAnalytics/query';

  const response = UrlFetchApp.fetch(endpoint, {
    method: 'POST',
    headers: {
      'Authorization': 'Bearer ' + service.getAccessToken(),
      'Content-Type': 'application/json'
    },
    payload: JSON.stringify(payload),
    muteHttpExceptions: true
  });

  const data = JSON.parse(response.getContentText());
  if (data.error) { Logger.log(data.error); return; }

  const ss    = SpreadsheetApp.getActiveSpreadsheet();
  let   sheet = ss.getSheetByName(SHEET_NAME);
  if (!sheet) sheet = ss.insertSheet(SHEET_NAME);
  sheet.clearContents();

  const headers = [
    'Query', 'Page', 'Clicks', 'Impressions',
    'CTR', 'Avg Position', 'Date Range', 'Exported At'
  ];
  sheet.appendRow(headers);

  const rows = (data.rows || []).map(row => [
    row.keys[0], row.keys[1],
    row.clicks, row.impressions,
    (row.ctr * 100).toFixed(2) + '%',
    row.position.toFixed(1),
    `${startDate} to ${endDate}`,
    new Date().toISOString()
  ]);

  if (rows.length > 0) {
    sheet.getRange(2, 1, rows.length, headers.length)
         .setValues(rows);
  }

  Logger.log('✅ Written %s rows to sheet "%s"', rows.length, SHEET_NAME);
}

Running the Script from a Sheet: This script must be opened from a Google Sheets bound script — open it via Extensions → Apps Script from within the Google Sheet. If you created a standalone script, replace getActiveSpreadsheet() with SpreadsheetApp.openById('your-spreadsheet-id').

11 Pagination for Large Sites

Sites with substantial query volume will exceed the 25,000-row limit per API request. The following function handles pagination automatically.

Paginated fetch — handles 25K+ row sites
Apps Script / JS
/**
 * Fetches ALL query data from GSC using paginated requests.
 * Returns a flat array of all rows across all pages.
 */
function fetchAllRows(siteUrl, startDate, endDate, dimensions) {
  const service    = getSearchConsoleService();
  const PAGE_SIZE  = 25000;
  const endpoint   =
    'https://searchconsole.googleapis.com/webmasters/v3/sites/' +
    encodeURIComponent(siteUrl) + '/searchAnalytics/query';

  let allRows  = [];
  let startRow = 0;
  let page     = 0;

  while (true) {
    page++;
    Logger.log('Fetching page %s (startRow: %s)...', page, startRow);

    const payload = {
      startDate, endDate, dimensions,
      rowLimit:  PAGE_SIZE,
      startRow
    };

    const response = UrlFetchApp.fetch(endpoint, {
      method: 'POST',
      headers: {
        'Authorization': 'Bearer ' + service.getAccessToken(),
        'Content-Type': 'application/json'
      },
      payload: JSON.stringify(payload),
      muteHttpExceptions: true
    });

    const data = JSON.parse(response.getContentText());
    if (data.error) { Logger.log(data.error); break; }

    const pageRows = data.rows || [];
    allRows = allRows.concat(pageRows);

    Logger.log('  → %s rows returned. Total: %s', pageRows.length, allRows.length);

    if (pageRows.length < PAGE_SIZE) break;

    startRow += PAGE_SIZE;
    Utilities.sleep(500);
  }

  Logger.log('✅ Total rows: %s across %s pages', allRows.length, page);
  return allRows;
}

12 Scheduling Automated Reports with Triggers

Apps Script triggers run functions on a schedule without manual execution. Setting up a weekly trigger means your GSC data is refreshed in Sheets automatically.

  1. Open the Triggers panel

    In the Apps Script editor, click the clock icon in the left sidebar (or navigate to Triggers from the menu). Click + Add Trigger.

  2. Configure the trigger settings

    In the trigger configuration panel:

    Trigger ConfigurationSETTINGS
    Choose function to run:  exportGscToSheets
    Choose deployment:       Head
    Select event source:    Time-driven
    Select type of trigger: Week timer
    Select day of week:     Every Monday
    Select time of day:     6am to 7am

    Click Save. Approve any additional permissions when prompted.

Execution Time Limit: Apps Script functions have a maximum execution time of 6 minutes for free accounts and 30 minutes for Google Workspace. Scripts that paginate through very large datasets may approach this limit. If you hit it, split the export into multiple functions — one per date range segment.

13 Advanced: CTR Drop Alert Script

This script compares CTR for your top queries week-over-week and sends an email alert when any high-impression query drops below a defined CTR threshold.

CTR Drop Alert — weekly email notification
Apps Script / JS
/**
 * Compares top-query CTR between the current week and last week.
 * Sends a Gmail alert for any query where:
 *   - Impressions >= MIN_IMPRESSIONS
 *   - CTR dropped by >= CTR_DROP_THRESHOLD percentage points
 */
function ctrDropAlert() {
  const SITE_URL           = 'https://www.yourdomain.com/';
  const ALERT_EMAIL        = 'youremail@domain.com';
  const MIN_IMPRESSIONS    = 100;
  const CTR_DROP_THRESHOLD = 2;

  const service = getSearchConsoleService();
  if (!service.hasAccess()) { Logger.log('Not authorized.'); return; }

  const today    = new Date();
  const d        = n => new Date(today - n * 86400000);
  const thisWeek = { start: formatDate(d(9)),  end: formatDate(d(2)) };
  const lastWeek = { start: formatDate(d(16)), end: formatDate(d(9)) };

  const fetchQueryMap = (startDate, endDate) => {
    const endpoint =
      'https://searchconsole.googleapis.com/webmasters/v3/sites/' +
      encodeURIComponent(SITE_URL) + '/searchAnalytics/query';
    const resp = UrlFetchApp.fetch(endpoint, {
      method: 'POST',
      headers: { 'Authorization': 'Bearer ' + service.getAccessToken(),
                 'Content-Type': 'application/json' },
      payload: JSON.stringify({ startDate, endDate,
        dimensions: ['query'], rowLimit: 1000 }),
      muteHttpExceptions: true
    });
    const parsed = JSON.parse(resp.getContentText());
    const map = new Map();
    (parsed.rows || []).forEach(r => map.set(r.keys[0], r));
    return map;
  };

  const currentMap = fetchQueryMap(thisWeek.start, thisWeek.end);
  const previousMap = fetchQueryMap(lastWeek.start, lastWeek.end);

  const drops = [];
  currentMap.forEach((curr, query) => {
    const prev = previousMap.get(query);
    if (!prev) return;

    const currCtr = curr.ctr * 100;
    const prevCtr = prev.ctr * 100;
    const drop    = prevCtr - currCtr;

    if (curr.impressions >= MIN_IMPRESSIONS && drop >= CTR_DROP_THRESHOLD) {
      drops.push({ query, currCtr, prevCtr, drop,
        impressions: curr.impressions,
        position:    curr.position.toFixed(1)
      });
    }
  });

  if (drops.length === 0) {
    Logger.log('✅ No CTR drops detected.');
    return;
  }

  drops.sort((a,b) => b.drop - a.drop);

  let body = `GSC CTR DROP ALERT\n
Site: ${SITE_URL}\nPeriod: ${thisWeek.start}${thisWeek.end}\n
vs. prior week: ${lastWeek.start}${lastWeek.end}\n
Threshold: -${CTR_DROP_THRESHOLD}pp CTR, min ${MIN_IMPRESSIONS} impressions\n\n
QUERIES FLAGGED (${drops.length}):\n${'─'.repeat(60)}\n`;

  drops.forEach(d => {
    body += `\n"${d.query}"\n
  CTR: ${d.prevCtr.toFixed(1)}% → ${d.currCtr.toFixed(1)}%  (-${d.drop.toFixed(1)}pp)\n
  Impressions: ${d.impressions}  |  Avg Pos: ${d.position}\n`;
  });

  GmailApp.sendEmail(
    ALERT_EMAIL,
    `🚨 GSC CTR Alert: ${drops.length} queries dropped — ${SITE_URL}`,
    body
  );
  Logger.log('📧 Alert sent: %s drops found.', drops.length);
}

14 Multi-Property Pipeline

For agencies managing multiple client properties, the following pattern iterates across an array of property URLs — writing each to its own named sheet tab.

Multi-property loop — one tab per client
Apps Script / JS
/**
 * Pulls GSC data for multiple properties and writes each
 * to its own sheet tab. Add client properties to PROPERTIES array.
 */
function exportAllClients() {
  const PROPERTIES = [
    { url: 'https://client-one.com/',    label: 'Client One'  },
    { url: 'https://client-two.com/',    label: 'Client Two'  },
    { url: 'sc-domain:client-three.com', label: 'Client Three' }
  ];

  const service = getSearchConsoleService();
  if (!service.hasAccess()) { Logger.log('Not authorized.'); return; }

  const today     = new Date();
  const endDate   = formatDate(new Date(today - 2  * 86400000));
  const startDate = formatDate(new Date(today - 29 * 86400000));
  const ss        = SpreadsheetApp.getActiveSpreadsheet();

  PROPERTIES.forEach(prop => {
    Logger.log('Processing: %s', prop.label);

    let sheet = ss.getSheetByName(prop.label);
    if (!sheet) sheet = ss.insertSheet(prop.label);
    sheet.clearContents();

    const endpoint =
      'https://searchconsole.googleapis.com/webmasters/v3/sites/' +
      encodeURIComponent(prop.url) + '/searchAnalytics/query';

    const response = UrlFetchApp.fetch(endpoint, {
      method: 'POST',
      headers: { 'Authorization': 'Bearer ' + service.getAccessToken(),
                 'Content-Type': 'application/json' },
      payload: JSON.stringify({ startDate, endDate,
        dimensions: ['query'], rowLimit: 500 }),
      muteHttpExceptions: true
    });

    const data = JSON.parse(response.getContentText());
    if (data.error) {
      Logger.log('Error for %s: %s', prop.label, data.error.message);
      return;
    }

    sheet.appendRow(['Query','Clicks','Impressions','CTR','Position']);

    const rows = (data.rows || []).map(r => [
      r.keys[0], r.clicks, r.impressions,
      (r.ctr*100).toFixed(2)+'%', r.position.toFixed(1)
    ]);
    if (rows.length) {
      sheet.getRange(2,1,rows.length,5).setValues(rows);
    }
    Logger.log('  ✅ %s rows written for %s', rows.length, prop.label);

    Utilities.sleep(1000);
  });
}

15 Troubleshooting

The following errors account for the majority of first-time setup issues. Each has a specific resolution.

Error / SymptomCauseResolution
Not authorized in log Access token not stored — authorization not completed Run authorizeScript(), open the URL in the log, and grant access.
Error 401: invalid_client Client ID or Client Secret is incorrect in Script Properties Verify values in Script Properties match exactly what's shown in Google Cloud Console.
Error 403: insufficientPermissions Authenticated user lacks GSC access to the property URL Confirm the Google account used for authorization has at least Restricted User access to the property.
Error 403: siteNotOwned Property URL format mismatch Ensure the SITE_URL matches exactly as it appears in GSC — including trailing slash. Use sc-domain:yourdomain.com for domain properties.
redirect_uri_mismatch Redirect URI in Google Cloud doesn't match script ID Update the redirect URI to include your actual Script ID: https://script.google.com/macros/d/YOUR_SCRIPT_ID/usercallback
Error 429: rateLimitExceeded API quota exceeded (1,200 req/min or 2,000 req/day) Add Utilities.sleep(500) between requests. If hitting daily quota, spread requests across multiple Cloud projects.
Empty rows array in response Date range has no data (too recent or too old) GSC data lags 2–3 days. Test with a known date range first using the GSC web UI.
Script times out after 6 minutes Too many rows for free account time limit Reduce rowLimit, split into smaller date range batches, or upgrade to Google Workspace (30-minute limit).
OAuth token expires in trigger Refresh token not obtained during initial authorization Ensure access_type: 'offline' and prompt: 'consent' are in getSearchConsoleService(). Run authorizeScript() again.

16 FAQ

What is the Google Search Console API?+
The Google Search Console API is a REST API that provides programmatic access to the data available in the Google Search Console web interface — including search performance metrics (clicks, impressions, CTR, position), index coverage status, URL inspection results, and sitemap data.
What is Google Apps Script?+
Google Apps Script is a cloud-based JavaScript platform built into the Google Workspace ecosystem that enables automation of Google products — including Sheets, Docs, Gmail, Drive, and external APIs. It runs on Google's servers, requires no local infrastructure, and can be triggered on schedules or events.
Do I need a Google Cloud project?+
Yes. The Google Search Console API requires a Google Cloud project with the API enabled and OAuth 2.0 credentials configured. The project can be on the free tier — the Search Console API has no direct cost for typical usage volumes.
What data can the API return?+
The Search Console API's Search Analytics endpoint returns clicks, impressions, CTR, and average position data — dimensioned by query, page, country, device, search type, and date. It supports up to 16 months of historical data with a maximum of 25,000 rows per request.
What is the row limit?+
The Search Analytics endpoint returns a maximum of 25,000 rows per request. For sites with more than 25,000 unique query-page combinations, pagination is required using the startRow parameter. The API has a quota of 1,200 requests per minute per user and 2,000 requests per day per project.
How do I automate reports?+
Search Console reports are automated in Apps Script using time-based triggers. Add a trigger for your function, set it to Time-driven, and configure the schedule (daily, weekly, or monthly). Apps Script will then run the function automatically on that schedule.

Automate Your GSC Reporting Pipeline

The scripts in this guide are the foundation. The Search Intelligence Hub takes this further — automated entity monitoring, AI Overview citation tracking, and multi-property GEO reporting in a unified dashboard.

Schedule Technical Briefing