assets/templates/cwv-audit-report.md
# Core Web Vitals Audit
**URL/Origin:** {target}
**Strategy:** {strategy}
## CrUX Field Data (28-day rolling average)
Real Chrome user experience data from the Chrome UX Report.
| Metric | p75 Value | Rating | Good Threshold | Distribution |
|--------|-----------|--------|----------------|-------------|
| LCP | {lcp_value} | {lcp_rating} | ≤ 2,500ms | Good: {lcp_good}% / NI: {lcp_ni}% / Poor: {lcp_poor}% |
| INP | {inp_value} | {inp_rating} | ≤ 200ms | Good: {inp_good}% / NI: {inp_ni}% / Poor: {inp_poor}% |
| CLS | {cls_value} | {cls_rating} | ≤ 0.1 | Good: {cls_good}% / NI: {cls_ni}% / Poor: {cls_poor}% |
| FCP | {fcp_value} | {fcp_rating} | ≤ 1,800ms | Good: {fcp_good}% / NI: {fcp_ni}% / Poor: {fcp_poor}% |
| TTFB | {ttfb_value} | {ttfb_rating} | ≤ 800ms | Good: {ttfb_good}% / NI: {ttfb_ni}% / Poor: {ttfb_poor}% |
**Collection Period:** {collection_start} to {collection_end}
## Lighthouse Lab Scores
| Category | Score |
|----------|-------|
| Performance | {perf_score}/100 |
| Accessibility | {a11y_score}/100 |
| Best Practices | {bp_score}/100 |
| SEO | {seo_score}/100 |
## CrUX History Trends (25-week)
| Metric | Direction | Change | Earliest → Latest |
|--------|-----------|--------|-------------------|
{trends_table}
## Top Opportunities
| Opportunity | Estimated Savings |
|-------------|-------------------|
{opportunities_table}
## Recommendations
{recommendations}
---
*CrUX data updated daily ~04:00 UTC. 28-day rolling average.*
*INP replaced FID as the responsiveness Core Web Vital on March 12, 2024.*
*Generated {timestamp}.*
assets/templates/gsc-performance-report.md
# Google Search Console Performance Report
**Property:** {property}
**Date Range:** {start_date} — {end_date}
**Search Type:** {search_type}
## Summary
| Metric | Value |
|--------|-------|
| Total Clicks | {total_clicks} |
| Total Impressions | {total_impressions} |
| Average CTR | {avg_ctr}% |
| Average Position | {avg_position} |
## Top Queries
| # | Query | Clicks | Impressions | CTR | Position |
|---|-------|--------|-------------|-----|----------|
{queries_table}
## Top Pages
| # | Page | Clicks | Impressions | CTR | Position |
|---|------|--------|-------------|-----|----------|
{pages_table}
## Quick Wins (Position 4-10, High Impressions)
These queries rank on page 1 but below position 3. A small ranking improvement could yield significant traffic gains.
| Query | Position | Impressions | Clicks | CTR | Opportunity |
|-------|----------|-------------|--------|-----|-------------|
{quick_wins_table}
## Device Breakdown
| Device | Clicks | Impressions | CTR | Position |
|--------|--------|-------------|-----|----------|
{device_table}
---
*Data freshness: Search Analytics has a 2-3 day lag. Data available for ~16 months.*
*If this report overlaps 2025-05-13 to 2026-04-27, impressions, CTR, and average position require caution due to the confirmed GSC impressions logging error. Clicks were not affected and affected metrics were not backfilled.*
*Generated {timestamp} via Google Search Console API.*
assets/templates/indexation-status-report.md
# URL Indexation Status Report
**Property:** {property}
**URLs Inspected:** {total_urls}
## Summary
| Status | Count | Percentage |
|--------|-------|-----------|
| Indexed (PASS) | {pass_count} | {pass_pct}% |
| Not Indexed (FAIL) | {fail_count} | {fail_pct}% |
| Neutral | {neutral_count} | {neutral_pct}% |
| Errors | {error_count} | {error_pct}% |
## Detailed Results
| URL | Verdict | Coverage State | Fetch State | Google Canonical | Last Crawl |
|-----|---------|---------------|-------------|-----------------|------------|
{results_table}
## Canonical Mismatches
URLs where Google selected a different canonical than declared:
| URL | User Canonical | Google Canonical |
|-----|---------------|-----------------|
{canonical_mismatches_table}
## Common Issues
| Issue | Count | Priority | Action |
|-------|-------|----------|--------|
{issues_table}
## Rich Results Detected
| URL | Rich Result Type | Status |
|-----|-----------------|--------|
{rich_results_table}
---
*URL Inspection API: 2,000 inspections/day per site, 600/min.*
*Generated {timestamp} via Google Search Console URL Inspection API.*
LICENSE.txt
MIT License - see repository root LICENSE file for complete terms.
Copyright (c) 2026 AgriciDaniel
https://github.com/AgriciDaniel/claude-seo
references/auth-setup.md
# Google API Authentication Setup
## Overview
Three credential types serve different APIs:
| Type | Used By | Cost |
|------|---------|------|
| **API Key** | PageSpeed Insights, CrUX, CrUX History, Knowledge Graph | Free |
| **Service Account** | Search Console, Indexing API, GA4 | Free |
| **Both** | Full seo-google skill | Free |
## Step 1: Create a Google Cloud Project
1. Go to [console.cloud.google.com](https://console.cloud.google.com)
2. Click **Select a project** > **New Project**
3. Name it (e.g., "Claude SEO") and note the project ID
4. Select the project after creation
## Step 2: Enable APIs
Navigate to **APIs & Services > Library** and enable:
| API | Required For |
|-----|-------------|
| Google Search Console API | GSC Search Analytics, URL Inspection, Sitemaps |
| PageSpeed Insights API | PSI Lighthouse lab data |
| Chrome UX Report API | CrUX field data + History |
| Web Search Indexing API | Indexing API v3 |
| Google Analytics Data API | GA4 organic traffic |
| Knowledge Graph Search API | Entity verification (optional) |
## Step 3: Create an API Key
1. **APIs & Services > Credentials > Create Credentials > API key**
2. Click **Restrict key**:
- Under **API restrictions**, select: PageSpeed Insights API, Chrome UX Report API, Knowledge Graph Search API
3. Copy the generated API key and store it securely
## Step 4: Create a Service Account
1. **IAM & Admin > Service Accounts > Create Service Account**
2. Name: `claude-seo` (or similar)
3. Skip optional permissions steps
4. Click on the created service account > **Keys > Add Key > Create new key > JSON**
5. Download the JSON file and store it securely (e.g., `~/.config/claude-seo/service_account.json`)
The JSON file looks like:
```json
{
"type": "service_account",
"project_id": "your-project-id",
"private_key_id": "...",
"private_key": "<service-account-private-key>",
"client_email": "<service-account-identifier>",
"client_id": "...",
"auth_uri": "https://accounts.google.com/o/oauth2/auth",
"token_uri": "https://oauth2.googleapis.com/token"
}
```
The `client_email` field is what you add to GSC and GA4.
## Step 5: Grant Search Console Access
1. Go to [Google Search Console](https://search.google.com/search-console)
2. Select your property
3. **Settings > Users and permissions > Add user**
4. Paste the service account `client_email`
5. Set permission level:
- **Full** for read-only (Search Analytics, URL Inspection, Sitemaps)
- **Owner** if you also need the Indexing API
## Step 6: Grant GA4 Access
1. Go to [Google Analytics](https://analytics.google.com)
2. **Admin > Property Access Management > Add users** (the + icon)
3. Paste the service account `client_email`
4. Set role: **Viewer** (minimum for read-only reporting)
5. Note the numeric property ID from **Admin > Property Details** (e.g., `123456789`)
## Step 7: Create Config File
```bash
mkdir -p ~/.config/claude-seo
```
Save to `~/.config/claude-seo/google-api.json`:
```json
{
"service_account_path": "~/.config/claude-seo/service_account.json",
"api_key": "<GOOGLE_API_KEY>",
"default_property": "sc-domain:example.com",
"ga4_property_id": "properties/123456789"
}
```
### Property URL Formats
| Format | Example | When to Use |
|--------|---------|-------------|
| Domain property | `sc-domain:example.com` | Covers all URLs on the domain (recommended) |
| URL-prefix property | `https://example.com/` | Covers only that specific prefix |
## Step 8: Verify Setup
```bash
claude-seo run google_auth.py --check
```
Expected output at Tier 2 (full):
```
Credential Tier: 2 -- Full (API key + Service Account + GA4)
[OK] PageSpeed Insights v5
[OK] Chrome UX Report (CrUX) API
[OK] CrUX History API
[OK] Google Search Console API
Service account: configured
[OK] Google Indexing API v3
[OK] GA4 Data API v1beta
```
## Environment Variable Alternatives
Instead of (or in addition to) the config file:
| Variable | Purpose |
|----------|---------|
| `GOOGLE_API_KEY` | API key for PSI/CrUX |
| `GOOGLE_APPLICATION_CREDENTIALS` | Path to service account JSON |
| `GA4_PROPERTY_ID` | GA4 property (e.g., `properties/123456789`) |
| `GSC_PROPERTY` | Default GSC property (e.g., `sc-domain:example.com`) |
## OAuth Scopes Used
| Scope | APIs |
|-------|------|
| `https://www.googleapis.com/auth/webmasters.readonly` | GSC (read) |
| `https://www.googleapis.com/auth/webmasters` | GSC (read/write, needed for sitemap submission) |
| `https://www.googleapis.com/auth/indexing` | Indexing API |
| `https://www.googleapis.com/auth/analytics.readonly` | GA4 (read) |
## Troubleshooting
| Error | Fix |
|-------|-----|
| `403 Forbidden` on GSC | Service account email not added to GSC property, or wrong permission level |
| `403 Forbidden` on GA4 | Service account email not added to GA4 property as Viewer |
| `404 Not Found` on GSC | Wrong property URL format. Use `sc-domain:` or include trailing slash for URL-prefix |
| `404 Not Found` on CrUX | Site has insufficient Chrome traffic. Not a credentials issue. |
| `429 Rate Limit` | Wait and retry. See rate-limits-quotas.md for per-API limits |
| `API not enabled` | Enable the specific API in GCP Console > APIs & Services > Library |
references/dma-consent-mode-v2.md
# DMA + Consent Mode v2 — click-through impact diagnostic
EU traffic flowing through Google Search has been subject to the
**Digital Markets Act** since 2024-03-07. The DMA limits how Google
can use behavioural data and forces consent-mode v2 compliance for
GA4 + Ads in EU. The operational effect for SEO audits:
- EU click-through-rate (CTR) data from Search Console is materially
noisier after the DMA enforcement date. Year-over-year CTR
comparisons across that boundary are not apples-to-apples.
- GA4 organic-traffic reports for EU users show systematic
under-reporting when consent-mode v2 is configured as "denied for
ad_storage, granted for analytics_storage" (the typical EU default).
Conversion modelling fills the gap, but the raw counts are lower
than pre-2024.
## What the seo-google skill should do
1. **When pulling GSC search analytics for an EU-targeted property**,
note: "EU CTR comparisons before/after 2024-03-07 are not
apples-to-apples (DMA + consent-mode v2 took effect)."
2. **When pulling GA4 organic traffic**, surface the consent-mode
configuration if the GA4 admin API exposes it. If the property
has `eu_data_collection_disabled` or denies ad_storage by default,
note "EU traffic counts are conservative; conversion-modelled
uplift may apply."
3. **Do not lecture the user on cookie consent UX** — that's a legal
team / engineering concern outside SEO scope. Just attach the
diagnostic note.
## Required GA4 / Consent Mode setup the audit should check for
- GA4 4.x consent-mode v2 wired up (`gtag('consent', 'default', ...)`
before any pageview).
- `ads_data_redaction` flag set on EU traffic.
- Server-side tagging at consent transitions (recommended pattern;
not legally required).
## Softening cookieless-attribution warnings
Google **abandoned** third-party cookie deprecation in July 2024 and
confirmed in April 2025 that Chrome will not ship a standalone cookie
prompt. The "cookieless future" framing is no longer urgent.
For audits as of May 2026:
- Do NOT recommend "switch to cookieless attribution" as a priority.
- DO recommend "implement consent-mode v2 + server-side tagging" for
EU compliance + signal-loss recovery.
- Privacy Sandbox APIs are still available, but optional. Mention
only if the audit subject is consumer-facing and has heavy
retargeting dependence.
## Primary sources
- DMA enforcement: https://digital-markets-act.ec.europa.eu/
- Google's third-party cookie reversal: https://privacysandbox.com/news/privacy-sandbox-update/
- Consent Mode v2, EEA consent updates: https://support.google.com/google-ads/answer/13695607
Last verified: 2026-05-17.
references/ga4-data-api.md
# GA4 Data API v1beta Reference
## Overview
The Google Analytics Data API v1beta provides programmatic access to GA4 report data. For SEO, the primary use case is organic traffic analysis.
**Base URL:** `https://analyticsdata.googleapis.com/v1beta`
## Key Methods
| Method | Description |
|--------|-------------|
| `properties.runReport` | Run a standard report |
| `properties.batchRunReports` | Up to 5 reports in one call |
| `properties.runRealtimeReport` | Last 30 minutes of data |
| `properties.getMetadata` | Available dimensions and metrics |
| `properties.checkCompatibility` | Verify dimension/metric combinations |
## runReport Request
```json
{
"property": "properties/123456789",
"dimensions": [
{ "name": "date" },
{ "name": "landingPage" }
],
"metrics": [
{ "name": "sessions" },
{ "name": "totalUsers" }
],
"dateRanges": [
{ "startDate": "28daysAgo", "endDate": "yesterday" }
],
"dimensionFilter": {
"filter": {
"fieldName": "sessionDefaultChannelGroup",
"stringFilter": {
"matchType": "EXACT",
"value": "Organic Search"
}
}
},
"orderBys": [
{ "metric": { "metricName": "sessions" }, "desc": true }
],
"limit": 100,
"returnPropertyQuota": true
}
```
## SEO-Relevant Dimensions
| Dimension | Description |
|-----------|-------------|
| `date` | Date in YYYYMMDD format |
| `pagePath` | Page path (e.g., `/blog/post`) |
| `landingPage` | Entry page path |
| `landingPagePlusQueryString` | Entry page with query params |
| `fullPageUrl` | Full page URL |
| `pageTitle` | Page title |
| `sessionSource` | Traffic source (e.g., `google`) |
| `sessionMedium` | Traffic medium (e.g., `organic`) |
| `sessionDefaultChannelGroup` | Channel grouping (e.g., `Organic Search`) |
| `country` | User country |
| `deviceCategory` | `desktop`, `mobile`, `tablet` |
| `hostName` | Domain name |
| `pageReferrer` | Referrer URL |
## SEO-Relevant Metrics
| Metric | Description |
|--------|-------------|
| `sessions` | Number of sessions |
| `totalUsers` | Total unique users |
| `newUsers` | First-time users |
| `activeUsers` | Users with engagement |
| `screenPageViews` | Page views |
| `bounceRate` | Bounce rate (0-1, multiply by 100 for %) |
| `averageSessionDuration` | Avg duration in seconds |
| `engagementRate` | Engaged session rate (0-1) |
| `keyEvents` | Key events (replaced deprecated `conversions`) |
| `eventCount` | Total event count |
## Filter Expressions
### String Filter
```json
{
"filter": {
"fieldName": "sessionDefaultChannelGroup",
"stringFilter": {
"matchType": "EXACT",
"value": "Organic Search"
}
}
}
```
Match types: `EXACT`, `BEGINS_WITH`, `ENDS_WITH`, `CONTAINS`, `FULL_REGEXP`, `PARTIAL_REGEXP`
### Combining Filters
```json
{
"andGroup": {
"expressions": [
{ "filter": { "fieldName": "country", "stringFilter": { "matchType": "EXACT", "value": "US" }}},
{ "filter": { "fieldName": "deviceCategory", "stringFilter": { "matchType": "EXACT", "value": "mobile" }}}
]
}
}
```
Also supports `orGroup` and `notExpression`.
## Date Range Shortcuts
| Value | Meaning |
|-------|---------|
| `today` | Current day |
| `yesterday` | Previous day |
| `NdaysAgo` | N days ago (e.g., `28daysAgo`) |
| `YYYY-MM-DD` | Specific date |
Up to 4 date ranges per request (for period-over-period comparison).
## Token-Based Quotas
| Quota | Limit | Scope |
|-------|-------|-------|
| Daily tokens | 25,000 | Per property per project |
| Hourly tokens | 5,000 | Per property per project |
| Concurrent requests | 10 | Per property per project |
| Hourly tokens (project-wide) | 1,250 | Per project per property per hour |
Set `returnPropertyQuota: true` to monitor consumption. Simple reports cost ~1-10 tokens; complex ones up to ~100.
## Python Example
```python
from google.analytics.data_v1beta import BetaAnalyticsDataClient
from google.analytics.data_v1beta.types import (
DateRange, Dimension, Filter, FilterExpression,
Metric, OrderBy, RunReportRequest,
)
from google.oauth2 import service_account
credentials = service_account.Credentials.from_service_account_file(
"service_account.json",
scopes=["https://www.googleapis.com/auth/analytics.readonly"],
)
client = BetaAnalyticsDataClient(credentials=credentials)
request = RunReportRequest(
property="properties/123456789",
dimensions=[Dimension(name="landingPage")],
metrics=[Metric(name="sessions"), Metric(name="totalUsers")],
date_ranges=[DateRange(start_date="28daysAgo", end_date="yesterday")],
dimension_filter=FilterExpression(
filter=Filter(
field_name="sessionDefaultChannelGroup",
string_filter=Filter.StringFilter(
match_type=Filter.StringFilter.MatchType.EXACT,
value="Organic Search",
),
)
),
order_bys=[OrderBy(metric=OrderBy.MetricOrderBy(metric_name="sessions"), desc=True)],
limit=50,
return_property_quota=True,
)
response = client.run_report(request)
for row in response.rows:
print(f"{row.dimension_values[0].value}: {row.metric_values[0].value} sessions")
```
## Authentication
- **Scope:** `https://www.googleapis.com/auth/analytics.readonly`
- Service account must have **Viewer** role in GA4 property
- Add via GA4 Admin > Property Access Management
references/indexing-api.md
# Google Indexing API v3 Reference
## Overview
The Indexing API allows you to notify Google when pages are added or removed.
**IMPORTANT:** Use the Indexing API only for eligible URLs with **JobPosting** or **BroadcastEvent embedded in VideoObject** structured data. For a few ordinary URLs, use URL Inspection; for many ordinary URLs, use sitemaps. A `URL_UPDATED` notification only means Google may recrawl soon and gives no indexing benefit for other page types.
## Endpoints
### Publish Notification
**`POST https://indexing.googleapis.com/v3/urlNotifications:publish`**
```json
{
"url": "https://example.com/jobs/software-engineer",
"type": "URL_UPDATED"
}
```
| Field | Values |
|-------|--------|
| `url` | The fully qualified URL |
| `type` | `URL_UPDATED` (page added/changed), `URL_DELETED` (page removed) |
**Response:**
```json
{
"urlNotificationMetadata": {
"url": "https://example.com/jobs/software-engineer",
"latestUpdate": {
"url": "https://example.com/jobs/software-engineer",
"type": "URL_UPDATED",
"notifyTime": "2026-03-27T10:00:00Z"
}
}
}
```
### Get Notification Metadata
**`GET https://indexing.googleapis.com/v3/urlNotifications/metadata?url={ENCODED_URL}`**
Returns `latestUpdate` and `latestRemove` timestamps for the URL.
### Batch Requests
**`POST https://indexing.googleapis.com/batch`**
Up to **100 URLs** per batch request using `multipart/mixed` format:
```
POST /batch HTTP/1.1
Content-Type: multipart/mixed; boundary=batch_boundary
--batch_boundary
Content-Type: application/http
Content-ID: <item1>
POST /v3/urlNotifications:publish HTTP/1.1
Content-Type: application/json
{"url": "https://example.com/jobs/1", "type": "URL_UPDATED"}
--batch_boundary
Content-Type: application/http
Content-ID: <item2>
POST /v3/urlNotifications:publish HTTP/1.1
Content-Type: application/json
{"url": "https://example.com/jobs/2", "type": "URL_UPDATED"}
--batch_boundary--
```
## Authentication
- **Scope:** `https://www.googleapis.com/auth/indexing`
- **Auth type:** Service account (OAuth 2.0)
- The service account must be **Owner** in Google Search Console for the target domain
## Quotas
| Limit | Value | Scope |
|-------|-------|-------|
| Publish requests | **200/day** (default) | Per project |
| Read-only requests | 180/min | Per project |
| Total requests | 380/min | Per project |
Request a quota increase: [Indexing API Quota Increase Form](https://developers.google.com/search/apis/indexing-api/v3/quota)
## Error Codes
| Code | Meaning | Action |
|------|---------|--------|
| 400 | Malformed URL or request body | Check URL format |
| 403 | Permission denied or quota exceeded | Add service account as Owner in GSC, or check quota |
| 429 | Rate limit exceeded | Back off and retry with exponential delay |
| 500/503 | Server error | Retry with exponential backoff |
## Best Practices
1. Submit only URLs with actual content changes (don't spam updates)
2. Use `URL_DELETED` only when a page is permanently removed (returns 404/410)
3. Track daily quota usage -- the 200/day limit resets at midnight Pacific Time
4. For large-scale indexing, use XML sitemaps + Search Console instead
5. Batch requests count individually against the daily quota (100 batch = 100 quota)
references/keyword-planner-api.md
# Google Ads API - Keyword Planner Reference
Gold-standard source for keyword search volume. DataForSEO gets its volume data from Google Ads -- this cuts out the middleman.
## Prerequisites (More Complex Than Other Google APIs)
1. **Google Ads Manager Account** -- create at ads.google.com (free to create)
2. **Developer Token** -- apply at Google Ads API Center (requires Basic access approval)
3. **OAuth 2.0 credentials** -- reuse existing OAuth client from seo-google config
4. **For exact volumes**: Run a minimal campaign (~$5-10/day). Without spend, volumes are bucketed ranges ("1K-10K")
## Key Methods
### GenerateKeywordIdeas
Generate keyword suggestions from seed terms.
**Returns per keyword:**
- `text`: Keyword string
- `avg_monthly_searches`: Average monthly volume (exact if spending, bucketed if not)
- `competition`: LOW / MEDIUM / HIGH (for ads, not organic)
- `competition_index`: 0-100 competition score
- `low_top_of_page_bid_micros`: ~20th percentile CPC in micros
- `high_top_of_page_bid_micros`: ~80th percentile CPC in micros
- `monthly_search_volumes[]`: Per-month volume for last 12 months
### GenerateKeywordHistoricalMetrics
Get volume data for specific keywords.
Same return fields as above but for exact keyword list instead of suggestions.
### GenerateKeywordForecastMetrics
Predict clicks, impressions, and cost for keywords.
## Configuration
Add to `~/.config/claude-seo/google-api.json`:
```json
{
"ads_developer_token": "YOUR_DEV_TOKEN",
"ads_customer_id": "123-456-7890",
"ads_login_customer_id": "123-456-7890"
}
```
## Rate Limits
- Keyword Planning requests are more strictly rate-limited than other Ads API services
- Exact QPM/QPS not publicly documented
- Google recommends caching results
## Python Library
```bash
pip install google-ads
```
Uses `google-ads` library (separate from `google-api-python-client`).
## Important Notes
- **Volume accuracy**: Without active ad spend, Google returns bucketed ranges ("1K-10K", "10K-100K") instead of exact numbers like "14,800"
- **Competition score**: Measures advertiser competition for ads, NOT organic ranking difficulty
- **CPC bids**: Reflect what advertisers pay, useful for estimating keyword commercial value
- **Location targeting**: Use location IDs (2840 = United States, 2826 = United Kingdom)
- **Language targeting**: Use language IDs (1000 = English, 1003 = Spanish)
references/nlp-api.md
# Google Cloud Natural Language API Reference
Use NLP analysis as an internal diagnostic for entity coverage, content sentiment, and topic classification. Do not present it as Google's E-E-A-T score, a ranking factor, or a proxy for numeric E-E-A-T weights.
## Endpoint
Entities use `POST https://language.googleapis.com/v1/documents:analyzeEntities`.
Sentiment, classification, and moderation use
`POST https://language.googleapis.com/v2/documents:annotateText`.
Send the API key in the `X-Goog-Api-Key` header, not in the URL.
## Features
| Feature | What It Does | SEO Use |
|---------|-------------|---------|
| `extractEntities` | Extract people, orgs, places, events with salience scores | Topic coverage depth, entity optimization |
| `extractDocumentSentiment` | Document + sentence-level sentiment score/magnitude | Content tone assessment |
| `classifyText` | Map content to 700+ Google categories | Topic relevance verification |
| `moderateText` | Content safety/moderation categories | Content quality flags |
## Entity Types
PERSON, LOCATION, ORGANIZATION, EVENT, WORK_OF_ART, CONSUMER_GOOD, OTHER, PHONE_NUMBER, ADDRESS, DATE, NUMBER, PRICE
Each entity includes:
- `name`: Entity text
- `type`: Entity type
- `salience`: Importance score (0-1, higher = more relevant)
- `sentiment`: Per-entity sentiment (score + magnitude)
- `metadata`: Wikipedia URL, MID (Knowledge Graph ID)
- `mentions`: Occurrences in the text
## Sentiment Scoring
- **Score**: -1.0 (negative) to +1.0 (positive)
- **Magnitude**: 0 to infinity (emotional intensity, higher = more emotional)
- Neutral content: score ~0, low magnitude
- Mixed content: score ~0, HIGH magnitude (both positive and negative)
## Pricing
| Feature | Free/month | Paid (per 1K chars) |
|---------|-----------|-------------------|
| Entity Analysis | 5,000 units | $0.001 |
| Sentiment Analysis | 5,000 units | $0.001 |
| Content Classification | 30,000 units | $0.002 |
| Text Moderation | 50,000 units | $0.0005 |
One "unit" = 1,000 characters. Free tier resets monthly.
## Enable the API
1. Go to [console.cloud.google.com/apis/library](https://console.cloud.google.com/apis/library)
2. Search for "Cloud Natural Language API"
3. Click Enable
4. **Billing must be enabled** on the project (free tier still applies)
Uses the same API key as PSI/CrUX.
references/pagespeed-crux-api.md
# PageSpeed Insights v5 + CrUX API Reference
## Table of Contents
1. [PageSpeed Insights v5](#pagespeed-insights-v5)
2. [CrUX API (Daily)](#crux-api-daily)
3. [CrUX History API (Weekly)](#crux-history-api-weekly)
4. [Core Web Vitals Thresholds](#core-web-vitals-thresholds)
---
## PageSpeed Insights v5
**Endpoint:** `GET https://www.googleapis.com/pagespeedonline/v5/runPagespeed`
### Parameters
| Param | Type | Description |
|-------|------|-------------|
| `url` | string | Required. URL to analyze. |
| `category` | string | `ACCESSIBILITY`, `BEST_PRACTICES`, `PERFORMANCE`, `SEO`. Can specify multiple. |
| `strategy` | string | `DESKTOP` or `MOBILE` (default). |
| `locale` | string | Locale for text (e.g., `en`). |
| `key` | string | API key. Optional but recommended for quota. |
### Response Structure
```
{
"id": "https://example.com/",
"loadingExperience": { ... }, // URL-level CrUX data
"originLoadingExperience": { ... }, // Origin-level CrUX data
"lighthouseResult": {
"categories": {
"performance": { "score": 0.85 },
"accessibility": { "score": 0.92 },
"best-practices": { "score": 0.88 },
"seo": { "score": 0.95 }
},
"audits": { ... } // Individual audit results
},
"analysisUTCTimestamp": "2026-03-27T..."
}
```
### Field Data Metrics (in loadingExperience)
| PSI Key | CrUX Metric | Unit |
|---------|-------------|------|
| `LARGEST_CONTENTFUL_PAINT_MS` | LCP | ms |
| `INTERACTION_TO_NEXT_PAINT` | INP | ms |
| `CUMULATIVE_LAYOUT_SHIFT_SCORE` | CLS | unitless |
| `FIRST_CONTENTFUL_PAINT_MS` | FCP | ms |
| `EXPERIMENTAL_TIME_TO_FIRST_BYTE` | TTFB | ms |
Each metric contains: `percentile` (p75), `distributions[]` ({min, max, proportion}), `category` (FAST/AVERAGE/SLOW/NONE).
### Key Lighthouse Audit IDs
`first-contentful-paint`, `largest-contentful-paint`, `total-blocking-time`, `cumulative-layout-shift`, `speed-index`, `interactive`
### Rate Limits
- 25,000 QPD with API key
- 240 QPM
- Free, no billing required
### Note on Field Data Migration
Google is migrating CrUX field data out of PSI. For field data, prefer the CrUX API directly. Use PSI primarily for Lighthouse lab data.
---
## CrUX API (Daily)
**Endpoint:** `POST https://chromeuxreport.googleapis.com/v1/records:queryRecord`
Send the API key in the `X-Goog-Api-Key` header, not in the URL.
### Request
```json
{
"origin": "https://example.com",
"formFactor": "PHONE",
"metrics": ["largest_contentful_paint", "interaction_to_next_paint", "cumulative_layout_shift"]
}
```
| Field | Description |
|-------|-------------|
| `origin` | Origin URL (mutually exclusive with `url`) |
| `url` | Specific page URL (mutually exclusive with `origin`) |
| `formFactor` | `DESKTOP`, `PHONE`, `TABLET` (optional, omit for all) |
| `metrics` | Array of metric names (optional, omit for all) |
### Available Metrics
| Metric | Type | Notes |
|--------|------|-------|
| `largest_contentful_paint` | int (ms) | Core Web Vital |
| `largest_contentful_paint_resource_type` | fractions | LCP resource type, added January 2025 |
| `largest_contentful_paint_image_time_to_first_byte` | int (ms) | LCP image subpart, added January 2025 |
| `largest_contentful_paint_image_resource_load_delay` | int (ms) | LCP image subpart, added January 2025 |
| `largest_contentful_paint_image_resource_load_duration` | int (ms) | LCP image subpart, added January 2025 |
| `largest_contentful_paint_image_element_render_delay` | int (ms) | LCP image subpart, added January 2025 |
| `interaction_to_next_paint` | int (ms) | Core Web Vital (replaced FID) |
| `cumulative_layout_shift` | **string** | Core Web Vital. **String-encoded!** Parse carefully. |
| `first_contentful_paint` | int (ms) | |
| `experimental_time_to_first_byte` | int (ms) | |
| `round_trip_time` | int (ms) | Replaced effectiveConnectionType (Feb 2025) |
| `navigation_types` | fractions | navigate, navigate_cache, reload, etc. |
| `form_factors` | fractions | desktop/phone/tablet distribution |
### Response
```json
{
"record": {
"key": { "origin": "https://example.com" },
"metrics": {
"largest_contentful_paint": {
"histogram": [
{ "start": 0, "end": 2500, "density": 0.72 },
{ "start": 2500, "end": 4000, "density": 0.18 },
{ "start": 4000, "density": 0.10 }
],
"percentiles": { "p75": 2100 }
},
"cumulative_layout_shift": {
"percentiles": { "p75": "0.05" }
}
},
"collectionPeriod": {
"firstDate": { "year": 2026, "month": 2, "day": 27 },
"lastDate": { "year": 2026, "month": 3, "day": 26 }
}
}
}
```
### Important
- **CLS p75 is a string** (e.g., `"0.05"` not `0.05`). Always parse as float from string.
- Last histogram bin has **no `end`** (extends to infinity).
- Densities sum to approximately 1.0.
- **404** = no data (insufficient Chrome traffic). Not an auth error.
- Updated daily ~04:00 UTC with ~2 day lag.
### Rate Limits
- 150 QPM shared between CrUX and CrUX History APIs
- Free, no paid increase available
---
## CrUX History API (Weekly)
**Endpoint:** `POST https://chromeuxreport.googleapis.com/v1/records:queryHistoryRecord`
Send the API key in the `X-Goog-Api-Key` header, not in the URL.
Same request format as CrUX API. Returns up to **25 weekly collection periods**.
### Response Differences from CrUX API
Instead of single values, returns timeseries:
```json
{
"record": {
"metrics": {
"largest_contentful_paint": {
"histogramTimeseries": [
{ "start": 0, "end": 2500, "densities": [0.70, 0.71, 0.72, ...] },
{ "start": 2500, "end": 4000, "densities": [0.19, 0.18, 0.18, ...] },
{ "start": 4000, "densities": [0.11, 0.11, 0.10, ...] }
],
"percentilesTimeseries": {
"p75s": [2200, 2150, 2100, ...]
}
}
},
"collectionPeriods": [
{
"firstDate": { "year": 2025, "month": 9, "day": 29 },
"lastDate": { "year": 2025, "month": 10, "day": 26 }
},
...
]
}
}
```
### NaN Handling
- `"NaN"` string for densities in ineligible periods
- `null` for percentile values in ineligible periods
- Always check for these before numeric operations
### Update Schedule
- Updated **Mondays** ~04:00 UTC
- Each period = 28-day rolling average ending on a Sunday
---
## Core Web Vitals Thresholds
Current as of 2026-07-09; no threshold changes were announced. INP replaced FID on March 12, 2024.
| Metric | Good | Needs Improvement | Poor |
|--------|------|-------------------|------|
| **LCP** | ≤ 2,500ms | 2,500–4,000ms | > 4,000ms |
| **INP** | ≤ 200ms | 200–500ms | > 500ms |
| **CLS** | ≤ 0.1 | 0.1–0.25 | > 0.25 |
| **FCP** | ≤ 1,800ms | 1,800–3,000ms | > 3,000ms |
| **TTFB** | ≤ 800ms | 800–1,800ms | > 1,800ms |
FID was removed from Chrome's field-data tools (CrUX, PSI) on September 9, 2024. Never reference FID in outputs.
references/rate-limits-quotas.md
# Google API Rate Limits & Quotas
## Consolidated Quota Table
| API | Per-Minute | Per-Day | Cost | Auth Type | Scope |
|-----|-----------|---------|------|-----------|-------|
| GSC Search Analytics | 1,200 QPM/user, 1,200 QPM/site | 30M QPD/project | Free | Service Account | Per user + per site |
| GSC URL Inspection | 600 QPM/site | 2,000 QPD/site | Free | Service Account | Per site |
| GSC Sitemaps | Standard | Standard | Free | Service Account | Per site |
| PageSpeed Insights v5 | 240 QPM | 25,000 QPD | Free | API Key | Per project |
| CrUX API | 150 QPM (shared) | Unlimited | Free | API Key | Per project |
| CrUX History API | 150 QPM (shared with CrUX) | Unlimited | Free | API Key | Per project |
| Indexing API | 380 RPM total, 180 read/min | 200 publish/day | Free | Service Account | Per project |
| GA4 Data API | 10 concurrent | ~25K tokens/day | Free | Service Account | Per property/project |
| Knowledge Graph | -- | 100,000 QPD | Free | API Key | Per project |
| Custom Search | -- | 10,000 QPD max | 100 free, $5/1K | API Key | Per project |
| Web Risk | 6,000 QPM | 100K/month | Free tier | API Key | Per project |
**Key distinction:** "Per site" quotas are scoped to a specific GSC property. "Per project" quotas are shared across all properties in a GCP project. "Per user" quotas are per authenticated user (service account).
## Exponential Backoff Strategy
When receiving 429 or 5xx errors:
```
Attempt 1: wait 1 second
Attempt 2: wait 2 seconds
Attempt 3: wait 4 seconds
Attempt 4: wait 8 seconds
Attempt 5: wait 16 seconds
Max: give up after 5 retries
```
Add random jitter (0-500ms) to each wait to avoid thundering herd.
## Common Error Codes
| Code | Meaning | Applies To | Action |
|------|---------|------------|--------|
| 400 | Bad request | All | Check URL format, request body |
| 401 | Unauthorized | Service Account APIs | Refresh credentials |
| 403 | Forbidden | GSC, GA4, Indexing | Check permissions (service account access) |
| 404 | Not found | CrUX, GSC | Insufficient traffic (CrUX) or invalid property (GSC) |
| 429 | Rate limited | All | Backoff and retry. Check Retry-After header. |
| 500 | Server error | All | Retry with backoff |
| 503 | Service unavailable | All | Retry with backoff |
## Retry-After Header
Some Google APIs return a `Retry-After` header with 429 responses. When present, use this value (in seconds) instead of exponential backoff.
## GA4 Token Budgeting
GA4 uses a token system rather than simple request counts:
- Simple 1-dimension, 1-metric report: ~1-5 tokens
- Complex multi-dimension, multi-metric: ~10-100 tokens
- Set `returnPropertyQuota: true` to monitor consumption
- Daily limit: 25,000 tokens per property per project
- Hourly limit: 5,000 tokens per property per project
- Concurrent: max 10 simultaneous requests
## CrUX Shared Quota
The CrUX API and CrUX History API share the same 150 QPM quota per project. Plan accordingly if querying both APIs in the same workflow.
## Cost Summary
**All APIs used by seo-google are free** at normal usage levels. No billing is required for:
- PSI, CrUX, CrUX History (API key, unlimited free)
- GSC (service account, 30M QPD)
- Indexing API (service account, 200 publish/day)
- GA4 Data API (service account, 25K tokens/day)
- Knowledge Graph (API key, 100K QPD)
Only Custom Search and Web Risk have paid tiers at high volumes.
references/search-console-api.md
# Google Search Console API Reference
## Table of Contents
1. [Search Analytics API](#search-analytics-api)
2. [URL Inspection API](#url-inspection-api)
3. [Sitemaps API](#sitemaps-api)
4. [Sites API](#sites-api)
---
## Search Analytics API
**Endpoint:** `POST https://www.googleapis.com/webmasters/v3/sites/{siteUrl}/searchAnalytics/query`
### Request Body
| Field | Type | Description |
|-------|------|-------------|
| `startDate` | string | Required. YYYY-MM-DD format. |
| `endDate` | string | Required. YYYY-MM-DD format. |
| `dimensions` | string[] | `query`, `page`, `country`, `device`, `date`, `searchAppearance` |
| `type` | string | `web` (default), `image`, `video`, `news`, `discover`, `googleNews` |
| `dimensionFilterGroups` | object[] | Array of filter groups (see below) |
| `aggregationType` | string | `auto` (default), `byPage`, `byProperty`, `byNewsShowcasePanel` |
| `rowLimit` | int | 1-25000 (default: 1000) |
| `startRow` | int | Pagination offset (default: 0) |
| `dataState` | string | `final` (default), `all`, `hourly_all` |
### Dimension Filters
```json
{
"dimensionFilterGroups": [{
"filters": [{
"dimension": "query",
"operator": "contains",
"expression": "seo"
}]
}]
}
```
**Operators:** `contains`, `equals`, `notContains`, `notEquals`, `includingRegex`, `excludingRegex`
- Regex uses RE2 syntax, max 4096 characters
### Response
```json
{
"rows": [
{
"keys": ["seo tools", "https://example.com/tools"],
"clicks": 150,
"impressions": 5000,
"ctr": 0.03,
"position": 4.2
}
],
"responseAggregationType": "byPage"
}
```
### Important Notes
- Data has a **2-3 day lag**. Available for approximately 16 months.
- If a date range overlaps 2025-05-13 to 2026-04-27, clicks remain usable but impressions, CTR, and average position require caution due to the confirmed GSC impressions logging error.
- `discover` and `googleNews` types do not support `query` dimension or `position` metric.
- Country codes are **ISO 3166-1 alpha-3** (e.g., `USA`, `GBR`, `DEU`).
- Pagination: increment `startRow` by `rowLimit` until fewer rows returned.
- `rowLimit` is per API request. The claude-seo `--limit` option is a total
result cap and requests are paged internally at up to 25,000 rows.
- For accurate site totals, omit `query` and `page` dimensions. Query rows can
omit anonymized low-volume traffic and must not be summed as site totals.
- Only present totals as authoritative when the script returns
`"totals_complete": true`.
### Rate Limits
- 1,200 QPM per user
- 1,200 QPM per site
- 40,000 QPM / 30,000,000 QPD per project
---
## URL Inspection API
**Endpoint:** `POST https://searchconsole.googleapis.com/v1/urlInspection/index:inspect`
### Request
```json
{
"inspectionUrl": "https://example.com/page",
"siteUrl": "sc-domain:example.com",
"languageCode": "en"
}
```
### Response Fields
**`indexStatusResult`:**
| Field | Values |
|-------|--------|
| `verdict` | `PASS`, `FAIL`, `NEUTRAL`, `PARTIAL`, `VERDICT_UNSPECIFIED` |
| `coverageState` | Human-readable coverage description |
| `robotsTxtState` | `ALLOWED`, `DISALLOWED` |
| `indexingState` | `INDEXING_ALLOWED`, `BLOCKED_BY_META_TAG`, `BLOCKED_BY_HTTP_HEADER` |
| `pageFetchState` | `SUCCESSFUL`, `SOFT_404`, `BLOCKED_ROBOTS_TXT`, `NOT_FOUND`, `ACCESS_DENIED`, `SERVER_ERROR`, `REDIRECT_ERROR`, `ACCESS_FORBIDDEN`, `BLOCKED_4XX`, `INTERNAL_CRAWL_ERROR`, `INVALID_URL` |
| `lastCrawlTime` | ISO 8601 timestamp |
| `googleCanonical` | URL Google selected as canonical |
| `userCanonical` | URL declared canonical by the page |
| `crawledAs` | `DESKTOP`, `MOBILE` |
**`richResultsResult`:** Verdict + detected rich result types (e.g. Product, Breadcrumb, Review snippet, Event). Note: FAQPage and HowTo no longer produce rich results (FAQ retired 2026-05-07; HowTo retired 2023), so don't expect them as current rich-result types.
### Rate Limits
- 2,000 QPD / 600 QPM per site
- 10,000,000 QPD / 15,000 QPM per project
---
## Sitemaps API
**Base:** `https://www.googleapis.com/webmasters/v3/sites/{siteUrl}/sitemaps`
| Method | Endpoint | Description |
|--------|----------|-------------|
| `GET` | `/sitemaps` | List all sitemaps |
| `GET` | `/sitemaps/{feedpath}` | Get specific sitemap |
| `PUT` | `/sitemaps/{feedpath}` | Submit a sitemap |
| `DELETE` | `/sitemaps/{feedpath}` | Delete a sitemap |
### Sitemap Resource
| Field | Description |
|-------|-------------|
| `path` | URL of the sitemap |
| `lastSubmitted` | Last submission timestamp |
| `isPending` | Whether processing is incomplete |
| `isSitemapsIndex` | Whether this is a sitemap index |
| `type` | `sitemap`, `atomFeed`, `rssFeed`, `urlList`, `notSitemap` |
| `warnings` | Warning count |
| `errors` | Error count |
| `contents[]` | Array with `type` (web, image, video, news) and `submitted` count |
---
## Sites API
**Base:** `https://www.googleapis.com/webmasters/v3/sites`
| Method | Endpoint | Description |
|--------|----------|-------------|
| `GET` | `/sites` | List all verified properties |
| `GET` | `/sites/{siteUrl}` | Get property info |
| `PUT` | `/sites/{siteUrl}` | Add a property |
| `DELETE` | `/sites/{siteUrl}` | Remove a property |
### Property Resource
| Field | Values |
|-------|--------|
| `siteUrl` | Property URL (e.g., `sc-domain:example.com`) |
| `permissionLevel` | `siteOwner`, `siteFullUser`, `siteRestrictedUser`, `siteUnverifiedUser` |
references/supplementary-apis.md
# Supplementary Google APIs for SEO
## Knowledge Graph Search API
Verify brand/entity presence in Google's Knowledge Graph.
**Endpoint:** `GET https://kgsearch.googleapis.com/v1/entities:search`
| Param | Description |
|-------|-------------|
| `query` | Search query |
| `ids` | Specific entity IDs (e.g., `/m/0d6lp`) |
| `languages` | Language codes (e.g., `en`) |
| `types` | Schema.org types to filter (e.g., `Organization`, `Person`) |
| `limit` | Max results (1-500) |
| `key` | API key (required) |
**Response:**
```json
{
"itemListElement": [{
"result": {
"@id": "kg:/m/0d6lp",
"name": "Google",
"@type": ["Organization", "Corporation"],
"description": "Technology company",
"detailedDescription": {
"articleBody": "Google LLC is an American...",
"url": "https://en.wikipedia.org/wiki/Google"
},
"image": { "url": "..." },
"url": "https://www.google.com"
},
"resultScore": 4892.5
}]
}
```
**Use for SEO:** Verify if a brand has a Knowledge Panel, check entity disambiguation, find related entities.
**Quota:** 100,000 reads/day. Free. API key only.
---
## Custom Search JSON API
Programmatic Google search results (limited).
**Endpoint:** `GET https://customsearch.googleapis.com/customsearch/v1`
| Param | Description |
|-------|-------------|
| `key` | API key (required) |
| `cx` | Programmable Search Engine ID (required) |
| `q` | Search query |
| `num` | Results per page (1-10) |
| `start` | Start index (max 91) |
| `dateRestrict` | Date restriction (e.g., `d30` for 30 days) |
| `gl` | Country (e.g., `us`) |
| `lr` | Language restriction |
| `searchType` | `image` for image search |
| `siteSearch` | Restrict to a domain |
**Limitations:**
- Max 100 total results per query (10 pages x 10 results)
- **CLOSED to new customers as of 2025.** Existing customers must migrate by January 2027.
- 100 queries/day free, $5 per 1,000 up to 10,000/day
**For SERP data, prefer DataForSEO** (`/seo dataforseo serp`) which has no such limitations.
---
## Web Risk API
Check if URLs are flagged as unsafe by Google Safe Browsing.
**Endpoint:** `GET https://webrisk.googleapis.com/v1/uris:search`
| Param | Description |
|-------|-------------|
| `threatTypes` | `MALWARE`, `SOCIAL_ENGINEERING`, `UNWANTED_SOFTWARE`, `SOCIAL_ENGINEERING_EXTENDED_COVERAGE` |
| `uri` | URL to check |
| `key` | API key (required) |
**Response (clean URL):** Empty threat object.
**Response (flagged URL):**
```json
{
"threat": {
"threatTypes": ["MALWARE"],
"expireTime": "2026-04-01T00:00:00Z"
}
}
```
**Use for SEO:** Check if pages are flagged (could explain deindexing), verify competitor safety, audit outbound links.
**Quota:** 6,000 QPM. 100,000/month free tier. Requires billing enabled on GCP project.
references/youtube-api.md
# YouTube Data API v3 Reference
YouTube mentions may correlate with AI visibility in some GEO research, but this file does not include a Google-owned source or methodology for a numeric benchmark. This API provides authoritative YouTube data directly from Google.
## Endpoints Used
| Method | Quota Cost | Description |
|--------|-----------|-------------|
| `search.list` | 100 units | Search for videos matching a query |
| `videos.list` | 1 unit | Get video details, statistics, content |
| `channels.list` | 1 unit | Get channel info, subscriber count |
| `commentThreads.list` | 1 unit | Get top comments on a video |
## Daily Quota
Default: **10,000 units/day** (free). This allows:
- ~100 searches per day, OR
- ~10,000 video/channel lookups per day
## Data Available
### Video Search
- Title, channel, channel ID, published date
- Description (first 300 chars), thumbnail URL
- Views, likes, comments count, duration
### Video Details
- Full description, tags, category ID
- Duration, definition (HD/SD), has captions
- Topic categories (Wikipedia URLs)
- Views, likes, comments, favorites
- Top 10 relevant comments with likes
### Channel Info
- Title, description, custom URL
- Subscriber count, video count, total views
- Country, published date, thumbnail
## Authentication
**API key only** (read-only public data). No OAuth needed.
## Enable the API
1. Go to [console.cloud.google.com/apis/library](https://console.cloud.google.com/apis/library)
2. Search for "YouTube Data API v3"
3. Click Enable
No billing required. The API key you already have for PSI/CrUX works.
SKILL.md
---
name: seo-google
description: >
Google SEO APIs: Search Console (Search Analytics, URL Inspection, Sitemaps),
PageSpeed Insights v5, CrUX field data with 25-week history, Indexing API v3,
and GA4 organic traffic. Provides real Google field data for Core Web Vitals,
indexation status, search performance, and organic traffic trends. Use when
user says "search console", "GSC", "PageSpeed", "CrUX", "field data",
"indexing API", "GA4 organic", "URL inspection", or "real CWV data".
user-invocable: true
argument-hint: "[command] [url|property]"
license: MIT
metadata:
author: AgriciDaniel
version: "2.2.5"
category: seo
---
# Google SEO APIs
Direct access to Google's own SEO data. Bridges the gap between crawl-based
analysis (existing claude-seo skills) and Google's real-time field data: actual
Chrome user metrics, real indexation status, search performance, and organic traffic.
All APIs are free. Setup requires a Google Cloud project with API key and/or
service account -- run `/seo google setup` for step-by-step instructions.
## Prerequisites
Before executing any command, check credentials:
```bash
claude-seo run google_auth.py --check --json
```
Config file: `~/.config/claude-seo/google-api.json`
```json
{
"service_account_path": "/path/to/service_account.json",
"api_key": "<GOOGLE_API_KEY>",
"default_property": "sc-domain:example.com",
"ga4_property_id": "properties/123456789"
}
```
If missing, read `references/auth-setup.md` and walk the user through setup.
### Credential Tiers
| Tier | Detection | Available Commands |
|------|-----------|-------------------|
| **0** (API Key) | `api_key` present | `pagespeed`, `crux`, `crux-history`, `youtube`, `nlp` |
| **1** (OAuth/SA) | + OAuth token or service account | Tier 0 + `gsc`, `inspect`, `sitemaps`, `index` |
| **2** (Full) | + `ga4_property_id` configured | Tier 1 + `ga4`, `ga4-pages` |
| **3** (Ads) | + `ads_developer_token` + `ads_customer_id` | Tier 2 + `keywords`, `volume` |
Always communicate the detected tier before running commands.
## Quick Reference
| Command | What it does | Tier |
|---------|-------------|------|
| `/seo google setup` | Check/configure API credentials | -- |
| `/seo google pagespeed <url>` | PSI Lighthouse + CrUX field data | 0 |
| `/seo google crux <url>` | CrUX field data only (p75 metrics) | 0 |
| `/seo google crux-history <url>` | 25-week CWV trend analysis | 0 |
| `/seo google gsc <property>` | Search Console: clicks, impressions, CTR, position | 1 |
| `/seo google inspect <url>` | URL Inspection: index status, canonical, crawl info | 1 |
| `/seo google inspect-batch <file>` | Batch URL Inspection from file | 1 |
| `/seo google sitemaps <property>` | GSC sitemap status | 1 |
| `/seo google index <url>` | Submit URL to Indexing API | 1 |
| `/seo google index-batch <file>` | Batch submit up to 200 URLs | 1 |
| `/seo google ga4 [property-id]` | GA4 organic traffic report | 2 |
| `/seo google ga4-pages [property-id]` | Top organic landing pages | 2 |
| `/seo google youtube <query>` | YouTube video search (views, likes, duration) | 0 |
| `/seo google youtube-video <id>` | YouTube video details + top comments | 0 |
| `/seo google nlp <url-or-text>` | NLP entity extraction + sentiment + classification | 0 |
| `/seo google entities <url-or-text>` | Entity analysis only (for E-E-A-T) | 0 |
| `/seo google keywords <seed>` | Keyword ideas from Google Ads Keyword Planner | 3 |
| `/seo google volume <keywords>` | Search volume lookup from Keyword Planner | 3 |
| `/seo google entity <query>` | Knowledge Graph entity check | 0 |
| `/seo google safety <url>` | Web Risk URL safety check | 0 |
| `/seo google quotas` | Show rate limits for all APIs | -- |
---
## PageSpeed + CrUX
### `/seo google pagespeed <url>`
Combined Lighthouse lab data + CrUX field data.
**Script:** `claude-seo run pagespeed_check.py <url> --json`
**Reference:** `references/pagespeed-crux-api.md`
**Default:** Both mobile + desktop strategies, all Lighthouse categories.
Output merges lab scores (point-in-time Lighthouse) with field data (28-day
Chrome user metrics). CrUX tries URL-level first, falls back to origin-level.
### `/seo google crux <url>`
CrUX field data only (no Lighthouse run). Faster.
**Script:** `claude-seo run pagespeed_check.py <url> --crux-only --json`
### `/seo google crux-history <url>`
25-week CrUX History trends. Shows whether CWV metrics are improving, stable, or degrading.
**Script:** `claude-seo run crux_history.py <url> --json`
**Reference:** `references/pagespeed-crux-api.md`
Output includes per-metric trend direction, percentage change, and weekly p75 values.
---
## Search Console
### `/seo google gsc <property>`
Search Analytics: clicks, impressions, CTR, position for last 28 days.
**Script:** `claude-seo run gsc_query.py --property <property> --json`
**Reference:** `references/search-console-api.md`
**Default:** 28 days, dimensions=query,page, type=web, limit=1000.
Includes quick-win detection: queries at position 4-10 with high impressions.
The `totals` block comes from a separate dimensionless aggregate query because
query-level rows can omit anonymized low-volume traffic. Treat totals as
site-wide only when `totals_complete` is true. `--limit` caps total returned
dimension rows, not the size of every pagination request.
> **AI surfaces in GSC (2026):**
> - **Generative AI performance report** (launched 2026-06-03), a dedicated view of **AI Overviews + AI Mode** visibility. **Impressions only** (no clicks/CTR/position/query); dimensions Pages/Countries/Devices/Dates (Pacific Time); 1,000-row limit; newest data preliminary; a separate Discover gen-AI report also exists. Rolling out to a subset of properties.
> - **AI Mode already rolls into standard Performance totals** (Web search type), clicks (external-link clicks in AI Mode) and impressions are counted in the normal report, so you **cannot** cleanly split "classic" vs "AI" traffic from totals. Use the Generative AI report for impressions-only AI visibility.
> - **Data-reliability caveat:** a GSC logging error made **impressions, CTR, and average position unreliable from 2025-05-13 to 2026-04-27** (clicks unaffected; fixed forward-only, **no backfill**). Treat impression/CTR/position trends spanning that window with caution; expect an apparent impressions drop after the fix.
> **Platform properties (2026):** Search Console can expose verified TikTok,
> Instagram, X, and YouTube accounts as individual properties. Verify each
> account separately, unless it was already added through a claimed Search
> profile. Use these properties for Google Search performance only, not as a
> substitute for the platform's own analytics. Source:
> developers.google.com/search/docs/monitor-debug/analyze-social-video-content
### `/seo google inspect <url>`
URL Inspection: real indexation status from Google.
**Script:** `claude-seo run gsc_inspect.py <url> --json`
Returns: verdict (PASS/FAIL), coverage state, robots.txt status, indexing state,
page fetch state, canonical selection, mobile usability, rich results.
### `/seo google inspect-batch <file>`
Batch inspection from a file (one URL per line). Rate limited to 2,000/day per site.
**Script:** `claude-seo run gsc_inspect.py --batch <file> --json`
### `/seo google sitemaps <property>`
List submitted sitemaps with status, errors, warnings. Sitemap contents report
submitted counts only; URL Inspection API is the indexation truth for whether
specific URLs are indexed.
**Script:** `claude-seo run gsc_query.py sitemaps --property <property> --json`
---
## Indexing API
### `/seo google index <url>`
Notify Google of a URL update.
**Script:** `claude-seo run indexing_notify.py <url> --json`
**Reference:** `references/indexing-api.md`
The Indexing API is officially for JobPosting and BroadcastEvent/VideoObject pages.
Always inform the user of this restriction. Daily quota: 200 publish requests.
### `/seo google index-batch <file>`
Batch submit URLs from a file. Tracks quota usage.
**Script:** `claude-seo run indexing_notify.py --batch <file> --json`
---
## GA4 Traffic
### `/seo google ga4 [property-id]`
Organic traffic report: daily sessions, users, pageviews, bounce rate, engagement.
**Script:** `claude-seo run ga4_report.py --property <id> --json`
**Reference:** `references/ga4-data-api.md`
**Default:** 28 days, filtered to Organic Search channel group.
> **GA4 "AI Assistants" channel (live ~2026-05-13):** GA4 added a native *AI Assistants* Default Channel Group. Sessions referred by a recognized AI assistant get `medium=ai-assistant`. Google's recognized sources are **ChatGPT, Gemini, Claude, Deepseek, Copilot, Grok** and the channel **excludes** Google AI Overviews / AI Mode. **Verify Perplexity separately if needed**; unsupported sources may stay in Referral, and most AI sessions arrive referrer-less and fall into **Direct**, so this channel undercounts AI traffic. Forward-only, no backfill.
### `/seo google ga4-pages [property-id]`
Top organic landing pages ranked by sessions.
**Script:** `claude-seo run ga4_report.py --property <id> --report top-pages --json`
---
## YouTube (Video SEO)
Some third-party studies report a 0.737 correlation between YouTube mentions and AI visibility. Treat it as a methodology-dependent signal. Free, API key only.
### `/seo google youtube <query>`
Search YouTube for videos. Returns title, channel, views, likes, duration.
**Script:** `claude-seo run youtube_search.py search "<query>" --json`
**Reference:** `references/youtube-api.md`
**Quota:** 100 units per search (10,000 units/day free).
### `/seo google youtube-video <video_id>`
Detailed video info + tags + top 10 comments.
**Script:** `claude-seo run youtube_search.py video <video_id> --json`
**Quota:** 2 units (video details + comments).
---
## NLP Content Analysis
Google NLP entity/sentiment output for internal content-quality checks. Do not treat it as Google E-E-A-T scoring.
### `/seo google nlp <url-or-text>`
Full NLP analysis: entities, sentiment, content classification.
**Script:** `claude-seo run nlp_analyze.py --url <url> --json` or `--text "..."`
**Reference:** `references/nlp-api.md`
**Free tier:** 5,000 units/month. Requires billing enabled on GCP project.
### `/seo google entities <url-or-text>`
Entity extraction only (faster, less quota).
**Script:** `claude-seo run nlp_analyze.py --url <url> --features entities --json`
---
## Keyword Research (Google Ads)
Gold-standard keyword volume data. Requires Google Ads account.
### `/seo google keywords <seed>`
Generate keyword ideas from seed terms.
**Script:** `claude-seo run keyword_planner.py ideas "<seed>" --json`
**Reference:** `references/keyword-planner-api.md`
**Requires:** Ads developer token + customer ID in config (Tier 3).
### `/seo google volume <keywords>`
Search volume for specific keywords (comma-separated).
**Script:** `claude-seo run keyword_planner.py volume "<kw1>,<kw2>" --json`
---
## Supplementary
### `/seo google entity <query>`
Knowledge Graph entity check. Verifies brand presence.
**Reference:** `references/supplementary-apis.md`
Uses Knowledge Graph Search API with API key.
### `/seo google safety <url>`
Web Risk API check for malware/social engineering flags.
**Reference:** `references/supplementary-apis.md`
### `/seo google quotas`
Display rate limits table. Read `references/rate-limits-quotas.md`.
---
## Reports
After any analysis command, offer to generate a PDF/HTML report.
### `/seo google report <type>`
Generate a professional PDF report with charts and analytics.
**Script:** `claude-seo run google_report.py --type <type> --data <json> --domain <domain> --format pdf`
| Type | Input | Output |
|------|-------|--------|
| `cwv-audit` | PSI + CrUX + CrUX History data | Core Web Vitals audit with gauges, timelines, distributions |
| `gsc-performance` | GSC query data | Search Console report with query tables, quick wins |
| `indexation` | Batch inspection data | Indexation status with coverage donut chart |
| `full` | All data combined | Comprehensive Google SEO report (all sections) |
**Workflow:**
1. Run data collection commands (pagespeed, gsc, inspect-batch, etc.)
2. Save JSON output to file: `claude-seo run pagespeed_check.py <url> --json > data.json`
3. Generate report: `claude-seo run google_report.py --type cwv-audit --data data.json --domain <domain>`
**Convention:** After completing analysis, suggest: "Generate a report? Use `/seo google report <type>`"
---
## Rate Limits
| API | Per-Minute | Per-Day | Auth |
|-----|-----------|---------|------|
| PSI v5 | 240 QPM | 25,000 QPD | API Key |
| CrUX + History | 150 QPM (shared) | Unlimited | API Key |
| GSC Search Analytics | 1,200 QPM/site | 30M QPD | Service Account |
| GSC URL Inspection | 600 QPM | 2,000 QPD/site | Service Account |
| Indexing API | 380 RPM | 200 publish/day | Service Account |
| GA4 Data API | 10 concurrent | ~25K tokens/day | Service Account |
## Cross-Skill Integration
- **seo-audit**: Spawns `seo-google` agent for live CWV + indexation data (conditional)
- **seo-technical**: Uses pagespeed_check.py for real CWV field data
- **seo-performance**: CrUX field data supplements Lighthouse lab data
- **seo-sitemap**: GSC sitemap status shows submitted counts, errors, and warnings; use URL Inspection for indexation truth
- **seo-content**: GSC query data informs keyword targeting
- **seo-geo**: Use GSC Generative AI performance reports and AI Overviews/AI Mode/Discover gen-AI include/exclude controls where available
## Output Format
- CWV metrics: traffic-light rating (Good / Needs Improvement / Poor)
- Performance reports: tables with sortable columns
- Always include data freshness note
- Save reports as `GOOGLE-API-REPORT-{domain}.md`
- Markdown/LLM templates in `assets/templates/`: `cwv-audit-report.md`, `gsc-performance-report.md`, `indexation-status-report.md`; distinct from `google_report.py`'s PDF pipeline
## Technical Notes
- INP replaced FID on March 12, 2024. Never reference FID.
- CLS values from CrUX are string-encoded (e.g., "0.05"). Scripts handle parsing.
- CrUX 404 = insufficient traffic, not an auth error.
- Search Analytics data has 2-3 day lag.
- `round_trip_time` replaced `effectiveConnectionType` in CrUX (Feb 2025).
- Custom Search JSON API is closed to new customers (2025).
## Error Handling
| Scenario | Action |
|----------|--------|
| No credentials configured | Run `/seo google setup`. List Tier 0 commands that work with just an API key. |
| Service account lacks GSC access | Report error. Instruct: add `client_email` to GSC > Settings > Users > Add. |
| CrUX data unavailable (404) | Report insufficient Chrome traffic. Suggest PSI lab data as fallback. |
| GA4 property not found | Report error. Show how to find property ID in GA4 Admin > Property Details. |
| Indexing API quota exceeded | Report 200/day limit. Suggest prioritizing most important URLs. |
| Rate limit (429) | Wait and retry with exponential backoff. Report which API hit the limit. |