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 and permissions → 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 | Search appearance type, such as rich result features. Query this dimension on its own to list the values your site has |
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 in a group are combined with AND, so match every
// tracked query with one regex instead of several "equals" filters
filters: [
{
dimension: "query",
operator: "includingRegex",
expression: `^(${queries
.map((q) => q.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"))
.join("|")})$`,
},
],
},
],
},
});
// 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 between positions 8 and 20, at the bottom of page 1 or on page 2. These are often your best 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 XeoRank CLI, which reads the GSC data you have connected to XeoRank and prints it as JSON.
Using the XeoRank 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 finalized data that lags by a few days. Here are alternatives and complementary APIs.
| API | Data Provided | Best For | Limitation |
|---|---|---|---|
| GSC API | Queries, clicks, impressions, position | Core search performance | 16-month history, final data lags a few days |
| 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 |
| XeoRank API | SEO + AEO + GEO scores, GSC sync | Combined SEO/AI analysis | Requires XeoRank account |
For one-off checks you do not need an API at all. The free sitemap checker and page analysis work on any public URL.
The XeoRank API
If you want GSC data combined with SEO analysis, AEO signals, and GEO scores in one place, XeoRank provides this. Connect your GSC properties once, sync them, and query the results through the API or CLI:
# XeoRank CLI: GSC data plus 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 --text "..." --json # Scores and recommendations
$ xyle gaps --page https://example.com/blog/post # Content gaps for a page
The advantage over the raw GSC API: you skip writing your own auth and pagination code, and the same tool can crawl and score the pages behind each query.
Rate Limits & Best Practices
The GSC API has the following limits (see Google's usage limits page for the current numbers):
- Search Analytics queries per minute: 1,200 per site and 1,200 per user, with higher per-project limits
- Row limit per request: 25,000 max
- Date range: Up to 16 months of data
- Data freshness: Finalized data lags by a few days;
dataState: "all"includes fresher data that may still change
Tips:
- Cache responses aggressively — GSC data does not change retroactively for finalized dates.
- Use
dataState: "final"for accurate data (it leaves out recent days that are not final yet). - Paginate with
startRowfor large result sets. - Store historical data in your own database — GSC only retains 16 months.
Skip the Boilerplate with XeoRank
If you would rather skip the auth setup, pagination logic, and data storage, XeoRank handles them. Connect your GSC properties once, 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 --page https://example.com/blog/post
XeoRank stores your GSC data each time you sync, finds content gaps, detects AEO and GEO signals (schema markup, content structure, quality metrics), and gives you SEO, AEO and GEO scores. Use the dashboard analysis page for visual results or the CLI for scripting.
Whether you build your own integration or use a tool like XeoRank, the key takeaway is this: your GSC data is too valuable to only view in a web UI. Programmatic access opens up 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 XeoRank 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.
Related reading
Content Decay in SEO: How to Spot Pages Losing Clicks Early
Content decay is the slow loss of clicks on pages that used to perform. Catch it early in Search Console, rule out other causes, then refresh, merge or wait.
Read more →How to Fix Crawl Errors: Find, Sort and Validate in Search Console
How to fix crawl errors: find them in Search Console, sort them by cause (server, DNS, robots.txt, 404, soft 404, redirects), fix the source, then validate.
Read more →Multi-Site SEO Management: Running SEO for 20 Client Sites
Multi-site SEO management works when client sites share the same access, baseline and review routine. A setup for Search Console, weekly triage and reports.
Read more →Ready to optimize your search rankings?
XeoRank connects to Google Search Console, analyzes content gaps with AI, and gives you actionable fixes — from the terminal or dashboard.