Key takeaways
- Nine blocks, seventeen endpoints probed one by one on 2026-08-27 across 16 Search Console properties and 43 GA4 properties. Freshness, ceilings, latencies and costs recorded call by call
- Three blocks of the same dashboard do not agree on a total. Grouping by query shows only 20.6% to 67.4% of clicks depending on the property. Grouping by page returns 100.5% to 106.4% of them, and up to 120.7% of impressions
- The AI citation block is not the hole in the dashboard. It exists, it is paid, it costs $0.101 per call. The real hole, on our side, is field Core Web Vitals, for want of an API key on the machine
- Two measured walls on the AI side: the ChatGPT platform refuses France, and every AI Overview source comes back with the domain google.com behind a redirect
- The three paid blocks cost $0.132536 for one pass, one call each, one keyword, one domain. No monthly cost was extrapolated
We ran a French SERP on "reporting seo", the very keyword this page targets, to see what an API actually returns for it. Google serves a full AI Overview there, with two references. Both come back under the same domain: google.com, behind a google.com/goto?url= redirect. The real names of the sources, YouTube and Semrush, exist only in a text field on the side.
That is a fair summary of what a dashboard covering both Google and AI engines looks like in 2026: the data exists, it is reachable, and it is never quite the thing you think you are reading.
On 27 August 2026 we probed, one by one, the endpoints feeding the nine blocks of our own dashboard: nine Google endpoints and eight DataForSEO endpoints, across 16 Search Console properties and 43 GA4 properties. Four questions each: what freshness, what ceiling, what latency, what cost. What follows is the record, including the two places where it proved us wrong.
Three blocks of the same dashboard, three different totals
Let us start with the most awkward result, because it cracks the way most dashboards are assembled.
Search Console returns a total when you query it with no dimension. It returns rows when you group by query, and other rows when you group by page. The three do not land on the same number. We measured it across 5 properties, over the 28 days from 28 July to 24 August 2026.
On vydera.com, grouping by query leaves only 20.6% of clicks visible: 143 of the window's 180 clicks appear in no query row at all. On une propriété du portefeuille the same operation shows 67.4%. Google drops rare queries for privacy reasons, and the size of that filter depends on the site's traffic profile, not on a setting you failed to tick.
In the other direction, grouping by page returns more clicks than the total: 100.5% to 106.4%, and up to 120.7% of impressions. Each of these five readings fits in a single call, so the overshoot does not come from pagination badly stitched on our side.
Two pieces of honesty, up front. n = 5 properties: an observed range, not a law, and it carries no test value. And the ratio between visible clicks and visible impressions inverts from one site to another: une propriété du portefeuille shows 46.6% of its clicks but 84.2% of its impressions, une propriété du portefeuille does exactly the opposite. The relationship is not monotonic and our measurement does not explain why. Same for the PAGE asymmetry, small on clicks and large on impressions: it is measured, the deduplication mechanism that might explain it is not.
The operational consequence, on the other hand, is unambiguous. Never display a total and a breakdown side by side without saying which dimension each comes from. The executive who adds up your query table and compares it to the big number at the top is right to come and ask: the dashboard is lying, not them.
The nine blocks, and where each number comes from
Here is the dashboard as we build it, block by block. Six run on Google APIs with nothing to pay. Three go through DataForSEO and are billed per call. Each block opens the exact call, the measured freshness, the measured ceiling and the caveat to know before wiring it in.
Two deliberate absences. Rank tracking gets no block of its own: your own site's positions are already in Search Console and cost nothing. You only buy a ranked_keywords call to look at a domain you do not own. And field Core Web Vitals are not there either: that is the real hole in this record, and we come back to it below.
GA4 runs two days ahead of Search Console
Freshness is the first setting to settle, because it decides what you put on the chart at the top.
Measured identically on une propriété du portefeuille and vydera.com, taking the last date present in a response grouped by DATE rather than the last date with non-zero clicks: Search Console on dataState: final runs 2 days behind. On dataState: all it runs zero days behind and serves the current day.
GA4 also serves the current day: 0 days behind, 30 sessions already counted on the day of the reading. On the same screen, GA4 therefore runs two days ahead of Search Console.
The trap is in all. The day it serves is necessarily partial: it is not over. A dashboard that displays it without saying so shows a daily drop that does not exist, and you will spend a Monday morning hunting a failure that never happened. The rule is simple: final for any trend line, all only if the last point is labelled partial.
One last freshness detail, in the other direction: the data stops between 4 April 2025, where the API returns zero rows, and 15 April 2025, where it returns five. That is roughly 16 months of history. A year-on-year comparison over 18 months is not possible through the API, whatever your tool claims.
The ceilings that bite, and the ones that do not exist
A documented ceiling and a measured ceiling are two different things. Here are the ones we actually hit.
- 25,000 rows per call, exactly.
rowLimit: 25001and50000both return HTTP 400, "is not a valid row limit value". Pagination throughstartRowthen takes over until the rows run out naturally: 25,354 query rows in 2 calls on une propriété du portefeuille. SEARCH_APPEARANCEcombines with nothing. "Cannot group by search appearance dimension together with another dimension." Every other combination passes, including the remaining five together: DATE, QUERY, PAGE, COUNTRY and DEVICE in one call.- Hourly data is capped at 231 hours. The HOUR dimension requires
dataState: hourly_all, otherwise HTTP 400. And the window is bounded whatever you ask for: 8 days requested return 199 hours, 12 days and 20 days both return 231 hours, about 9.6 days. DATE and HOUR do not group together. - On URL Inspection, the limit is latency, not quota. 6,445 to 6,642 ms per call, 6,549 ms average over 3 calls. Burning through the documented quota of 2,000 per day per property would take 218 minutes sequentially, more than three and a half hours. We did not reach it and did not try: burning it to confirm it would have starved our own indexation audits of the same quota.
- The GA4 quota is readable in the response. With
returnPropertyQuota: true, the API returns its own counter: 39,979 tokens left on the hour for the property, but only 13,979 for the project. On a dashboard polling 43 properties, the project ceiling is the one that binds first, and it appears on no doc page you would read before writing the code.
One more, nastier: URL Inspection does not return the same field set from one URL to the next. Ten fields on our home page, five on a URL never crawled, eleven on a page present in the sitemap, which also gains a rich results block. A dashboard that assumes a fixed schema breaks precisely on the URLs you most want to inspect. And since we are publishing what stings: our /lab page has never been crawled, and our French contact page was last crawled on 11 June 2026.
The sitemap block, meanwhile, comes down to a single useful field. lastDownloaded gives the age of Google's last read: 10 days on vydera.com against 2 days on une propriété du portefeuille, which says more about crawl frequency than any chart. The indexed field on contents returns 0 everywhere: it is no longer populated. Do not wire it in.
The AI citation block exists. We expected a hole and found a price.
The plan for this article said: on AI citations there is no public API equivalent to Search Console, and that is what explains the price of GEO tools on the market. The measurement does not confirm it.
The exact version is more nuanced, and more useful. There is no first-party API at no cost: neither OpenAI nor Anthropic nor Perplexity opens the equivalent of a Search Console on their answers. A third-party API does, however, it works, and we were billed for it during this record.
llm_mentions/target_metrics_litereturns a mention count and an AI search volume for a domain: $0.101 per call. On une propriété du portefeuille, platformgoogle, France and French: 6 mentions and an AI search volume of 650. 10 entities per call.chat_gpt/llm_scraperreplays a ChatGPT search and returns the sources cited: $0.004 per call, 13.7 seconds of latency. On the query we tested, 6 sources cited, but 0 search results, 0 fan-out queries and 0 brand entities returned, even withforce_web_searchset totrue.serp/google/organic/live/advancedreturns the full AI Overview: $0.0035 per call, one keyword, 2.9 seconds.
So the AI citation block is not the hole in the dashboard. It is paid, it is cheap, and it has two walls worth knowing before you sell AI visibility tracking in France.
First wall: the ChatGPT platform refuses France. With platform: chat_gpt and location_code: 2250, the task comes back as error 40501, "Invalid Field: 'location_code'", and the call is not billed. Only location_code: 2840 and language_code: en pass. In other words, tracking ChatGPT mentions on the French market is not available through this route. The google platform, the AI Overviews one, accepts France and French without complaint.
Second wall: AI Overview sources are masked. That is where we started. On "reporting seo" in France, the ai_overview item comes back fully loaded, with its two references. But the domain field on each reads google.com and the URL is a redirect. A dashboard counting "our domain among the AI Overview references" by reading domain will count zero forever. The real source name sits in the source field, and that is a label, not a domain.
The block nobody wires in, and it costs nothing
One block of the dashboard already exists in your stack and is almost never looked at: AI referrers in GA4. A runReport on the sessionSource dimension, and you have the visits arriving from an assistant's answer.
On vydera.com, 28 days, 691 sessions across 22 sources: claude.ai 5 sessions, gemini.google.com 2 sessions. No other AI source detected.
Seven sessions out of 691. The number is laughable and we publish it as is, because it says two true things. First: the signal exists, it is traceable, and it costs nothing to wire in. Second: 7 sessions carry no test value, and nobody should be steering anything by them today.
One clarification that matters. GA4 measures visits coming from an AI answer, not citations without a click. It is not a substitute for citation tracking, it is the other end of the chain: one measures exposure, the other the outcome. On how AI engines read your site in the first place, we covered the upstream half in our llms.txt guide.
What it costs, and why our own ledger was wrong
The three paid blocks, at one call each, one keyword and one domain, come to $0.132536 for one pass: 0.0035 for the AI Overview, 0.101 plus 0.004 for LLM mentions, 0.024036 for backlinks. That figure is a sum of measured costs, not a projection. We did not extrapolate the monthly cost of tracking N keywords, and this number does not support it: that would need its own measurement, on the volume you actually target.
The whole record, seven calls to paid endpoints of which one was refused at zero, cost $0.246736.
And here is where the measurement proved us wrong. Our own internal ledger carried this warning, written after an earlier pass: "do not trust the published rate, measure the balance before and after". Wrong on both halves.
Across the 7 calls, the cost field in the response equals the balance delta bracketing the call, to the sixth decimal. And the rate published in /v3/appendix/user_data reproduces it exactly, provided you add the per_result component to the per_request one: 0.1 + 0.001 × 1 = 0.101 for mentions, 0.024 + 0.000036 × 1 = 0.024036 for backlinks, 0.012 + 0.00012 × 10 = 0.0132 for ranked keywords. The gap on the earlier pass is explained without assuming any hidden surcharge: a sum of per_request rates alone mechanically underestimates the bill as soon as an endpoint also charges per result.
The second half of the warning was worse. During this project the balance moved by $0.09 between two readings with none of our own calls running, exactly the rate of another endpoint: the account is shared across several projects. Bracketing a balance around a series of calls therefore attributes nothing reliably. The cost field on each response is the right instrument; the balance only serves to check it. The ledger has been corrected.
What we could not measure, and why we say so
The real hole in this dashboard is not where the plan announced it. It is field Core Web Vitals. No Google API key exists on the machine: the variable meant to hold it is declared and empty. CrUX therefore returns a 403, "Method doesn't allow unregistered callers", and PageSpeed Insights a 429, its shared anonymous quota already exhausted.
Read that for what it is: two credential failures, not a verdict on these APIs. CrUX works perfectly well with a key, we did not have one. The hole is fixed by an activation form in the Google Cloud console, not by a subscription. That is exactly the nuance a rushed audit turns into "CrUX does not work", and it would be false.
Since we are on rigour, the confession that goes with it. The first run of our script reported a CrUX error, "API key not valid". It came from our own code, which pasted an empty variable into the URL and sent ?key=null. The real error is the 403. The script no longer appends the parameter when the key is missing. A script bug nearly became a published fact, and that is precisely how a dashboard starts lying politely for six months.
Three other blocks stay out of reach, and we would rather say so than fill them approximately:
- AI crawler hits on the site. Webflow does not expose server logs, and no API works around that.
- Share of voice in AI answers, tracked over time. Verified at runtime: no tracking project exists on vydera.com today. That is an accepted debt, not a technical limit.
- Bing Webmaster Tools. No key on the machine, the API was never called. So we have nothing to say about it.
Running the measurement yourself
Everything above replays with a read-only OAuth token on Search Console and GA4, plus a DataForSEO account for the three paid blocks. The principle fits in one sentence: probe each endpoint in isolation and read the response for its own metadata, rather than trusting the documentation.
Three rules that saved us from publishing wrong numbers.
- Measure freshness on the last date present in a response grouped by DATE, not on the last date with non-zero clicks. The second will convince you of a lag that does not exist, especially on a small site. And if the API returns zero-impression date rows for a search type you do not use, that is not freshness: it is a dated empty table.
- A 403 or a 429 is not a result. It is a missing credential or a shared quota. Record it as such, never as a verdict on the API.
- Cost is read from the response, call by call. Not from the balance, especially if the account serves several projects.
On choosing what goes into the dashboard once the sources are wired, we published the grid elsewhere: which KPIs are worth a decision, and how to turn all this into euros without lying. For the vocabulary, the webmaster tools glossary entry sets the terms. And if you would rather have it wired, measured and reviewed by someone else, that is what our performance tracking offer does.
Why does the Search Console total not match the sum of queries?
Because Google drops rare queries from the QUERY dimension for privacy reasons. Across the 5 properties we measured, the share of clicks still visible when grouping by query runs from 20.6% to 67.4%. On vydera.com, 143 clicks out of 180 appear in no query row. The no-dimension total is the right number; the query breakdown is a sample.
Can you track your ChatGPT citations through an API?
Not through a first-party API: OpenAI opens no Search Console equivalent. A third-party API does, at $0.101 per call for a mention count and $0.004 to replay a search. But the ChatGPT platform refuses France there: the French location code returns a 40501 error, only the United States and English pass. The Google platform, the AI Overviews one, does accept French.
How fresh is Search Console data, really?
2 days behind on
dataState: final, zero ondataState: all, measured identically on two properties. Careful:allserves the current day, which is necessarily partial. A dashboard displaying it without flagging it shows a daily drop that does not exist. GA4 also serves the current day and therefore runs two days ahead of Search Console.How many rows does the Search Console API return per call?
25,000 exactly. A value of 25,001 or 50,000 returns HTTP 400, "is not a valid row limit value". Beyond that, pagination through
startRowruns until the rows are naturally exhausted: 25,354 queries in 2 calls on the busiest property in our account.What does it cost to feed an SEO and AEO dashboard?
Six of the nine blocks run on Google APIs and cost nothing. The other three, at one call each, one keyword and one domain, come to $0.132536 for one pass: 0.0035 for the AI Overview, 0.105 for LLM mentions, 0.024036 for backlinks. We did not extrapolate a monthly cost: it depends on how many keywords you track and needs its own measurement.
Can field Core Web Vitals be tracked through an API?
Yes, through the CrUX API, but it requires a Google API key and refuses anonymous callers with a 403. We had no key on the machine at the time of the reading, and the PageSpeed Insights anonymous quota was already exhausted, hence a 429. Those are two credential failures, not a limit of these APIs: activation happens in the Google Cloud console.



