API reference
Search Console
Pull Google Search Console data (clicks, impressions, CTR, average position, queries, pages, countries, devices) into a site's dashboard. Synced into Totallytics storage, so history outlives Google's 16-month retention.
Base URL: https://analytics.bitgate.dev. Download OpenAPI or read this page as Markdown.
Access
All endpoints on this page require the site owner Firebase ID bearer token, even for public sites — GSC data never appears on public dashboards. The OAuth callback (/api/import-oauth/google/callback) is public; the signed, encrypted, 10-minute-expiring state blob is the capability.
Connecting
The OAuth dance is browser-driven and normally starts from site settings → Google Search Console → Connect. Programmatically:
POST /api/import-oauth/google/startwith{ "site": "example.com", "provider": "google-search-console", "origin": "https://totallytics.app" }→ returns{ "url" }; send the browser there.originmust be one of the app hosts and decides where the user lands afterwards.- Google redirects back to
/api/import-oauth/google/callback, which stores an encrypted refresh token (one connection per account + provider) and bounces to…/settings?gsc=connected. GET /api/sites/{hostname}/gsc/properties?connection_id=…lists the account's Search Console properties, withmatches: trueon the ones covering this site.POST /api/sites/{hostname}/gsc/linkwith{ "connection_id", "property" }validates access and starts the backfill.
Sync behavior
Linking kicks off a backfill that walks backwards month-by-month through 16 months of history, then the link flips to live. A nightly job refreshes the trailing 10 days (Google finalizes data with a 2-3 day lag; only finalized data is stored). Syncs are insert-only: every run writes fresh versions and reads collapse to the newest, so retries and overlapping runs never duplicate or hole data. POST …/gsc/sync queues an out-of-band refresh of the recent window.
sync_state is backfill, live, or error (a revoked Google connection lands in error with last_error set; reconnect from settings to resume).
Endpoints
| Method | Route | Result |
|---|---|---|
| POST | /api/import-oauth/google/start |
{ url } consent URL |
| GET | /api/import-oauth/google/callback |
OAuth redirect target (public) |
| GET | /api/import-oauth/google/connections |
List connections (tokens never returned) |
| DELETE | /api/import-oauth/google/connections/{id} |
Delete connection, revoke at Google, unlink sites |
| GET | /api/sites/{hostname}/gsc/properties |
List the account's GSC properties |
| POST | /api/sites/{hostname}/gsc/link |
Link a property, start backfill |
| DELETE | /api/sites/{hostname}/gsc/link |
Unlink (synced data is kept) |
| GET | /api/sites/{hostname}/gsc/status |
Link + sync state |
| POST | /api/sites/{hostname}/gsc/sync |
Queue a refresh (202) |
| GET | /api/sites/{hostname}/gsc/overview |
Totals, previous period, daily series, tracker overlay |
| GET | /api/sites/{hostname}/gsc/breakdown |
Top queries / pages / countries / devices |
Overview response
GET …/gsc/overview?from=<unix>&to=<unix>&tz=<iana> returns:
{
"totals": { "clicks": 512, "impressions": 39120, "ctr": 0.0131, "position": 18.4 },
"previous": { "clicks": 480, "impressions": 36600, "ctr": 0.0131, "position": 19.1 },
"series": [{ "t": 1758326400, "clicks": 31, "impressions": 2404, "ctr": 0.0129, "position": 17.9 }],
"overlay": [{ "t": 1758326400, "gsc_clicks": 31, "tt_pageviews": 24 }]
}previouscovers the equally-sized period directly before the range.positionandctrare impression-weighted, matching the Search Console UI.overlayputs GSC clicks next to the same day's google-referrer pageviews measured by the tracker — a quick read on how many search clicks actually land (ad blockers, bounces before load, and SERP features all eat clicks).- Day buckets follow the
tzparameter; the underlying GSC days are Pacific Time, so exact boundaries can differ by a few hours from your local tz.
Breakdown response
GET …/gsc/breakdown?dim=queries&from=…&to=…&limit=100 returns { "dim": "queries", "rows": [{ "name", "clicks", "impressions", "ctr", "position" }] } sorted by clicks. countries uses ISO 3166-1 alpha-3 codes (as GSC returns them). limit caps at 500.
GSC anonymizes rare queries and caps rows per day, so query totals can undershoot the daily totals — that's Google's data, not a sync bug.