Reading Search Console on a schedule is better than opening it only when something feels wrong. Traffic dipped, a page vanished, someone asked. You set a date range, squint at the chart, download a zip, and try to reconstruct what changed.
The setup takes fifteen minutes, and it gives you the most useful thing the export leaves out.
The question the export cannot answer
Open a Performance export and you get one file of your top queries and another of your top pages. There is no file with both.
So this has no answer in the zip: for a query I care about, which of my pages is Google actually showing?
You need that the moment two of your own posts cover overlapping ground and neither ranks well. The export shows the problem and not where it comes from. The workaround people use is to filter the Performance view to one query in the interface and read which pages appear, one query at a time. Fine for three queries. Useless for eight hundred.
The API returns page and query in the same row. Among the rows Google returns, each page and query combination becomes a lookup instead of a manual filter.
It also gives you more rows. The interface export stops at 1,000 representative rows, while an API request can return up to 25,000. And once one date range is a request, the previous range is just a second request.
What that caught on my site
I had rewritten a post's title and meta description as a deliberate test, with a checkpoint four weeks out.
Paired data says 84% of the post's disclosed impressions are two variants of one string: somebody pasting a certification quiz question into Google. The keyword I was actually targeting has zero.
The test would have come back looking like a failed rewrite. It is not a failed rewrite. It has not started, because you cannot move a keyword the page does not rank for yet. If I had only read the page's click count, I would have recorded the wrong conclusion and trusted it for months.
Setup
Fifteen minutes, all in the browser except the last step. You need a Google account with access to the property.
1. Pick or create a Cloud project
Go to console.cloud.google.com and use the project picker in the top bar. Any existing project works, so reuse one if you have it.
2. Enable the Search Console API
APIs & Services, then Library. Search for Google Search Console API, open it, click Enable.
Do not skip this because it sounds like a formality. If the API is off, your first request returns a 403, and a 403 reads as a permissions problem, so you will go back and check the user you added rather than the switch you never flipped. Enable it now and give it a minute to propagate.
3. Create a service account
APIs & Services, then Credentials, then Create credentials, then Service account. Give it a name.
Google will offer to grant it project roles. Skip that (it is optional, and the access that matters is granted in step 6). Click Done.
4. Create a JSON key
Click the service account, open the Keys tab, then Add key, Create new key, JSON.
It downloads immediately and it is the only copy. Google will not show it again.
5. Copy the service account address
Same page, looks like something@your-project.iam.gserviceaccount.com. It behaves like an email address even though nobody reads mail there.
6. Add it to your property
Over to search.google.com/search-console. Pick the property, then Settings, Users and permissions, Add user.
Paste the address and give it Restricted access. searchAnalytics.query needs read permission, and Restricted is enough for the Performance data this fetcher reads. This is the step that grants access to your data. The Cloud side only created an identity.
7. Store the key out of the way
mkdir -p ~/.config/gsc && chmod 700 ~/.config/gsc
mv ~/Downloads/your-key.json ~/.config/gsc/example-com.json
chmod 600 ~/.config/gsc/example-com.jsonThe fetcher reads this working credential each time it asks Google for your data. Moving it here gives the API route a predictable, protected location. The manual export route does not use a key.
The commands do four things:
mkdir -p ~/.config/gsccreates a configuration folder in your home directory.chmod 700 ~/.config/gscprevents other normal local users from opening that folder.mvmoves the downloaded key into that folder and renames it for the Search Console property.chmod 600prevents other normal local users from reading or changing the key.
The filename comes from the property name. For sc-domain:example.com, use example-com.json. When you give search-visibility that property name, its fetcher looks for this filename automatically. It reads the key locally to sign Google's request. You do not upload the file to Claude or paste its contents into a prompt.
Keep the file outside any web root and out of a project directory that gets deployed. One wrong rule and the key becomes downloadable. If these commands are unfamiliar, or you are using Windows, use the AI helper below. Give it your operating system and current setup, but never the key itself.
Asking for page and query together
The request is a POST to:
https://www.googleapis.com/webmasters/v3/sites/{property}/searchAnalytics/queryThe property goes in the path, URL-encoded, exactly as Search Console names it. A domain property is sc-domain:example.com, which encodes to sc-domain%3Aexample.com.
The body:
{
"startDate": "2026-06-01",
"endDate": "2026-08-29",
"dimensions": ["page", "query"],
"rowLimit": 25000,
"type": "web"
}dimensions is the whole trick. Ask for ["query"] and you get the table you already had. Ask for ["page", "query"] and you get the pairing no export contains. rowLimit maxes at 25,000 and startRow handles pagination.
Pagination still does not mean complete data. Google says the API is limited internally and returns top rows rather than guaranteeing every row. It gives you a larger and more useful sample than the interface export, not a perfect copy of every search.
Or skip the plumbing
I built this for my own site and packaged it, so you do not have to write the fetcher, the pagination, or the analysis.
/plugin marketplace add imisic/claude-marketplace
/plugin install search-visibility@imisicThen /search-visibility:a-seo-gsc. Claude Code namespaces skills installed through a plugin, so the plugin name stays in the command. Give it your property name and it pulls the data, fetches a matching earlier window for comparison, and hands back a ranked action plan instead of a wall of rows. It finds pages earning impressions but no clicks, and queries sitting just off page one where a title rewrite beats a new post. It also finds where your own pages compete with each other, which is the one the pairing makes possible.
The bundled API fetcher needs Python 3 and openssl on macOS, Linux, or WSL. On native Windows, the skill offers WSL or the manual-export route before you create a key.
You do not need the API to use it. It opens by asking which route you are on. Say you would rather download the reports and it tells you exactly which three to export and reads those instead, because the fetcher writes Google's own export shapes and the parser cannot tell the two apart. The only thing the download route gives up is the page and query pairing, so that one section is skipped, not reported as clean. Every other part of the analysis runs identically.
The bundled parser does the arithmetic before Claude reasons over the findings, so the numbers are computed rather than guessed from a spreadsheet. The fetcher saves the raw exports locally and connects only to Google's OAuth and Search Console endpoints. Claude receives the parser's findings as part of the session, under the data controls of your Claude Code plan. The plugin adds no telemetry.
It is free, MIT, and the code is on GitHub. The longer write-up is on the Toolshed.
Read this before trusting any query number
I checked the fetch against figures I already knew. The gap changed how I read every query number, and it applies to both routes.
Google omits rare queries to protect privacy (reasonably enough). I compared one 28-day window on my own site two ways, page rows against query rows, both describing the same traffic.
The query rows saw 16% of the impressions and 8% of the clicks.
Everything I had concluded from a query table was a statement about a small disclosed minority, and I had been reading it as a statement about the site. Query data is still useful. It just needs that coverage ratio stated next to any finding, and you never subtract query clicks from page clicks and give the remainder a name.
I would expect the gap to narrow on a busier site, but I would check rather than assume. Once both tables arrive, it is one subtraction.
What the API will not give you
The Search Console API has no endpoint for the coverage report. Indexed versus not indexed, the reason breakdown, the list of URLs sitting in "Crawled, currently not indexed", none of it. Still a manual export, and skipping it costs you the entire indexing half of any analysis.
The URL Inspection API looks like a substitute and is not. It reports on URLs you hand it, one at a time, so it can confirm pages you already know about and can never show you a page Google indexed that you never submitted. It cannot see index bloat, and index bloat is half of what the coverage report is for.
There is no API for the links report either.
So this replaces one of three exports, and it is the one you were doing most often.
Worth it or not
If you check Search Console a few times a year and mostly want to know whether traffic is up, the export is fine and this is not worth your afternoon.
If you make decisions from it, run title tests, or have more than a few pages covering related ground, the pairing answers a question you have probably been guessing at. Fifteen minutes of clicking, then it runs on a schedule and you stop only looking when something has already gone wrong.
The reverse problem, getting data out of a system with no API at all, is a different kind of work. I wrote about capturing email and calendar without a mailbox API when that was the constraint. Worth knowing which situation you are in before you start building.
Here the API existed the whole time. I just kept clicking Export.
Sources
- Search Analytics: query, Google for Developers. Defines the endpoint, dimensions, 25,000-row request limit, pagination, and the top-row limitation.
- Export data directly from a Search Console report, Google Search Console Help. Documents the 1,000-row export limit and representative sampling.
- Prerequisites, Google for Developers. Confirms that
searchAnalytics.queryrequires read permission on the property. - API Reference, Google for Developers. Lists the Search Console API services and confirms there are no coverage or links-report endpoints.
- Discover and install prebuilt plugins through marketplaces, Claude Code Docs. Documents marketplace installation and namespaced plugin skills.
Get Personalized Help
Copy this prompt to ChatGPT, Claude, or your favorite AI assistant. Fill in your details and get guidance tailored to your specific situation.
I'm setting up the Search Console API using https://ivanmisic.net/blog/ai-tools/search-console-api-page-query-pairs. My setup: - Search Console property exactly as shown: [sc-domain:example.com OR https://www.example.com/] - Current point: [STARTING / API ENABLED / SERVICE ACCOUNT CREATED / JSON KEY DOWNLOADED / KEY STORED / STUCK ON AN ERROR] - How I want to use the data: [SEARCH-VISIBILITY PLUGIN / MY OWN SCRIPT] - Computer and terminal: [MACOS WITH ZSH / WINDOWS WITH POWERSHELL / LINUX WITH BASH] - Error message, if any: [PASTE THE ERROR WITHOUT CREDENTIALS, OR NONE] Give me only the next steps from my current point. Use the article's restricted-access setup and explain each command before asking me to run it. Explain where the JSON key should go, why that location is the working credential rather than a backup, and how my selected tool will find it. If I selected the search-visibility plugin, check that its bundled fetcher can run before leading me through key setup. It needs Python 3 and `openssl` on macOS, Linux, or WSL. On native Windows, offer WSL or the manual-export route first. If I selected my own script, separate Google's setup from that script's credential location and requirements. Never ask me to paste the JSON key, private key, access token, or other credential. Ask for a missing non-secret detail instead of assuming it. If a Google screen, label, or permission may have changed, check current official Google documentation and state what you verified. End with one safe verification step and explain what success looks like. Preserve the article's limits: the API does not guarantee every row, and Coverage and Links still need manual exports.