A Google Search Console API alternative, for people who just want the data
Most people looking for a Search Console API alternative don't actually want a different API. They want to skip the four things every GSC client ends up building. The API works fine — it's well documented and it does what it says. The cost is that "get my Search Console data into something I can query" turns into an auth layer, a pagination loop, a storage schema and a scheduler, none of which are the thing you set out to do. This post is an honest map: what the API costs you, what each alternative trades away, and how to get from zero to a SQL query over your own GSC data.
The four things you end up building anyway
Write a Search Console client and the shape is always the same, regardless of language:
- Auth. OAuth 2.0, a consent screen, and then the unglamorous part — storing a refresh token somewhere that isn't your git repo, and refreshing it before it bites you at 3am.
- Pagination. The Search Analytics endpoint returns a capped number of rows per request (25,000), so any query with the
queryorpagedimension on a real site means looping withstartRowuntil the response comes back short. - Storage. The API answers one request at a time and returns JSON. Every interesting question — week over week, which queries are new, which pages decayed — is cross-day, so you have to land the rows somewhere before you can ask anything. That's a schema, a database and a dedupe strategy.
- A scheduler with a memory. Data finalizes late, so you can't pull once and be done; you need incremental syncs that re-pull the trailing window, plus retries for the runs that fail while you're asleep.
None of it is hard. All of it is maintenance, and it's maintenance that produces zero insight on its own. Add the API's own boundaries — data retained for about 16 months, and rare queries withheld for privacy so your row totals never quite reconcile with the summary numbers — and you've spent a weekend to arrive at the starting line.
The honest comparison
There are four realistic paths. Which one is right depends entirely on what you're building.
1. The API itself
Best when you're shipping a product on top of GSC data, need control over every field, or have compliance reasons the data can't leave your infrastructure. Costs the four layers above, forever.
2. Looker Studio's Search Console connector
Best when you need a chart for a stakeholder this afternoon and nothing else. Free, no code. Trades away the data itself — it lives inside the report, so joining it against ad spend or GA4 sessions means fighting blend limitations, and there's no SQL surface to ask an unanticipated question. Reports also tend to get slow as ranges grow.
3. The UI export
Best when it's genuinely one-off. Trades away completeness and time — the interface caps what you can pull, and every export is a snapshot that's stale tomorrow. Fine for one answer, corrosive as a habit.
4. A managed backend you query in SQL
Best when you want the data as data — queryable, joinable, current — without owning the pipeline. This is where TableBI sits: connect Search Console once, and the rows land in a table your agent (or you) query with ordinary read-only SQL. Trades away field-level control over the sync itself; you get the connector's behavior, not your own.
Not a hosted-model product: TableBI runs no LLM over your numbers. The backend serves deterministic SQL results; if an agent is doing the interpreting, that happens in your own Claude Code session under your account. Inference cost on our side is $0.
Zero to a query, without a client
The connector owns auth, pagination, incremental syncing and retries. Your side is one command:
npm i -g @tablebi/cli
tablebi login
# browser opens for OAuth, then data syncs in
tablebi connect gsc --site sc-domain:example.com
No consent screen to configure, no refresh token in your repo, no startRow loop. To keep a source current later, or to see how far behind each one is:
tablebi sync gsc # pull the latest, with retries handled server-side tablebi sources # connected sources + freshness
What you actually came for: SQL over GSC
Synced Search Console data lands in search_console_raw — lossless, per-row, exactly as Google reported it: date, site, query, page, clicks, impressions, position. Which means the questions that needed a storage layer are now one command. Rising and falling queries, week over week:
tablebi ask "SELECT query,
SUM(CASE WHEN date > (SELECT MAX(date) FROM search_console_raw) - 7
THEN impressions ELSE 0 END) AS this_week,
SUM(CASE WHEN date <= (SELECT MAX(date) FROM search_console_raw) - 7
THEN impressions ELSE 0 END) AS prev_week
FROM search_console_raw
WHERE date >= (SELECT MAX(date) FROM search_console_raw) - 14
GROUP BY query ORDER BY this_week - prev_week DESC LIMIT 20"
Pages that get impressions but no clicks — the CTR opportunity list, using the avg_position macro so position is impression-weighted rather than a naive average of averages:
tablebi ask "SELECT page, SUM(impressions) AS imp, SUM(clicks) AS clicks,
avg_position(SUM(position*impressions), SUM(impressions)) AS pos
FROM search_console_raw
WHERE date >= (SELECT MAX(date) FROM search_console_raw) - 28
GROUP BY page HAVING SUM(impressions) > 200
ORDER BY clicks ASC LIMIT 20"
Two details worth stealing. First, anchor date windows to MAX(date), never to today — GSC finalizes performance data roughly two to three days late, so a filter on the current date silently includes days that will fill in later and makes every recent trend look like a collapse. Second, the impression-weighted position: averaging the position column directly gives a number that's wrong in a way that looks plausible, which is the worst kind of wrong.
If you run several sites, there's a shortcut for the question you'd otherwise write by hand every Monday:
tablebi scoreboard # which properties are growing, which are sliding
The thing the API can't give you: joins
This is the real argument for landing GSC data somewhere queryable rather than calling the API per question. Search Console alone tells you what happened in organic search. It can't tell you whether the page that lost impressions is one you're also paying to advertise on, or whether the traffic you kept actually converted.
Because GA4, Meta Ads and Google Ads connect through the same backend, organic sits next to paid under one set of definitions — a unified facts table with shared macros (roas, ctr, cpa) alongside the per-platform raw tables. Every answer also carries a trust block: how fresh each source is, and the caveats that stop naive comparisons — including the explicit one that a GSC click and a GA4 session are different populations counted different ways and shouldn't be expected to match. That caveat exists because reconciling those two numbers is the single most common way a Search Console analysis goes wrong.
And then stop re-running it
Any query worth running twice can be pinned to a live URL that refreshes itself as data syncs:
tablebi pin --title "Search performance" \ --widget "Daily clicks (28d)::line=SELECT date, SUM(clicks) AS clicks FROM search_console_raw …" \ --widget "Top queries=SELECT query, SUM(impressions) AS imp FROM search_console_raw …" ✓ published → https://dk.tablebi.com/d/dsh_… (public, read-only, self-refreshing)
Here's a real one — live Search Console data across a portfolio of sites, pinned exactly this way: