The Google Search Console API: Six Endpoints, One That Matters, and the Auth Step Everyone Misses
The Search Console API in full: OAuth versus service accounts, the searchAnalytics query that does the real work, the 25,000-row cap, and why there is no indexing endpoint.
The Search Console API is small: six endpoints, and one of them is the reason anyone uses it. It serves the same 16 months as the interface, with the same anonymised queries missing, at 25 times the rows. What to know before you start:
- The endpoint that matters: searchAnalytics.query. Clicks, impressions, CTR and position by query, page, country, device, date or search appearance, up to 25,000 rows per request and paged with startRow.
- The auth step people miss: A service account is not enough on its own. You must also add its email address as a user on the property inside Search Console, manually, once per property.
- There is no indexing endpoint: The Indexing API only accepts JobPosting and BroadcastEvent pages. For index status on a normal URL you want urlInspection.index.inspect, at 2,000 calls per day per property.
- Two flags change your numbers: dataState decides whether fresh incomplete days are included, and aggregationType decides whether totals are counted per property or per page. Both explain most API-versus-UI mismatches.
- It is not a warehouse: The API reads the same rolling 16 months and backfills nothing. If you want history, the BigQuery bulk export is the answer and the API is the wrong tool.
Start with sites.list. It is one call, it proves your credentials work, and it prints the exact siteUrl strings every other endpoint demands.
CrawlRaven reads this API on your behalf, so treat the last section as interested. The rest is the documentation I wanted when I first wired it up, including the auth failure that costs everyone an afternoon. Try CrawlRaven free: 1 site, no credit card →
The Search Console API has a reputation for being fiddly, and it is not. It has six endpoints, one of which does the real work, and a single authentication step that fails silently enough to cost people a whole afternoon.
This covers the surface, the auth, the first two calls, and the two flags that explain almost every "my numbers do not match the interface" question.
What the API is, and what it is not
It is the same Search Console data, read programmatically. That sentence sets both the appeal and the ceiling.
- Same window. A rolling 16 months, with the oldest day dropping off every morning. The API cannot reach further back than the interface can.
- Same privacy line. Anonymised queries have no row over the API either. More rows of the visible long tail is not the same as more queries.
- Twenty-five times the rows. 25,000 per request against the interface's 1,000, paged. This is the actual reason to use it.
- Schedulable. The interface needs a human. A cron job does not, which is what turns a report into a pipeline.
What it is not is a warehouse. It reads; it never stores. Any history beyond the window is something you built.
Every endpoint, in one table
The whole API fits on one screen, which is unusual for a Google product and makes it much less intimidating than the documentation implies.
Every Search Console API endpoint
| Endpoint | Verb | What it returns | What to know |
|---|---|---|---|
| searchAnalytics.query | POST | Clicks, impressions, CTR and position by query, page, country, device, date or search appearance | The reason to use the API at all. Up to 25,000 rows per request |
| sites.list | GET | Every property this credential can read, with its permission level | Your first call. Confirms auth works and prints exact siteUrl strings |
| sites.get / add / delete | GET / PUT / DELETE | Read, create or remove a property | Adding a property does not verify it. Verification is still manual |
| sitemaps.list / get | GET | Submitted sitemaps with their status, warnings, errors and URL counts | The discovered-versus-submitted gap, without opening the interface |
| sitemaps.submit / delete | PUT / DELETE | Submit or remove a sitemap URL | Worth wiring into a deploy. Resubmitting is cheap and idempotent |
| urlInspection.index.inspect | POST | Index status, canonical, crawl date and rich-result verdicts for one URL | Separate host and quota: 2,000 per day and 600 per minute, per property |
Note the split hosts. Search analytics, sites and sitemaps live under the Webmasters v3 base; URL inspection is on its own host with its own quota, and is the newest part of the surface.
Authentication: the step that trips people up
Everything is OAuth 2.0. What differs is who consents, and that choice determines how your integration dies later.
Three ways to authenticate
Two scopes exist, and you should ask for the smaller one:
https://www.googleapis.com/auth/webmastersgrants read and write. You need it to submit sitemaps or manage properties.https://www.googleapis.com/auth/webmasters.readonlygrants read only, and is what a reporting integration should request. People read consent screens.
The service account gotcha
This is the afternoon. Creating a service account in Google Cloud and enabling the API grants it exactly nothing in Search Console, and the failure looks like a permissions bug rather than a missing step.
- Create the service account in your Google Cloud project.
- Copy its email address, which ends in
.iam.gserviceaccount.com. - Open Search Console, pick the property, then Settings → Users and permissions.
- Add that email as a user, exactly as you would add a colleague. Full for write access, restricted for read.
- Repeat for every property. There is no bulk step.
Until step four, every call returns a 403 that reads like an auth problem, because technically it is one: the credential is valid and has no access to anything. Who can perform step four is covered in users and permissions, and the answer is owners only.
The quickstart: your first two calls
Make sites.list your first request every time. It is cheap, it proves the credential works, and it prints the exact siteUrl strings every other endpoint insists on.
GET https://www.googleapis.com/webmasters/v3/sitesThe response names your properties in the exact string format every other endpoint demands. Two shapes:
sc-domain:example.comfor a domain property.https://example.com/for a URL-prefix property, trailing slash included.
Copy them verbatim and URL-encode them wherever they sit in a path. Then the call you came for:
POST https://www.googleapis.com/webmasters/v3/sites/sc-domain%3Aexample.com/searchAnalytics/query
{
"startDate": "2026-08-01",
"endDate": "2026-08-28",
"dimensions": ["query", "page"],
"rowLimit": 25000,
"startRow": 0,
"dataState": "final"
}That is the whole quickstart. Everything else is choosing dimensions, filtering, and paging.
searchAnalytics.query in detail
One endpoint, and most of the API's value. The body is small and each field changes the shape of what comes back.
- dimensions: any of
query,page,country,device,date,searchAppearance. Combine them and each row is a unique combination, which multiplies row counts fast. - type: which surface you are asking about. Web, image, video, news and discover are separate datasets, not filters on one.
- dimensionFilterGroups: the API equivalent of interface filters, including regex. The free GSC regex generator writes patterns that work in both.
- rowLimit and startRow: paging. Request 25,000, then increment
startRowby 25,000 until a response returns fewer rows than you asked for.
There is no total-count field, so the short response is the end-of-results signal. A loop that waits for an empty page instead will make one wasted request every time, which is harmless but tells you the author never read the response shape.
Why your API numbers do not match the interface
This is the most common support question about the API and it is almost never a bug. Two flags explain nearly all of it.
- dataState. It defaults to
final, which excludes the most recent days while they are still settling. Set it toallto include fresh, incomplete data, and expect those last days to keep moving. - aggregationType. Whether totals are counted by property or by page. Adding the
pagedimension changes the aggregation, and therefore changes the totals, which surprises people who expected filtering to be lossless. - Anonymised queries. The moment you add the
querydimension, hidden queries drop out. Your query rows will always sum to less than your unfiltered totals.
Rule of thumb: compare like with like. Rows to rows, totals to totals, one dataState throughout. A mismatch between an API pull and a screenshot usually means the two used different settings, not that one of them is wrong. The same discipline applies when you cross into GA4, where the gap is structural rather than configurable.
Index status, and the Indexing API misconception
People come looking for an endpoint that gets pages indexed. It exists, it is a different product, and it almost certainly does not apply to you.
- The Indexing API is narrow. Google accepts it only for pages carrying JobPosting or BroadcastEvent structured data. Calling it for ordinary content does nothing, whatever a plugin claims.
- URL Inspection is what you want.
urlInspection.index.inspectreturns Google's index verdict for a URL, the canonical it chose, the last crawl date and rich-result verdicts. - Its quota is real. 2,000 calls per day and 600 per minute, per property. That is generous for spot checks and small for a nightly sweep of a large site.
For bulk indexability across a site, inspection is the wrong shape entirely: you want a crawl, and the free page indexability checker covers the common case. What the statuses mean once you have them is in the page indexing report guide.
Sitemaps: the endpoint worth wiring into a deploy
The sitemaps endpoints get overlooked because submitting one by hand takes ten seconds. Doing it automatically is still worth the ten minutes.
- sitemaps.submit takes the property and the full sitemap URL. It is idempotent, so resubmitting on every deploy is safe and cheap.
- sitemaps.list returns status, warnings, errors and counts for everything submitted. The submitted-versus-discovered gap is a finding on its own.
- Alert on the gap, not the submission. A sitemap that submits cleanly and discovers a third of its URLs is the failure worth catching in CI.
Validate the file before you submit it rather than after Google complains: the free sitemap validator checks it against the protocol, and the sitemap checker live-checks the URLs inside it.
Limits, quotas and what the API will not give you
The honest list, so you find out here rather than three days into a build.
- No link data. The Links report has no endpoint. If you want the links report programmatically, you cannot have it.
- No Core Web Vitals. Not exposed. The CrUX API is a separate product with a different dataset.
- No manual actions or security issues. Both are interface and email only, which matters if you hoped to monitor a client roster programmatically.
- No enhancement reports. Structured-data validity is not available through this API.
- Quota you will not hit normally. Per-project and per-user limits are generous for reporting and easy to exhaust with a retry loop that has no backoff. Handle 429 properly from the start.
The gap between "I pulled search analytics" and "I have a pipeline" is much wider than it looks on day one. The first script takes an afternoon. Paging, retries, token refresh, schema drift, a property someone renamed, and the daily job that fails silently for three weeks are the actual project.
Build it when the pull is genuinely specific to how your team works. Do not build it to avoid paying for a tool, because the maintenance is the price and it arrives monthly rather than once.
API, interface or bulk export
Three ways to read the same data, and the choice is about depth against setup rather than about quality.
Interface, API and bulk export
| Interface | API | BigQuery export | |
|---|---|---|---|
| Rows per pull | 1,000 | 25,000, paged | All of them |
| History | 16 months | 16 months | From setup day, forever |
| Anonymised queries | Hidden | Hidden | One aggregate row per day |
| Setup cost | None | An afternoon | A Cloud project |
| Running cost | Free | Free | Google Cloud billing |
| Needs code | No | Yes | SQL |
Most teams that get this right run two of the three: the BigQuery bulk export as the archive, because it is the only route that beats the 16-month window, and the API for the specific questions they ask weekly. The interface stays for the reports the API does not expose at all.
If the goal is a language model reading your Search Console data rather than a dashboard, the API is one layer below where you want to be: the Search Console MCP routes cover what sits on top of it.
Key Takeaways
- →Six endpoints, one that matters: searchAnalytics.query does the work. sites, sitemaps and urlInspection round it out, and the whole surface fits on one screen.
- →Start with sites.list: One call that proves your credentials and prints the exact siteUrl strings, including the sc-domain: prefix, that every other endpoint demands.
- →The service account needs a second step: Creating it in Google Cloud grants nothing. Add its email as a user on the property in Search Console, manually, once per property, or every call returns 403.
- →25,000 rows, paged with startRow: Against 1,000 in the interface. There is no total count, so a response with fewer rows than you requested is your end-of-results signal.
- →Two flags explain the mismatches: dataState decides whether fresh incomplete days are in. aggregationType decides property versus page counting. Check both before reporting a bug.
- →There is no indexing endpoint for normal pages: The Indexing API takes JobPosting and BroadcastEvent only. For index status use urlInspection.index.inspect, capped at 2,000 per day per property.
- →It reads, it never stores: Same rolling 16 months, same anonymised queries withheld, no backfill. For history you want the BigQuery bulk export, and it starts from the day you switch it on.
Primary sources
Frequently asked questions
What is the Google Search Console API?
It is the programmatic interface to your Search Console property. Six endpoints cover search analytics, property listing and management, sitemap submission and status, and URL inspection. It serves the same data as the interface, from the same rolling 16-month window, with the same anonymised queries withheld, but returns far more rows per request and can be scheduled.
How do I authenticate with the Search Console API?
OAuth 2.0, in one of two shapes. User consent, where a person signs in and you store a refresh token, suits scripts and products. A service account suits unattended jobs. Either way you create a Google Cloud project, enable the Search Console API, and request the scope https://www.googleapis.com/auth/webmasters, or the readonly variant if you never write.
Why does my service account get a 403 from the Search Console API?
Almost always because you skipped the second step. Creating a service account in Google Cloud grants it nothing in Search Console. You must open the property, go to Settings then Users and permissions, and add the service account's email address as a user, exactly as you would add a colleague. This is manual and needed once per property.
What is the row limit on the Search Console API?
25,000 rows per request for searchAnalytics.query, against 1,000 in the interface export. Page through larger result sets with startRow, incrementing by your rowLimit until a response comes back with fewer rows than you asked for. That is the signal you have reached the end; there is no total count to check against.
Is there a Google Search Console API for indexing pages?
Not for general web pages. Google's Indexing API only accepts pages with JobPosting or BroadcastEvent structured data, and using it for ordinary content does nothing. To read whether a URL is indexed, use urlInspection.index.inspect, which returns Google's index verdict, the canonical it chose and the last crawl date, at 2,000 calls per day per property.
Why do my API numbers differ from the Search Console interface?
Usually one of two flags. dataState defaults to final, which excludes the most recent incomplete days that the interface may show; setting it to all includes them. And aggregationType changes whether totals are counted by property or by page, which shifts every figure once the page dimension is involved. Check both before assuming a bug.
Does the Search Console API show anonymised queries?
No. The API applies the same privacy line as the interface: queries typed by very few people have no row over the API either, and query filters exclude them the same way. You get more rows of the visible long tail, not access to the hidden part. The BigQuery bulk export is the only place their aggregate size appears.
Can the Search Console API give me more than 16 months of data?
No. The API reads the same rolling 16-month window as the interface and backfills nothing, so it cannot recover history you did not capture. If you want a longer record, you either pull on a schedule and store the results yourself from today onward, or set up the BigQuery bulk export, which has the same start-from-today constraint.
What scopes does the Search Console API need?
Two exist. https://www.googleapis.com/auth/webmasters grants read and write, which you need for submitting sitemaps or managing properties. https://www.googleapis.com/auth/webmasters.readonly grants read only and is what almost every reporting integration should request. Ask for the narrower one unless you genuinely write, because users notice what a consent screen asks for.
Is the Search Console API free?
Yes. There is no charge for calls and no paid tier, in keeping with the rest of Search Console. What you get instead is quota: per-project and per-user limits that normal reporting will not reach, and that a retry loop without backoff will reach quickly. The URL Inspection API has its own published caps of 2,000 per day and 600 per minute per property.
Can I submit a sitemap through the API?
Yes, with sitemaps.submit, which takes the property and the full sitemap URL and needs the read-write scope. It is idempotent, so resubmitting an existing sitemap is harmless, which makes it a sensible last step in a deploy pipeline. sitemaps.list returns the status, warnings, errors and URL counts for everything already submitted.
Should I use the API or the BigQuery bulk export?
The API when you want specific slices on demand and can live inside 16 months. The bulk export when you want everything, including the long tail the API truncates and an aggregate row for anonymised queries, and when you are building history. Many teams run both: the export for the archive, the API for the queries they ask weekly.
15+ years of growing SaaS websites through SEO | Author, 200-Point Audit Checklist
Aditi has spent 15+ years helping SaaS companies scale organic traffic through technical SEO and content strategy. She is the author of the CrawlRaven 200-Point Audit checklist used by agencies and in-house teams to systematically improve search performance.