| name | google-search-console |
| description | Connect Google Search Console to pull real click, impression, CTR, and ranking position data for keyword research. This skill walks the user through creating a Google Cloud project, authenticating via OAuth, and querying the Search Console API — all from Claude Code. |
| metadata | {"clawdbot":{"requires":{"env":["GOOGLE_CLIENT_ID","GOOGLE_CLIENT_SECRET","GSC_REFRESH_TOKEN"]}}} |
FIRST TIME READING THIS SKILL? STOP AND READ THIS SECTION TO THE USER.
Before running any commands, explain the following to the user:
What Google Search Console gives you:
Google Search Console (GSC) contains your actual search performance data — every keyword Google shows your site for, how many people clicked, your average position, and which pages rank. This data is free and comes directly from Google. Combining it with keyword research lets you find quick wins (keywords you already rank for but could optimize) and validate which keywords actually drive traffic.
What you need:
A Google Cloud project with OAuth credentials. The project does NOT need to be published or verified by Google — it stays in "Testing" mode. You add your own email as a test user and that's it. No approval process, no review, no fees.
How long it takes:
About 5 minutes to set up the Google Cloud project. After that, you run a one-time auth script and you're done.
Setup: Create a Google Cloud Project
Follow these steps to create OAuth credentials. Claude Code cannot do this for you — it requires clicking through the Google Cloud Console UI.
Step 1: Create the project
- Go to console.cloud.google.com
- Click the project dropdown at the top and select "New Project"
- Name it anything (e.g. "SEO Keyword Research")
- Click "Create"
Step 2: Enable the Search Console API
- In your new project, go to APIs & Services > Library
- Search for "Google Search Console API"
- Click it and press Enable
Step 3: Configure the OAuth consent screen
- Go to APIs & Services > OAuth consent screen
- Select External user type, click Create
- Fill in the required fields:
- App name: anything (e.g. "SEO Research")
- User support email: your email
- Developer contact email: your email
- Click Save and Continue
- On the Scopes page, click Add or Remove Scopes
- Search for
webmasters.readonly and check it
- Click Update, then Save and Continue
- On the Test Users page, click Add Users
- Add your own Google email address (the one that has Search Console access)
- Click Save and Continue, then Back to Dashboard
The app stays in "Testing" mode. Only the test users you added can authorize it. No verification or approval from Google is needed.
Step 4: Create OAuth credentials
- Go to APIs & Services > Credentials
- Click Create Credentials > OAuth client ID
- Application type: Web application
- Name: anything (e.g. "SEO Research Client")
- Under Authorized redirect URIs, add:
http://localhost:8080/callback
- Click Create
- Copy the Client ID and Client Secret
Step 5: Set environment variables
Add these to your .env file in the repo root (create it from .env.example if you haven't already):
GOOGLE_CLIENT_ID=your_client_id_here
GOOGLE_CLIENT_SECRET=your_client_secret_here
The .env file is gitignored — your credentials will never be committed.
Authentication: Get Your Refresh Token
After setting the client ID and secret, Claude Code will generate and run a small Python script that opens a browser window for you to authorize access. You click "Allow", and the script captures your refresh token.
Claude Code: generate and run the following script when the user is ready to authenticate.
"""
Google Search Console OAuth Flow
Run this script once to get a refresh token. After that, store the token
as an environment variable and this script is no longer needed.
"""
import http.server
import json
import os
import sys
import urllib.parse
import webbrowser
try:
import requests
except ImportError:
os.system(f"{sys.executable} -m pip install requests")
import requests
CLIENT_ID = os.environ.get("GOOGLE_CLIENT_ID")
CLIENT_SECRET = os.environ.get("GOOGLE_CLIENT_SECRET")
REDIRECT_URI = "http://localhost:8080/callback"
SCOPE = "https://www.googleapis.com/auth/webmasters.readonly"
AUTH_URL = "https://accounts.google.com/o/oauth2/v2/auth"
TOKEN_URL = "https://oauth2.googleapis.com/token"
if not CLIENT_ID or not CLIENT_SECRET:
print("ERROR: Set GOOGLE_CLIENT_ID and GOOGLE_CLIENT_SECRET environment variables first.")
sys.exit(1)
auth_params = urllib.parse.urlencode({
"client_id": CLIENT_ID,
"redirect_uri": REDIRECT_URI,
"response_type": "code",
"scope": SCOPE,
"access_type": "offline",
"prompt": "consent",
})
consent_url = f"{AUTH_URL}?{auth_params}"
tokens = {}
class CallbackHandler(http.server.BaseHTTPRequestHandler):
def do_GET():
query = urllib.parse.urlparse(.path).query
params = urllib.parse.parse_qs(query)
params:
.send_response()
.end_headers()
.wfile.write()
code = params[][]
resp = requests.post(TOKEN_URL, data={
: code,
: CLIENT_ID,
: CLIENT_SECRET,
: REDIRECT_URI,
: ,
})
data = resp.json()
data:
.send_response()
.end_headers()
.wfile.write(.encode())
tokens[] = data[]
tokens[] = data[]
.send_response()
.send_header(, )
.end_headers()
.wfile.write()
():
()
()
webbrowser.(consent_url)
server = http.server.HTTPServer((, ), CallbackHandler)
server.handle_request()
server.server_close()
tokens:
( + * )
()
()
()
()
()
( + * )
()
()
()
:
()
After running the script: The user copies the printed export lines into their .env file or shell profile. From then on, Claude Code can query GSC using the refresh token.
Token Refresh
Access tokens expire after ~1 hour. Use this to refresh:
curl -s -X POST https://oauth2.googleapis.com/token \
-d "client_id=$GOOGLE_CLIENT_ID" \
-d "client_secret=$GOOGLE_CLIENT_SECRET" \
-d "refresh_token=$GSC_REFRESH_TOKEN" \
-d "grant_type=refresh_token" | jq .
Returns a new access_token. The refresh_token does not change.
When making API calls, always try the request first. If you get a 401, refresh the token and retry.
API Reference
Base URL: https://www.googleapis.com/webmasters/v3
All requests require: Authorization: Bearer $GSC_ACCESS_TOKEN
List Sites
curl -s -H "Authorization: Bearer $GSC_ACCESS_TOKEN" \
"https://www.googleapis.com/webmasters/v3/sites" | jq .
Returns all Search Console properties the user has access to. Use the siteUrl value (e.g. https://example.com/ or sc-domain:example.com) in subsequent calls.
Search Analytics (Top Queries)
SITE_URL="https://example.com/"
curl -s -X POST \
-H "Authorization: Bearer $GSC_ACCESS_TOKEN" \
-H "Content-Type: application/json" \
"https://www.googleapis.com/webmasters/v3/sites/$(python3 -c 'import urllib.parse; print(urllib.parse.quote(\"'$SITE_URL'\", safe=\"\"))')/searchAnalytics/query" \
-d '{
"startDate": "'$(date -v-28d +%Y-%m-%d 2>/dev/null || date -d "28 days ago" +%Y-%m-%d)'",
"endDate": "'$(date +%Y-%m-%d)'",
"dimensions": ["query"],
"rowLimit": 50,
"dataState": "final"
}' | jq .
Response:
{
"rows": [
{
"keys": ["best seo tool"],
"clicks": 142,
"impressions": 3200,
"ctr": 0.044,
"position": 8.2
}
]
}
Fields:
keys -- the search query
clicks -- number of clicks from Google search
impressions -- how many times the page appeared in results
ctr -- click-through rate (clicks / impressions)
position -- average ranking position (1 = top)
Search Analytics (Top Pages)
Same endpoint, change dimension to page:
curl -s -X POST \
-H "Authorization: Bearer $GSC_ACCESS_TOKEN" \
-H "Content-Type: application/json" \
"https://www.googleapis.com/webmasters/v3/sites/$(python3 -c 'import urllib.parse; print(urllib.parse.quote(\"'$SITE_URL'\", safe=\"\"))')/searchAnalytics/query" \
-d '{
"startDate": "'$(date -v-28d +%Y-%m-%d 2>/dev/null || date -d "28 days ago" +%Y-%m-%d)'",
"endDate": "'$(date +%Y-%m-%d)'",
"dimensions": ["page"],
"rowLimit": 25
}' | jq .
Search Analytics (Query + Page combined)
Get which queries drive traffic to which pages:
curl -s -X POST \
-H "Authorization: Bearer $GSC_ACCESS_TOKEN" \
-H "Content-Type: application/json" \
"https://www.googleapis.com/webmasters/v3/sites/$(python3 -c 'import urllib.parse; print(urllib.parse.quote(\"'$SITE_URL'\", safe=\"\"))')/searchAnalytics/query" \
-d '{
"startDate": "'$(date -v-28d +%Y-%m-%d 2>/dev/null || date -d "28 days ago" +%Y-%m-%d)'",
"endDate": "'$(date +%Y-%m-%d)'",
"dimensions": ["query", "page"],
"rowLimit": 100
}' | jq .
List Sitemaps
curl -s -H "Authorization: Bearer $GSC_ACCESS_TOKEN" \
"https://www.googleapis.com/webmasters/v3/sites/$(python3 -c 'import urllib.parse; print(urllib.parse.quote(\"'$SITE_URL'\", safe=\"\"))')/sitemaps" | jq .
Returns sitemap info including indexed page counts.
How to Use GSC Data in Keyword Research
When GSC credentials are available, use the data to enrich keyword research:
- Pull top 50 queries from the last 28 days
- Identify quick wins: keywords where position is 5-20 (page 1-2 but not top 3). These are keywords you already rank for and could push higher with a targeted article.
- Validate keyword ideas: Cross-reference discovered keywords against GSC data. If a keyword already gets impressions, it confirms real search demand.
- Find content gaps: Look for queries with high impressions but low CTR — the page might need a better title/meta description, or a dedicated article.
- Prioritize by real data: Keywords you already rank for are easier to improve than starting from zero.
Add a "GSC Insights" section to the HTML dashboard when this data is available:
- Table of current rankings with clicks, impressions, CTR, position
- Quick wins highlighted (position 5-20 with decent impressions)
- Content gaps (high impressions, low CTR)
Rate Limits
The Search Console API allows approximately 1,200 queries per minute per project. For keyword research, you'll typically make 2-5 calls total, so rate limits are not a concern.
Troubleshooting
| Issue | Solution |
|---|
| "Access blocked: This app's request is invalid" | Make sure you added http://localhost:8080/callback as an authorized redirect URI |
| "Error 403: access_denied" | Make sure you added your email as a test user in OAuth consent screen |
| No refresh_token in response | Add prompt=consent and access_type=offline to the auth URL (already included in the script) |
| 401 on API calls | Access token expired. Refresh it using the refresh endpoint above |
| Empty search analytics | Your site may be new or have very little traffic. GSC needs at least a few days of data |