How to Use the Google Search Console API: A Developer's Guide
Google Search Console (GSC) is the single best source of truth for how your site performs in Google search. The web UI is fine for a quick glance, but if you want to build dashboards, automate reporting, or feed SEO data into your AI workflows, you need the API.
This guide walks you through setting up authentication, making your first query, and building something useful — all with working code.
Prerequisites
- A Google Cloud project with the Search Console API enabled
- A verified site in Google Search Console
- Node.js 18+ (examples use TypeScript, but the API is language-agnostic)
Step 1: Authentication
The GSC API uses OAuth 2.0. You have two options:
Option A: Service Account (server-to-server)
Best for automated workflows and CI pipelines.
- Create a service account in Google Cloud Console
- Download the JSON key file
- Add the service account email as a user in GSC (Settings → Users → Add user)
import { google } from "googleapis";
const auth = new google.auth.GoogleAuth({
keyFile: "./service-account.json",
scopes: ["https://www.googleapis.com/auth/webmasters.readonly"],
});
const searchconsole = google.searchconsole({ version: "v1", auth });
Option B: OAuth 2.0 (user consent)
Best for apps where users connect their own GSC properties.
const oauth2Client = new google.auth.OAuth2(
process.env.GOOGLE_CLIENT_ID,
process.env.GOOGLE_CLIENT_SECRET,
"http://localhost:3000/api/auth/callback"
);
// Generate consent URL
const authUrl = oauth2Client.generateAuthUrl({
access_type: "offline",
scope: ["https://www.googleapis.com/auth/webmasters.readonly"],
});
// After user consents, exchange code for tokens
const { tokens } = await oauth2Client.getToken(code);
oauth2Client.setCredentials(tokens);
Step 2: List Verified Sites
Before querying analytics, verify which sites the account has access to:
const sites = await searchconsole.sites.list();
for (const site of sites.data.siteEntry || []) {
console.log(`${site.siteUrl} — ${site.permissionLevel}`);
}
Output:
https://example.com/ — siteOwner
https://blog.example.com/ — siteFullUser
Step 3: Query Search Analytics
This is the core endpoint. It returns queries, pages, clicks, impressions, CTR, and position data.
const response = await searchconsole.searchanalytics.query({
siteUrl: "https://example.com",
requestBody: {
startDate: "2026-01-01",
endDate: "2026-03-20",
dimensions: ["query", "page"],
rowLimit: 25,
dataState: "final",
},
});
for (const row of response.data.rows || []) {
const [query, page] = row.keys!;
console.log(
`"${query}" → ${page} | pos ${row.position?.toFixed(1)} | ${row.clicks} clicks`
);
}
Dimensions You Can Use
| Dimension | Description |
|-----------|-------------|
| query | The search term the user typed |
| page | The URL that appeared in results |
| device | DESKTOP, MOBILE, or TABLET |
| country | ISO 3166-1 alpha-3 country code |
| date | Individual date (for time series) |
| searchAppearance | Rich result type (e.g., RECIPE, FAQ) |
Filtering Results
Narrow your data with dimension filters:
const response = await searchconsole.searchanalytics.query({
siteUrl: "https://example.com",
requestBody: {
startDate: "2026-01-01",
endDate: "2026-03-20",
dimensions: ["query"],
dimensionFilterGroups: [
{
filters: [
{
dimension: "page",
operator: "contains",
expression: "/blog/",
},
{
dimension: "query",
operator: "notContains",
expression: "brand-name",
},
],
},
],
rowLimit: 100,
},
});
Step 4: Build Something Useful
Automated Position Tracking
Track how your rankings change over time by storing daily snapshots:
async function trackPositions(siteUrl: string, queries: string[]) {
const today = new Date().toISOString().split("T")[0];
const sevenDaysAgo = new Date(Date.now() - 7 * 86400000)
.toISOString()
.split("T")[0];
const response = await searchconsole.searchanalytics.query({
siteUrl,
requestBody: {
startDate: sevenDaysAgo,
endDate: today,
dimensions: ["query", "date"],
dimensionFilterGroups: [
{
filters: queries.map((q) => ({
dimension: "query" as const,
operator: "equals" as const,
expression: q,
})),
},
],
},
});
// Group by query and detect position changes
const byQuery = new Map<string, { date: string; position: number }[]>();
for (const row of response.data.rows || []) {
const [query, date] = row.keys!;
if (!byQuery.has(query)) byQuery.set(query, []);
byQuery.get(query)!.push({ date, position: row.position! });
}
for (const [query, data] of byQuery) {
const sorted = data.sort((a, b) => a.date.localeCompare(b.date));
const first = sorted[0].position;
const last = sorted[sorted.length - 1].position;
const delta = first - last; // positive = improved
console.log(
`"${query}": ${last.toFixed(1)} (${delta > 0 ? "↑" : "↓"} ${Math.abs(delta).toFixed(1)})`
);
}
}
Content Gap Detection
Find queries where you rank on page 2 (positions 11-20) — these are your biggest opportunities:
async function findContentGaps(siteUrl: string) {
const response = await searchconsole.searchanalytics.query({
siteUrl,
requestBody: {
startDate: "2026-01-01",
endDate: "2026-03-20",
dimensions: ["query", "page"],
rowLimit: 1000,
},
});
const gaps = (response.data.rows || [])
.filter((row) => row.position! >= 8 && row.position! <= 20)
.filter((row) => row.impressions! > 100)
.sort((a, b) => b.impressions! - a.impressions!);
console.log("Content gaps (high impressions, page 2):");
for (const row of gaps.slice(0, 20)) {
const [query, page] = row.keys!;
console.log(
` "${query}" → pos ${row.position?.toFixed(1)} | ${row.impressions} imp | ${page}`
);
}
}
Google Search Console to JSON
If you need GSC data as JSON for dashboards, CI pipelines, or AI workflows, you have two paths: the raw API (shown above) or the Xyle CLI which handles auth, pagination, and formatting for you.
Using the Xyle CLI
# Get your top queries as JSON
$ xyle queries --site example.com --limit 50 --json
# Full Search Console overview as JSON
$ xyle search-console overview --site example.com --json
# Export to a file for further processing
$ xyle queries --site example.com --json > gsc-queries.json
The JSON output includes clicks, impressions, CTR, and average position for each query — ready to pipe into jq, load into a database, or feed into an AI agent's context.
Converting API Responses to JSON
If you are using the raw API, the response is already JSON. Here is a utility to flatten the nested structure into a clean format:
interface GSCRow {
query: string;
page: string;
clicks: number;
impressions: number;
ctr: number;
position: number;
}
function flattenGSCResponse(response: any): GSCRow[] {
return (response.data.rows || []).map((row: any) => ({
query: row.keys?.[0] || "",
page: row.keys?.[1] || "",
clicks: row.clicks || 0,
impressions: row.impressions || 0,
ctr: row.ctr || 0,
position: row.position || 0,
}));
}
// Usage
const data = flattenGSCResponse(response);
console.log(JSON.stringify(data, null, 2));
Using the GSC API with Python
The GSC API works with any language. Here is the Python equivalent using the official Google client library.
Setup
pip install google-api-python-client google-auth
Authentication with a Service Account
from google.oauth2 import service_account
from googleapiclient.discovery import build
credentials = service_account.Credentials.from_service_account_file(
"service-account.json",
scopes=["https://www.googleapis.com/auth/webmasters.readonly"],
)
service = build("searchconsole", "v1", credentials=credentials)
Querying Search Analytics
response = service.searchanalytics().query(
siteUrl="https://example.com",
body={
"startDate": "2026-01-01",
"endDate": "2026-04-15",
"dimensions": ["query", "page"],
"rowLimit": 100,
"dataState": "final",
},
).execute()
for row in response.get("rows", []):
query, page = row["keys"]
print(f'"{query}" → {page} | pos {row["position"]:.1f} | {row["clicks"]} clicks')
Exporting to JSON
import json
rows = [
{
"query": row["keys"][0],
"page": row["keys"][1],
"clicks": row["clicks"],
"impressions": row["impressions"],
"ctr": row["ctr"],
"position": row["position"],
}
for row in response.get("rows", [])
]
with open("gsc-data.json", "w") as f:
json.dump(rows, f, indent=2)
Filtering by Page or Query
response = service.searchanalytics().query(
siteUrl="https://example.com",
body={
"startDate": "2026-01-01",
"endDate": "2026-04-15",
"dimensions": ["query"],
"dimensionFilterGroups": [
{
"filters": [
{
"dimension": "page",
"operator": "contains",
"expression": "/blog/",
}
]
}
],
"rowLimit": 100,
},
).execute()
Google SEO API Alternatives
The GSC API is the primary source of Google search data, but it has limitations: 16 months of history, 25,000 rows per request, and 2-3 days of data delay. Here are alternatives and complementary APIs.
| API | Data Provided | Best For | Limitation |
|-----|--------------|----------|------------|
| GSC API | Queries, clicks, impressions, position | Core search performance | 16-month history, 2-3 day delay |
| Google PageSpeed API | Core Web Vitals, performance scores | Page speed monitoring | Per-URL, no bulk queries |
| Google Indexing API | Submit URLs for indexing | Fast indexing of new content | Only for JobPosting and BroadcastEvent officially |
| Xyle API | SEO + AEO + GEO scores, GSC sync | Combined SEO/AI analysis | Requires Xyle account |
The Xyle API
If you want GSC data combined with SEO analysis, AEO signals, and GEO scores in a single API, Xyle provides this. Connect your GSC properties once, and the API handles sync, pagination, and enrichment:
# Xyle CLI — combines GSC data with SEO/AEO/GEO analysis
$ xyle queries --site example.com --json # GSC query data
$ xyle crawl --url https://example.com --json # Full page analysis
$ xyle analyze --url https://example.com --json # AI-powered recommendations
$ xyle gaps --site example.com # Content gap detection
The advantage over the raw GSC API: no OAuth setup, no pagination logic, and every query includes AI visibility signals that the GSC API does not provide.
Rate Limits & Best Practices
The GSC API has the following limits:
- Queries per minute: 1,200 (per project)
- Row limit per request: 25,000 max
- Date range: Up to 16 months of data
- Data freshness: Usually 2-3 days behind
Tips:
- Cache responses aggressively — GSC data does not change retroactively for finalized dates.
- Use
dataState: "final"for accurate data (excludes the most recent 2-3 days). - Paginate with
startRowfor large result sets. - Store historical data in your own database — GSC only retains 16 months.
Skip the Boilerplate with Xyle
If you would rather skip the auth setup, pagination logic, and data storage, Xyle handles all of it. Connect your GSC properties in one click, then access your data via the dashboard, CLI, or REST API.
$ npm install -g @xyleapp/cli
$ xyle login
$ xyle queries --site example.com --limit 20
$ xyle gaps --site example.com
Xyle syncs your GSC data automatically, runs AI analysis on content gaps, detects AEO signals (schema markup, content structure, quality metrics), and gives you dual SEO + AEO scores — no OAuth setup required. Use the dashboard analysis page for visual results or the CLI for automation.
Whether you build your own integration or use a tool like Xyle, the key takeaway is this: your GSC data is too valuable to only view in a web UI. Programmatic access unlocks automated monitoring, AI-powered optimization for both search engines and AI answer engines, and workflows that scale with your site.
Frequently Asked Questions
How do I convert Google Search Console data to JSON?
Use the GSC API directly — it returns JSON by default. For a simpler approach, the Xyle CLI provides xyle queries --json and xyle search-console overview --json which output clean, pre-formatted JSON without needing to handle authentication or pagination yourself.
Is there a Python library for the Google Search Console API?
Yes. Use Google's official google-api-python-client library with google-auth for authentication. Install with pip install google-api-python-client google-auth, authenticate with a service account, and call service.searchanalytics().query() to retrieve data. See the Python section above for complete examples.
What is the difference between the Google SEO API and the Search Console API?
There is no separate "Google SEO API." The Google Search Console API is the primary API for accessing search performance data (queries, clicks, impressions, positions). Other Google APIs like the PageSpeed Insights API and Indexing API provide complementary data, but the GSC API is the core tool for SEO analytics.
Ready to optimize your search rankings?
Xyle connects to Google Search Console, analyzes content gaps with AI, and gives you actionable fixes — from the terminal or dashboard.