- 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":"[Truncated]"}}}
## 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
1. Go to [console.cloud.google.com](https://console.cloud.google.com)
2. Click the project dropdown at the top and select "New Project"
3. Name it anything (e.g. "SEO Keyword Research")
4. Click "Create"
### Step 2: Enable the Search Console API
1. In your new project, go to **APIs & Services > Library**
2. Search for "Google Search Console API"
3. Click it and press **Enable**
### Step 3: Configure the OAuth consent screen
1. Go to **APIs & Services > OAuth consent screen**
2. Select **External** user type, click Create
3. Fill in the required fields:
- App name: anything (e.g. "SEO Research")
- User support email: your email
- Developer contact email: your email
4. Click **Save and Continue**
5. On the Scopes page, click **Add or Remove Scopes**
6. Search for `webmasters.readonly` and check it
7. Click **Update**, then **Save and Continue**
8. On the Test Users page, click **Add Users**
9. Add your own Google email address (the one that has Search Console access)
10. 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
1. Go to **APIs & Services > Credentials**
2. Click **Create Credentials > OAuth client ID**
3. Application type: **Web application**
4. Name: anything (e.g. "SEO Research Client")
5. Under **Authorized redirect URIs**, add: `http://localhost:8080/callback`
6. Click **Create**
7. 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.**
```python
"""
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(self):
query = urllib.parse.urlparse(self.path).query
params = urllib.parse.parse_qs(query)
if "code" not in params:
self.send_response(400)
self.end_headers()
self.wfile.write(b"No authorization code received.")
return
code = params["code"][0]
resp = requests.post(TOKEN_URL, data={
"code": code,
"client_id": CLIENT_ID,
"client_secret": CLIENT_SECRET,
"redirect_uri": REDIRECT_URI,
"grant_type": "authorization_code",
})
data = resp.json()
if "refresh_token" not in data:
self.send_response(500)
self.end_headers()
self.wfile.write(f"Token exchange failed: {json.dumps(data)}".encode())
return
tokens["access_token"] = data["access_token"]
tokens["refresh_token"] = data["refresh_token"]
self.send_response(200)
self.send_header("Content-Type", "text/html")
self.end_headers()
self.wfile.write(b"<h1>Success!</h1><p>You can close this tab and go back to your terminal.</p>")
def log_message(self, format, *args):
pass # Suppress server logs
print(f"\nOpening browser for Google authorization...\n")
print(f"If the browser doesn't open, visit this URL:\n{consent_url}\n")
webbrowser.open(consent_url)
server = http.server.HTTPServer(("localhost", 8080), CallbackHandler)
server.handle_request() # Handle exactly one request (the callback)
server.server_close()
if tokens:
print("\n" + "=" * 60)
print("SUCCESS! Save these environment variables:\n")
print(f'export GSC_ACCESS_TOKEN="{tokens["access_token"]}"')
print(f'export GSC_REFRESH_TOKEN="{tokens["refresh_token"]}"')
print(f'export GOOGLE_CLIENT_ID="{CLIENT_ID}"')
print(f'export GOOGLE_CLIENT_SECRET="{CLIENT_SECRET}"')
print("\n" + "=" * 60)
print("\nAdd these to your .env file in the repo root (it's gitignored).")
print("The access token expires in ~1 hour. The refresh token is permanent.")
print("Claude Code will automatically refresh the access token when needed.")
else:
print("\nAuthentication failed. Check the error above and try again.")
```
**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:
```bash
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
```bash
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)
```bash
SITE_URL="https://example.com/" # URL-encode if needed
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:**
```json
{
"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`:
```bash
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:
```bash
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
```bash
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:
1. **Pull top 50 queries** from the last 28 days
2. **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.
3. **Validate keyword ideas:** Cross-reference discovered keywords against GSC data. If a keyword already gets impressions, it confirms real search demand.
4. **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.
5. **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 |
在 GitHub 查看