How OpenSEO Integrates with Google Search Console for Keyword Data: A Complete Technical Guide

OpenSEO pulls keyword metrics, impressions, and click-through rates from Google Search Console through a layered TypeScript architecture that manages OAuth grants in the account table, persists site connections via GscConnectionRepository, and queries the GSC REST API through the GscService facade.

OpenSEO enables users to analyze search performance without leaving the application by integrating directly with the Google Search Console API. The codebase implements a clean separation between authentication, data persistence, and API communication, ensuring type-safe access to keyword metrics like clicks, impressions, and average position. This integration centers around the GscService class, which orchestrates OAuth grants defined in [src/shared/gsc.ts](https://github.com/every-app/open-seo/blob/main/src/shared/gsc.ts) and delegates HTTP operations to a specialized client layer.

Architecture of the GSC Integration

The integration follows a facade pattern that shields the frontend from Google API complexities. The architecture consists of three distinct layers: OAuth credential management, connection persistence, and low-level API communication.

OAuth Grant Management

Authentication begins with the GSC_OAUTH_PROVIDER_ID constant defined in [src/shared/gsc.ts](https://github.com/every-app/open-seo/blob/main/src/shared/gsc.ts). When a user connects their Google account, OpenSEO stores the OAuth refresh token in the account table with providerId set to this GSC-specific identifier. The GscService.userHasGrant() method checks for the existence of this grant before attempting any API calls, ensuring the application never requests data without valid credentials.

Connection Persistence

Site-level connections are managed by [GscConnectionRepository.ts](https://github.com/every-app/open-seo/blob/main/src/server/features/gsc/repositories/GscConnectionRepository.ts), which maintains the relationship between an OpenSEO project and a specific GSC property (site URL). This repository handles the siteUrl, Google account email, and verification status, allowing projects to maintain persistent links to search properties even when users navigate away from the settings page.

API Client Abstraction

The [src/server/lib/gscClient.ts](https://github.com/every-app/open-seo/blob/main/src/server/lib/gscClient.ts) file provides a thin wrapper around the Google Search Console REST endpoints. It handles automatic token refresh, request construction for the searchAnalytics query method, and URL inspection calls. Errors are categorized into GscApiError and GscTokenError classes defined in [src/server/lib/gscErrors.ts](https://github.com/every-app/open-seo/blob/main/src/server/lib/gscErrors.ts), enabling the service layer to distinguish between permission denials and authentication failures.

Core Implementation Files

The following files comprise the complete integration stack:

Step-by-Step Data Flow

Understanding the request lifecycle helps debug connection issues and optimize data fetching strategies.

Authorizing Access

When a user initiates a connection, the application redirects to Google's OAuth endpoint using the provider ID from [src/shared/gsc.ts](https://github.com/every-app/open-seo/blob/main/src/shared/gsc.ts). Upon callback, the refresh token is encrypted and stored in the account table. The GscService retrieves this token via internal user ID lookups whenever it instantiates a new GSC client.

Listing Search Console Properties

To display available properties, GscService.listSitesForUserWithGrantStatus() creates a client instance using the stored refresh token and calls the underlying listSites() method. This returns every site the user can access, including permission levels such as "siteUnverifiedUser" or "siteOwner".

Establishing Site Connections

Selecting a property triggers GscService.setSite(), which validates that the chosen URL belongs to the selected Google account and is verified. If the site is unverified, the service throws a FORBIDDEN error before persisting the connection via GscConnectionRepository.

Fetching Keyword Analytics

Performance queries flow through GscService.getPerformance(), which retrieves the stored connection, builds a searchAnalytics request using buildSearchAnalyticsRequest, and executes client.querySearchAnalytics(). The response contains rows of keyword data with dimensions like query, page, or country, alongside metrics including clicks, impressions, ctr, and position.

Inspecting URL Index Status

For debugging indexing issues, GscService.inspectUrls() accepts an array of URLs and sequentially calls client.inspectUrl() for each entry. This batch processing captures per-URL errors individually, preventing a single failed inspection from aborting the entire request.

Working with the GscService API

The following TypeScript examples demonstrate common operations when interacting with the Google Search Console integration.

Checking if a User Has a GSC Grant

Before displaying connection UI elements, verify the user has authenticated with Google:

import { GscService } from "@/server/features/gsc/services/GscService";

const hasGrant = await GscService.userHasGrant(currentUser.id);
if (!hasGrant) {
  // Prompt user to connect Google Search Console
  console.log("No GSC authorization found");
}

Source: GscService.userHasGrant

Retrieving Available Sites

List all properties accessible to the authenticated user:

const siteList = await GscService.listSitesForUserWithGrantStatus(currentUser.id);

for (const account of siteList.accounts) {
  console.log(`Account: ${account.email}`);
  for (const site of account.sites) {
    console.log(`  - ${site.siteUrl} (${site.permissionLevel})`);
  }
}

Source: GscService.listSitesForUserWithGrantStatus

Connecting a Project to a Property

Persist the association between an OpenSEO project and a specific GSC site:

await GscService.setSite({
  projectId: "proj_123",
  organizationId: "org_456",
  siteUrl: "https://example.com/",
  accountId: "google-sub-789",
  userId: currentUser.id,
});

Source: GscService.setSite

Querying Keyword Performance Data

Fetch clicks and impressions for specific keywords within a date range:

const performance = await GscService.getPerformance({
  projectId: "proj_123",
  startDate: "2024-01-01",
  endDate: "2024-01-31",
  dimensions: ["query"],
  rowLimit: 250,
});

performance.rows.forEach(row => {
  console.log(`${row.keys[0]}: ${row.clicks} clicks, ${row.impressions} impressions`);
});

Source: GscService.getPerformance

Inspecting Multiple URLs

Check the index status of specific pages:

const inspection = await GscService.inspectUrls({
  projectId: "proj_123",
  urls: [
    "https://example.com/blog/post-1",
    "https://example.com/products/item-2",
  ],
  languageCode: "en",
});

inspection.results.forEach(result => {
  if (result.error) {
    console.error(`Failed to inspect ${result.url}: ${result.error}`);
  } else {
    console.log(`${result.url}: ${result.result?.indexStatus}`);
  }
});

Source: GscService.inspectUrls

Handling Authentication Failures

Implement reconnection prompts when tokens expire or are revoked:

try {
  const data = await GscService.getPerformance({ projectId, startDate, endDate });
} catch (error) {
  if (GscService.isExpectedGrantFailure(error)) {
    // Trigger UI reconnection flow
    showReconnectModal();
  } else {
    // Handle unexpected API errors
    console.error("Unexpected GSC error:", error);
  }
}

Source: GscService.isExpectedGrantFailure

Summary

Frequently Asked Questions

How does OpenSEO store Google Search Console authentication tokens?

OpenSEO stores GSC authentication as OAuth grants in the account table using the provider identifier GSC_OAUTH_PROVIDER_ID from [src/shared/gsc.ts](https://github.com/every-app/open-seo/blob/main/src/shared/gsc.ts). The refresh token is encrypted and associated with the user's account ID, allowing the [gscClient.ts](https://github.com/every-app/open-seo/blob/main/src/server/lib/gscClient.ts) to instantiate authenticated sessions without requiring repeated logins.

What happens when a user's GSC token expires or is revoked?

When the Google API returns a 401 or 403 error, the GscTokenError class captures the failure. The GscService.isExpectedGrantFailure() method identifies these specific errors, triggering the UI to display a reconnection prompt rather than crashing the application.

Can OpenSEO connect to multiple Google Search Console properties simultaneously?

While a single OpenSEO project connects to one GSC property at a time via GscConnectionRepository, the listSitesForUserWithGrantStatus() method allows users to view all accessible properties across multiple Google accounts. Users can switch connections by calling setSite() with a different siteUrl and accountId.

How does the integration handle API rate limits and errors?

Error handling is centralized in [src/server/lib/gscErrors.ts](https://github.com/every-app/open-seo/blob/main/src/server/lib/gscErrors.ts), which distinguishes between GscApiError (general API failures) and GscTokenError (authentication issues). The inspectUrls() method processes URLs sequentially with individual error catching, ensuring that temporary rate limiting on one URL does not prevent inspection of others in the batch.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →