Saya ingin Claude Code bisa membaca data Google Search Console saya dan memberi tahu apa yang sebaiknya saya tulis berikutnya, tanpa saya harus export CSV. Search Console API bisa melakukan itu, tapi setup-nya melewati tiga tempat: Google Cloud untuk credential, Search Console untuk permission, dan Claude Code skill yang benar-benar memanggil API-nya.
Ini jalur lengkapnya, dari project Google Cloud kosong sampai skill yang dijalankan Claude sendiri. Setup yang sama dipakai oleh skill content-scout di repo blog ini.
Jawaban singkat
- Buat project Google Cloud dan enable Google Search Console API.
- Buat service account dan download JSON key-nya.
- Di Search Console, tambahkan email service account sebagai user Restricted di property kamu.
- Tulis script kecil yang autentikasi pakai key tersebut dan memanggil
searchAnalytics/query. - Taruh script itu di sebelah
SKILL.mddi.claude/skills/<name>/supaya Claude Code tahu kapan dan bagaimana menjalankannya.
Tidak perlu OAuth consent screen dan tidak perlu login lewat browser. Service account hanyalah user lain di property Search Console kamu.
Kenapa service account, bukan OAuth
Search Console API mendukung keduanya. OAuth berarti ada consent flow di browser dan refresh token yang terikat ke akun Google kamu. Itu oke untuk web app, tapi canggung untuk script yang dijalankan Claude di terminal, atau di cloud session yang tidak punya browser.
Service account adalah akun Google "robot" dengan email sendiri. Kamu beri akses read-only ke satu property, dan dia tidak bisa menyentuh apa pun lainnya yang kamu punya. Kalau key-nya bocor, hapus key-nya atau hapus user-nya di Search Console.
Langkah 1: Buat project dan enable Search Console API
Di Google Cloud console:
- Buat project baru (saya kasih nama sesuai nama situs).
- Buka APIs & Services → Library, cari Google Search Console API, lalu klik Enable.
Atau pakai gcloud:
gcloud projects create henggana-gsc
gcloud services enable searchconsole.googleapis.com --project henggana-gsc
API ini tidak butuh billing account.
Langkah 2: Buat service account dan JSON key
Di IAM & Admin → Service Accounts, buat service account. Lewati langkah "grant this service account access to project". Service account ini tidak butuh role Google Cloud apa pun, karena akses yang penting diberikan di dalam Search Console.
Lalu buka service account-nya, masuk ke Keys → Add key → Create new key → JSON. Browser akan mendownload file key.
Pakai gcloud:
gcloud iam service-accounts create gsc-reader --project henggana-gsc
mkdir -p ~/.config/gsc
gcloud iam service-accounts keys create ~/.config/gsc/henggana-sa.json \
--iam-account gsc-reader@henggana-gsc.iam.gserviceaccount.com
Saya simpan key-nya di ~/.config/gsc/, di luar repo. Ini credential jangka panjang, jadi jangan sampai masuk ke git.
Copy client_email dari key tersebut. Ini dibutuhkan di langkah berikutnya:
jq -r .client_email ~/.config/gsc/henggana-sa.json
Langkah 3: Tambahkan service account ke Search Console
Di Search Console, pilih property kamu, lalu Settings → Users and permissions → Add user.
- Email:
client_emaildari key - Permission: Restricted
Restricted sudah cukup untuk membaca search analytics. Skill ini hanya membaca data, jadi tidak ada alasan memberi Full.
Catat nama property-nya dengan tepat, karena API memintanya sebagai siteUrl:
- Domain property:
sc-domain:henggana.com - URL-prefix property:
https://henggana.com/(dengan trailing slash)
Langkah 4: Cek apakah key bisa melihat property
Sebelum menulis yang lebih besar, cek dulu apakah auth jalan dan permission-nya sudah masuk. Script ini menampilkan semua property yang bisa dilihat service account:
# /// script
# dependencies = ["google-auth", "requests"]
# ///
import os
from google.auth.transport.requests import AuthorizedSession
from google.oauth2 import service_account
creds = service_account.Credentials.from_service_account_file(
os.path.expanduser("~/.config/gsc/henggana-sa.json"),
scopes=["https://www.googleapis.com/auth/webmasters.readonly"],
)
print(AuthorizedSession(creds).get("https://searchconsole.googleapis.com/webmasters/v3/sites").json())
Blok # /// script adalah inline script metadata, jadi uv meng-install dependency-nya langsung:
uv run sites.py
Kamu harusnya melihat property kamu dengan siteRestrictedUser:
{'siteEntry': [{'siteUrl': 'sc-domain:henggana.com', 'permissionLevel': 'siteRestrictedUser'}]}
Kalau hasilnya {} kosong, berarti key-nya jalan tapi service account belum jadi user di property mana pun. Kembali ke langkah 3.
Langkah 5: Tulis script untuk query search analytics
Endpoint yang berguna adalah searchAnalytics/query. Ini versi ringkas dari script di skill saya. Script ini mengambil baris query + page untuk 90 hari terakhir dan mencetaknya sebagai TSV:
# /// script
# requires-python = ">=3.10"
# dependencies = ["google-auth", "requests"]
# ///
import datetime as dt
import os
from google.auth.transport.requests import AuthorizedSession
from google.oauth2 import service_account
SITE = "sc-domain:henggana.com"
KEY = os.path.expanduser("~/.config/gsc/henggana-sa.json")
SCOPES = ["https://www.googleapis.com/auth/webmasters.readonly"]
creds = service_account.Credentials.from_service_account_file(KEY, scopes=SCOPES)
session = AuthorizedSession(creds)
end = dt.date.today() - dt.timedelta(days=3)
start = end - dt.timedelta(days=90)
rows, offset = [], 0
while True:
r = session.post(
f"https://searchconsole.googleapis.com/webmasters/v3/sites/{SITE}/searchAnalytics/query",
json={
"startDate": start.isoformat(),
"endDate": end.isoformat(),
"dimensions": ["query", "page"],
"rowLimit": 25000,
"startRow": offset,
},
)
r.raise_for_status()
batch = r.json().get("rows", [])
rows += batch
if len(batch) < 25000:
break
offset += 25000
print("query\tpage\tclicks\timpressions\tctr\tposition")
for row in sorted(rows, key=lambda r: -r["impressions"]):
q, page = row["keys"]
print(f"{q}\t{page}\t{row['clicks']}\t{row['impressions']}\t{row['ctr']:.1%}\t{row['position']:.1f}")
Beberapa detail di sana:
endadalah tiga hari lalu. Data Search Console telat beberapa hari, dan hari-hari terakhir biasanya kosong atau belum lengkap.rowLimitmaksimal 25.000 per request, jadi loop-nya pindah halaman pakaistartRow.- Scope-nya
webmasters.readonly. Bahkan kalau permission-nya Full, token ini tetap tidak bisa menulis.
Script asli saya selangkah lebih jauh dan hanya mencetak baris yang layak ditindaklanjuti: query dengan rata-rata posisi 8–20 (striking, halaman satu sudah dekat) dan query di top 8 dengan CTR di bawah 2% (low-ctr, biasanya masalah title atau description). Hasil run aslinya seperti ini:
# sc-domain:henggana.com 2026-09-07..2026-10-05, 53 queries, 19 pages
query page clicks impressions ctr position bucket
resizeobserver is not defined /id/blog/fix-jest-resize-observer-is-not-defined/ 0 15 0.0% 8.2 striking
not implemented: window's scrollto() method /en/blog/fix-jest-error-window-scrollTo/ 0 13 0.0% 5.6 low-ctr
Filter di script membuat output-nya kecil. Claude membaca lebih sedikit baris dan fokus memutuskan apa artinya.
Langkah 6: Jadikan Claude Code skill
Skill adalah folder berisi SKILL.md. Project skill ada di .claude/skills/ di dalam repo, jadi ikut ter-version bersama kode dan tersedia juga di cloud session:
.claude/skills/gsc/
SKILL.md
gsc.py
description di frontmatter adalah yang dipakai Claude untuk memutuskan kapan memuat skill, jadi isi dengan frasa yang memang akan kamu ketik. Body-nya memberi tahu Claude cara menjalankan script dan apa yang harus dilakukan dengan output-nya:
---
name: gsc
description: Use when the user asks about Search Console data, search queries, rankings, CTR, or "what should I write next" for henggana.com.
---
# Search Console
Run:
uv run -q .claude/skills/gsc/gsc.py
Key: `~/.config/gsc/henggana-sa.json`. If it's missing, say so and stop. Don't ask for the key in chat.
Output is TSV: query, page, clicks, impressions, ctr, position.
- Query ranks an existing post at position 8-20 → suggest what section or title change could push it to page one.
- Query in the top 8 with CTR under 2% → suggest a better title or meta description.
- Several queries with an intent no post covers → suggest a new post.
Traffic is small. Read the queries for intent, not volume.
Sudah, itu saja. Tanya Claude Code "apa yang ranking di halaman dua di Search Console?" dan dia akan memuat skill, menjalankan script, lalu membaca TSV-nya. Kamu juga bisa memanggilnya langsung dengan /gsc.
Tanpa instruksi itu, Claude cuma dapat tumpukan angka dan memberi ringkasan SEO yang generik. Dengan instruksi itu, dia tahu baris mana yang berarti "perbaiki post ini" dan mana yang berarti "tulis post baru".
Langkah 7: Supaya jalan di Claude Code cloud session
Cloud session tidak punya folder ~/.config/gsc/ kamu, jadi key-nya harus datang dari environment variable. Saya buat script-nya membaca GSC_KEY_JSON dulu, lalu fallback ke file:
import base64
import json
if raw := os.environ.get("GSC_KEY_JSON", "").strip():
info = json.loads(raw if raw.startswith("{") else base64.b64decode(raw))
creds = service_account.Credentials.from_service_account_info(info, scopes=SCOPES)
else:
creds = service_account.Credentials.from_service_account_file(KEY, scopes=SCOPES)
Bagian base64 itu muncul dari kegagalan nyata. Awalnya saya paste raw JSON key ke field environment variable di cloud environment, dan json.loads error. Key-nya multi-line dan private_key-nya penuh escape \n, yang tidak selamat ketika di-paste ke field env var satu baris.
Base64 mengubah seluruh key jadi satu baris yang aman:
# macOS
base64 -i ~/.config/gsc/henggana-sa.json | pbcopy
# Linux (-w0 disables line wrapping)
base64 -w0 ~/.config/gsc/henggana-sa.json
Paste hasilnya sebagai GSC_KEY_JSON di settings cloud environment.
Cloud environment juga butuh akses network ke dua host Google. Satu untuk membuat access token, satu lagi API-nya:
oauth2.googleapis.com
searchconsole.googleapis.com
Tanpa keduanya, script gagal di request token, sebelum sempat sampai ke Search Console.
Kesalahan umum
Format siteUrl salah
Domain property ditulis sc-domain:example.com. URL-prefix property ditulis https://example.com/. Di Search Console keduanya property yang berbeda, walaupun situsnya sama. Pakai siteUrl persis seperti yang dikembalikan call sites di langkah 4.
Memberi role IAM Google Cloud, bukan akses Search Console
Memberi service account role Owner atau Viewer di project Google Cloud tidak berpengaruh apa-apa ke Search Console. Satu-satunya permission yang dihitung adalah user yang kamu tambahkan di Users and permissions Search Console.
Lupa enable API
Service account dan key tetap bisa dibuat tanpa API di-enable, tapi setiap call akan gagal sampai Google Search Console API di-enable di project pemilik service account.
Commit key ke repo
Simpan JSON key di luar repo, atau minimal masukkan ke gitignore. Kalau sempat masuk ke git, hapus key itu di Google Cloud console dan buat yang baru.
Referensi
- Search Console API: authorizing requests
- Search Analytics: query
- Managing owners, users, and permissions in Search Console
- Claude Code skills
Kesimpulan
Supaya Claude Code skill bisa memakai Google Search Console API: enable API-nya di project Google Cloud, buat service account dengan JSON key, tambahkan email-nya ke property sebagai user Restricted, lalu bungkus script searchAnalytics/query kecil dengan SKILL.md yang menjelaskan ke Claude arti setiap baris. Untuk cloud session, kirim key sebagai env var base64 dan izinkan oauth2.googleapis.com serta searchconsole.googleapis.com.