Skip to main content
Read this before trusting any figure from the gsc_* tools. Google Search Console is not a neutral recorder: it censors queries, lags about 3 days, caps rows, and rate-limits properties. Every rule below exists because a confident wrong answer was produced without it. The same content is available mid-session as the resource aga://guides/gsc-data-fidelity.

Anonymization: named queries are a biased floor

Google withholds rare queries. It is common for well under half of impressions — and even fewer clicks — to be attributable to a named query. Three properties of that hidden tail matter:
  • It is not random. Rare long-tail phrasings live there — exactly where conversational AI-style queries sit — and hidden queries often convert better than named ones. The named sample is biased, not just incomplete.
  • The naming rate is non-stationary. The named share can double within months as a site grows. Distinct-query counts are NOT comparable across periods — later months look broader partly because Google names more of them.
  • Zero is the most fragile finding. NEVER claim “zero clicks” from query data. A claim of “zero clicks for an entire intent class” has collapsed the moment page-level data was checked: the clicks were real, sitting on queries Google refuses to name.
Get the denominator. Pull [page, query] and read meta.anonymization — it reports named vs anonymized impressions, clicks, and CTR. Query-only pulls never surface it; you need the page-level denominator alongside the named total. Pull it early: this block has retracted headline findings after they were already reported. Query-frame responses without a page denominator carry a top-level queryBiasNote restating this: gsc_query_analytics, gsc_export_dataset, gsc_query_export, and gsc_compare_periods all say so when their rows are query-level. Read it as a standing warning, not a per-call observation. Operating rule: use query text to learn WHAT KIND of demand exists and to find specific opportunities — never to size a segment, compare breadth across periods, or claim something earns no clicks. Size segments at page level; page data has no anonymization. Every query-derived figure is a floor. Where the instrument is biased, quote bounds (page-level ceiling vs named-query floor), not point estimates. The page-level ceiling can be several times the named-query floor — the truth is between, and the bound is the quotable result.

The ~3-day lag and incompleteDays

GSC data settles over about 3 days; the most recent days revise upward. Cut analysis at today − 3. meta.incompleteDays flags only dates that fall inside the real lag window — today−3 … today at the time of the call. Historical pulls carry no flags: a January export requested in September is complete and says so. Never trend on flagged days; never discard unflagged historical data because of a stale mental model of the flag.

Row cap, partitioning, coverage

  • gsc_export_dataset caps at 25,000 rows per property per call. Slice large accounts by month so every export lands under the cap — a single busy month on a growing site can run to tens of thousands of rows.
  • Capped pulls auto-partition (device → page-regex) and reconcile against an uncapped denominator. Gate impression-based analysis on coverageRatio of at least 0.98 and truncatedByApi: false. Click-based analysis survives capping either way — the cap discards the zero-click impression tail first.

The daily quota

Google enforces a daily per-property load quota. When it trips, EVERYTHING fails — pulls can fail repeatedly for hours, and a tiny analytics request fails identically to a huge export, because it is a property-level daily cap, not a request-size limit. It resets the next day. When the quota is exhausted the tools return GSC 429 errors with quota-specific guidance — the daily per-property cap, the next-day reset, and the budgeting rules below. Treat a 429 as “stop pulling today,” not “retry smaller.” Budget your pulls:
  • Enumerate what you need before you start, rather than iterating exploratorily against the API.
  • Prefer one wide export plus local re-slicing (gsc_query_export, or pandas over the downloaded JSON) over many narrow calls.
  • Exploit the 6-hour identical-params cache instead of re-pulling.
  • Have a labelled proxy method ready. When page×query attribution is unavailable, pages can be classified by their own target search term instead — a documented proxy, clearly labelled, replaced with real attribution once quota returns.

Export files are remote

gsc_export_dataset writes the file on the MCP server, not your machine. The returned filePath is a server path — reading it locally fails. The response also carries a pre-signed downloadUrl that expires in about an hour; curl it immediately:
Do not collect URLs and fetch hours later. Files are auto-deleted after 24 hours, and identical params within 6 hours reuse the existing file — pass forceRefresh to bypass.

Filters and metadata

  • A substring path filter catches unrelated URLs — a filter like page contains "articles" can also match pages that merely contain the string. Enumerate what it caught before trusting it.
  • updatedAt may be a bulk touch: count distinct dates and the max articles sharing one — hundreds of articles sharing a handful of timestamps means a bulk operation. True update dates may live in content bylines or schema.org markup — ask the client what their content contains.
  • The session’s current account is an in-memory override that can reset silently — pass accountId explicitly on every call, subagents especially. content_planner_list_questions’s questionCounts.total can undercount; the returned rows are authoritative.

See also