Skip to main content
RapidDev - Software Development Agency
bolt-ai-integrationsBolt Chat + API Route

How to Integrate Bolt.new with Google Search Console

Integrate Google Search Console with Bolt.new by creating a Next.js API route that calls the Search Console API v3 using OAuth 2.0 or a service account. This provides authoritative first-party keyword data — clicks, impressions, positions, and CTR for sites you own. The API is free with a 1,200 queries/minute rate limit. OAuth redirect requires a deployed URL, not Bolt's preview.

What you'll learn

  • How to enable the Google Search Console API and set up credentials in Google Cloud Console
  • How to implement a service account integration for server-side Search Console access
  • How to fetch keyword performance data — queries, clicks, impressions, and ranking positions
  • How to build a search performance dashboard with date range comparison in Bolt.new
  • Why OAuth redirect URLs require a deployed domain and how to handle this in Bolt's development environment
Book a free consultation
4.9Clutch rating
600+Happy partners
17+Countries served
190+Team members
Intermediate16 min read35 minutesSEOApril 2026RapidDev Engineering Team
TL;DR

Integrate Google Search Console with Bolt.new by creating a Next.js API route that calls the Search Console API v3 using OAuth 2.0 or a service account. This provides authoritative first-party keyword data — clicks, impressions, positions, and CTR for sites you own. The API is free with a 1,200 queries/minute rate limit. OAuth redirect requires a deployed URL, not Bolt's preview.

Build a Search Performance Dashboard with Google Search Console and Bolt.new

Google Search Console API provides the most authoritative SEO data available for sites you own: actual clicks and impressions from Google's search index, average ranking positions, click-through rates, and breakdowns by query, page, country, and device. Unlike third-party SEO tools that estimate this data from panels and sampling, Search Console data is Google's own record of how your site performs in their search results. The API is completely free with a generous rate limit of 1,200 queries per minute — sufficient for any dashboard use case.

The integration requires handling Google's OAuth 2.0 authentication, which adds some complexity compared to simple API key integrations. For building internal dashboards or agency tools where you control which properties are accessed, a Google Cloud service account is the simpler approach — you add the service account email as a user in Search Console, and the server authenticates server-to-server without per-user OAuth flows. For multi-tenant tools where different users connect their own Search Console properties, OAuth 2.0 with user consent is the correct approach.

One critical constraint for Bolt.new development: OAuth 2.0 redirect URIs must be pre-registered in Google Cloud Console, and they must be stable, predictable URLs. Bolt's WebContainer preview uses dynamic URLs like `https://[hash].local.credentialless.webcontainer-api.io/` that change between sessions and cannot be registered as OAuth redirect URIs. For the service account approach, this doesn't matter — there's no OAuth callback. For OAuth 2.0 with user consent, you must deploy to Netlify or Bolt Cloud first, then register your deployed URL as an authorized redirect URI.

Integration method

Bolt Chat + API Route

Google Search Console API uses OAuth 2.0 to authenticate site owners and retrieve private search performance data. All API calls are proxied through a Next.js API route to keep OAuth tokens server-side. For server-to-server integrations without per-user OAuth, a Google Cloud service account with Search Console property access works as a simpler alternative. OAuth callbacks require a deployed URL since Bolt's WebContainer preview uses dynamic URLs incompatible with OAuth redirect URI registration.

Prerequisites

  • A Google account with verified properties in Google Search Console
  • A Google Cloud project with the Search Console API enabled
  • Either: a service account with the service account email added as a Search Console property user
  • Or: OAuth 2.0 client credentials (requires a deployed URL for redirect URI registration)
  • A Bolt.new project using Next.js (required for server-side Google API authentication)

Step-by-step guide

1

Enable the Search Console API and Create Service Account Credentials

Go to console.cloud.google.com and create a new project (or select an existing one). In the left sidebar, click 'APIs & Services' → 'Library'. Search for 'Google Search Console API' and click 'Enable'. Now create a service account: go to 'APIs & Services' → 'Credentials' → 'Create Credentials' → 'Service Account'. Give it a name like 'search-console-bot', click 'Create and Continue'. Skip role assignment for now (Search Console manages access separately) and click 'Done'. Click on the newly created service account, go to the 'Keys' tab, click 'Add Key' → 'Create new key' → select JSON → 'Create'. A JSON file downloads — this is your credential file. Keep it secure and never commit it to git. Open the JSON file and copy the `client_email` field value. Now go to Google Search Console (search.google.com/search-console), click Settings → Users and permissions → Add user, paste the service account email, select 'Restricted' or 'Full' permission, and click 'Add'. The service account now has programmatic access to that property. Store the entire JSON content as a single-line string in your .env file as GOOGLE_SERVICE_ACCOUNT_JSON.

Bolt.new Prompt

Set up Google Search Console API integration. Create a .env file with GOOGLE_SERVICE_ACCOUNT_JSON=paste_your_json_here and GSC_SITE_URL=https://yourdomain.com. Create lib/google-auth.ts that exports a getAccessToken() function using the service account JSON from environment variables to get a Google OAuth2 access token using the JWT grant flow.

Paste this in Bolt.new chat

lib/google-auth.ts
1// lib/google-auth.ts
2// Requires: npm install jose
3import { SignJWT, importPKCS8 } from 'jose';
4
5interface ServiceAccount {
6 client_email: string;
7 private_key: string;
8 token_uri: string;
9}
10
11let cachedToken: { token: string; expires: number } | null = null;
12
13export async function getGoogleAccessToken(): Promise<string> {
14 // Return cached token if still valid (with 60s buffer)
15 if (cachedToken && Date.now() < cachedToken.expires - 60000) {
16 return cachedToken.token;
17 }
18
19 const serviceAccountJson = process.env.GOOGLE_SERVICE_ACCOUNT_JSON;
20 if (!serviceAccountJson) {
21 throw new Error('GOOGLE_SERVICE_ACCOUNT_JSON not configured');
22 }
23
24 const sa: ServiceAccount = JSON.parse(serviceAccountJson);
25 const privateKey = await importPKCS8(sa.private_key, 'RS256');
26
27 const now = Math.floor(Date.now() / 1000);
28 const jwt = await new SignJWT({
29 iss: sa.client_email,
30 sub: sa.client_email,
31 aud: sa.token_uri,
32 scope: 'https://www.googleapis.com/auth/webmasters.readonly',
33 iat: now,
34 exp: now + 3600,
35 })
36 .setProtectedHeader({ alg: 'RS256' })
37 .sign(privateKey);
38
39 const tokenRes = await fetch(sa.token_uri, {
40 method: 'POST',
41 headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
42 body: new URLSearchParams({
43 grant_type: 'urn:ietf:params:oauth:grant-type:jwt-bearer',
44 assertion: jwt,
45 }),
46 });
47
48 const tokenData = await tokenRes.json();
49 if (!tokenData.access_token) {
50 throw new Error(`Token exchange failed: ${JSON.stringify(tokenData)}`);
51 }
52
53 cachedToken = {
54 token: tokenData.access_token,
55 expires: Date.now() + tokenData.expires_in * 1000,
56 };
57
58 return cachedToken.token;
59}

Pro tip: Install the 'jose' package for JWT signing: prompt Bolt with 'npm install jose'. The jose library is a pure JavaScript JWT implementation that works perfectly in Bolt's WebContainer. The in-memory token cache prevents repeated token exchanges for multiple API calls within the same session.

Expected result: The getGoogleAccessToken() function exchanges the service account JSON for a valid Google OAuth access token. Subsequent calls within 3600 seconds return the cached token.

2

Fetch Keyword Search Analytics Data

With authentication working, build the API route that queries Search Console's `searchAnalytics/query` endpoint. This endpoint is the core of the Search Console API — it returns keyword performance data for any verified property with flexible filtering and grouping. Key parameters: `startDate` and `endDate` define the reporting window (Search Console data has a 3-day delay, so yesterday's data may not yet be available), `dimensions` array controls how data is grouped ('query' for keywords, 'page' for URLs, 'country', 'device'), `rowLimit` caps the number of results (maximum 25,000), and `orderBy` controls sorting. The endpoint URL format is: `https://www.googleapis.com/webmasters/v3/sites/{siteUrl}/searchAnalytics/query`. Note that the siteUrl must be URL-encoded — a property like `https://example.com/` must become `https%3A%2F%2Fexample.com%2F`. The site URL must exactly match the property URL as it appears in Search Console — including the trailing slash if your property was added with one. Case sensitivity matters: `https://Example.com` and `https://example.com` are different properties.

Bolt.new Prompt

Create a Next.js API route at app/api/gsc/keywords/route.ts that accepts 'siteUrl' and optional 'days' query params. Use getGoogleAccessToken from lib/google-auth.ts to get a fresh token. Call the Google Search Console searchAnalytics/query endpoint with dimensions=['query'], last N days date range, rowLimit=20, and orderBy clicks descending. Return a JSON array with keyword, clicks, impressions, ctr, and position.

Paste this in Bolt.new chat

app/api/gsc/keywords/route.ts
1// app/api/gsc/keywords/route.ts
2import { NextRequest, NextResponse } from 'next/server';
3import { getGoogleAccessToken } from '@/lib/google-auth';
4
5export async function GET(request: NextRequest) {
6 const siteUrl = request.nextUrl.searchParams.get('siteUrl')
7 ?? process.env.GSC_SITE_URL;
8 const days = Math.min(parseInt(request.nextUrl.searchParams.get('days') ?? '28'), 90);
9
10 if (!siteUrl) {
11 return NextResponse.json({ error: 'siteUrl required' }, { status: 400 });
12 }
13
14 const endDate = new Date(Date.now() - 3 * 86400000); // 3-day delay
15 const startDate = new Date(endDate.getTime() - days * 86400000);
16 const fmt = (d: Date) => d.toISOString().split('T')[0];
17
18 try {
19 const token = await getGoogleAccessToken();
20 const encodedSite = encodeURIComponent(siteUrl);
21
22 const res = await fetch(
23 `https://www.googleapis.com/webmasters/v3/sites/${encodedSite}/searchAnalytics/query`,
24 {
25 method: 'POST',
26 headers: {
27 Authorization: `Bearer ${token}`,
28 'Content-Type': 'application/json',
29 },
30 body: JSON.stringify({
31 startDate: fmt(startDate),
32 endDate: fmt(endDate),
33 dimensions: ['query'],
34 rowLimit: 20,
35 orderBy: [{ fieldName: 'clicks', sortOrder: 'DESCENDING' }],
36 }),
37 }
38 );
39
40 if (!res.ok) {
41 const err = await res.json();
42 throw new Error(err.error?.message ?? `HTTP ${res.status}`);
43 }
44
45 const data = await res.json();
46 const keywords = (data.rows ?? []).map((row: any) => ({
47 keyword: row.keys[0],
48 clicks: row.clicks,
49 impressions: row.impressions,
50 ctr: Math.round(row.ctr * 1000) / 10,
51 position: Math.round(row.position * 10) / 10,
52 }));
53
54 return NextResponse.json({
55 siteUrl,
56 dateRange: { startDate: fmt(startDate), endDate: fmt(endDate) },
57 keywords,
58 });
59 } catch (error) {
60 const message = error instanceof Error ? error.message : 'Unknown error';
61 return NextResponse.json({ error: message }, { status: 500 });
62 }
63}

Pro tip: Search Console data has a 3-day delay — data for 'yesterday' is often not yet available. The route above automatically accounts for this by using a 3-day offset, so the most recent data shown is 3 days ago rather than yesterday. This prevents confusing empty results for recent dates.

Expected result: Calling /api/gsc/keywords?siteUrl=https://yourdomain.com returns the top 20 keywords by clicks for the last 28 days, matching what you see in the Google Search Console web interface.

3

Add Page-Level and Date Comparison Analytics

Extend the integration with two additional analysis views. First, add a page-level route using `dimensions: ['page']` instead of `['query']` — this returns which URLs on your site receive the most organic search traffic. Page-level data is critical for content audits: pages ranking on page 2 (position 11-20) with significant impressions are prime candidates for content updates that could bring them to page 1. Second, add date range comparison by running two parallel requests — one for the current period and one for the comparison period — then merging the results to calculate click and position changes. Promise.all runs both requests simultaneously, halving the wait time compared to sequential requests. The comparison logic joins the two datasets by keyword, calculates the delta for clicks and position, and surfaces the biggest movers. This is the same 'compare dates' feature visible in Search Console's UI, now available programmatically in your Bolt dashboard.

Bolt.new Prompt

Add a second API route at app/api/gsc/pages/route.ts that uses the same authentication but with dimensions=['page'] to return top 20 pages by clicks. Also add a comparison route at app/api/gsc/compare/route.ts that accepts currentPeriodDays and previousPeriodDays, runs two parallel searchAnalytics queries, and returns keywords with clickChange and positionChange fields showing the difference between periods.

Paste this in Bolt.new chat

app/api/gsc/compare/route.ts
1// app/api/gsc/compare/route.ts
2import { NextRequest, NextResponse } from 'next/server';
3import { getGoogleAccessToken } from '@/lib/google-auth';
4
5async function querySearchConsole(
6 siteUrl: string,
7 startDate: string,
8 endDate: string,
9 token: string
10) {
11 const res = await fetch(
12 `https://www.googleapis.com/webmasters/v3/sites/${encodeURIComponent(siteUrl)}/searchAnalytics/query`,
13 {
14 method: 'POST',
15 headers: { Authorization: `Bearer ${token}`, 'Content-Type': 'application/json' },
16 body: JSON.stringify({
17 startDate,
18 endDate,
19 dimensions: ['query'],
20 rowLimit: 50,
21 }),
22 }
23 );
24 const data = await res.json();
25 const map: Record<string, { clicks: number; position: number }> = {};
26 for (const row of data.rows ?? []) {
27 map[row.keys[0]] = { clicks: row.clicks, position: row.position };
28 }
29 return map;
30}
31
32export async function GET(request: NextRequest) {
33 const siteUrl = request.nextUrl.searchParams.get('siteUrl') ?? process.env.GSC_SITE_URL ?? '';
34 const days = parseInt(request.nextUrl.searchParams.get('days') ?? '28');
35 const fmt = (d: Date) => d.toISOString().split('T')[0];
36
37 const now = Date.now() - 3 * 86400000;
38 const currentEnd = new Date(now);
39 const currentStart = new Date(now - days * 86400000);
40 const prevEnd = new Date(now - days * 86400000 - 86400000);
41 const prevStart = new Date(now - days * 2 * 86400000 - 86400000);
42
43 try {
44 const token = await getGoogleAccessToken();
45 const [current, previous] = await Promise.all([
46 querySearchConsole(siteUrl, fmt(currentStart), fmt(currentEnd), token),
47 querySearchConsole(siteUrl, fmt(prevStart), fmt(prevEnd), token),
48 ]);
49
50 const keywords = Object.entries(current).map(([keyword, curr]) => {
51 const prev = previous[keyword] ?? { clicks: 0, position: 0 };
52 return {
53 keyword,
54 clicks: curr.clicks,
55 clickChange: curr.clicks - prev.clicks,
56 position: Math.round(curr.position * 10) / 10,
57 positionChange: Math.round((prev.position - curr.position) * 10) / 10,
58 };
59 }).sort((a, b) => Math.abs(b.positionChange) - Math.abs(a.positionChange));
60
61 return NextResponse.json({ siteUrl, keywords });
62 } catch (error) {
63 const message = error instanceof Error ? error.message : 'Unknown error';
64 return NextResponse.json({ error: message }, { status: 500 });
65 }
66}

Pro tip: positionChange is calculated as prevPosition - currentPosition, so a positive number means the keyword improved (moved to a lower number = higher rank). A negative positionChange means the ranking dropped. Make sure your UI labels this clearly — 'position 5 → position 8' is a drop of 3 positions.

Expected result: The comparison route returns keywords sorted by magnitude of position change. Big movers (both improvements and drops) appear at the top, making algorithm impact and content wins immediately visible.

4

Deploy and Handle OAuth Redirect Constraints

For the service account integration described in this guide, deployment is straightforward: connect Netlify via Settings → Applications → Netlify OAuth → click Publish, or use Bolt Cloud's Publish button. After deploying, add these environment variables in your hosting platform: `GOOGLE_SERVICE_ACCOUNT_JSON` (the full JSON content as a string, properly escaped), and `GSC_SITE_URL` (e.g., https://yourdomain.com). For Netlify: Site Configuration → Environment Variables. For Bolt Cloud: Secrets panel before publishing. After adding variables, trigger a redeploy. The service account approach works in Bolt's WebContainer during development because it makes outbound HTTPS calls to Google's token endpoint and API — no incoming OAuth callbacks needed. However, if you later add per-user OAuth (so individual users can connect their own Search Console accounts), that OAuth flow requires a deployed URL for the redirect URI. Bolt's WebContainer preview URL (e.g., https://[hash].local.credentialless.webcontainer-api.io/) is dynamic and cannot be registered in Google Cloud Console's Authorized redirect URIs. For per-user OAuth, always test on the deployed site, not the preview.

Bolt.new Prompt

Build a Search Console dashboard UI at app/dashboard/search-console/page.tsx. Show a date range selector (last 7 days, 28 days, 90 days) and display keyword performance data fetched from /api/gsc/keywords in a table with sortable columns. Add a tab for 'Pages' that fetches from /api/gsc/pages. Style with Tailwind.

Paste this in Bolt.new chat

app/dashboard/search-console/page.tsx
1// app/dashboard/search-console/page.tsx
2'use client';
3import { useState, useEffect } from 'react';
4
5interface KeywordRow {
6 keyword: string;
7 clicks: number;
8 impressions: number;
9 ctr: number;
10 position: number;
11}
12
13export default function SearchConsoleDashboard() {
14 const [days, setDays] = useState(28);
15 const [data, setData] = useState<KeywordRow[]>([]);
16 const [loading, setLoading] = useState(true);
17 const [error, setError] = useState('');
18
19 useEffect(() => {
20 setLoading(true);
21 fetch(`/api/gsc/keywords?days=${days}`)
22 .then((r) => r.json())
23 .then((d) => {
24 setData(d.keywords ?? []);
25 setLoading(false);
26 })
27 .catch((e) => {
28 setError(e.message);
29 setLoading(false);
30 });
31 }, [days]);
32
33 const positionColor = (pos: number) =>
34 pos <= 3 ? '#22c55e' : pos <= 10 ? '#eab308' : '#ef4444';
35
36 return (
37 <div className="p-6">
38 <h1 className="text-2xl font-bold mb-4">Search Performance</h1>
39 <div className="flex gap-2 mb-4">
40 {[7, 28, 90].map((d) => (
41 <button
42 key={d}
43 onClick={() => setDays(d)}
44 className={`px-3 py-1 rounded ${days === d ? 'bg-blue-600 text-white' : 'bg-gray-100'}`}
45 >
46 Last {d} days
47 </button>
48 ))}
49 </div>
50 {error && <p className="text-red-500">{error}</p>}
51 {loading ? (
52 <p>Loading...</p>
53 ) : (
54 <table className="w-full text-sm border-collapse">
55 <thead>
56 <tr className="bg-gray-50">
57 {['Keyword', 'Clicks', 'Impressions', 'CTR', 'Position'].map((h) => (
58 <th key={h} className="border p-2 text-left">{h}</th>
59 ))}
60 </tr>
61 </thead>
62 <tbody>
63 {data.map((row, i) => (
64 <tr key={i} className="hover:bg-gray-50">
65 <td className="border p-2">{row.keyword}</td>
66 <td className="border p-2">{row.clicks.toLocaleString()}</td>
67 <td className="border p-2">{row.impressions.toLocaleString()}</td>
68 <td className="border p-2">{row.ctr}%</td>
69 <td className="border p-2" style={{ color: positionColor(row.position) }}>
70 {row.position}
71 </td>
72 </tr>
73 ))}
74 </tbody>
75 </table>
76 )}
77 </div>
78 );
79}

Pro tip: The Search Console dashboard is an internal tool that should be password-protected before deploying publicly. Prompt Bolt: 'Add simple password protection to the /dashboard route using middleware.ts — require a PASSWORD environment variable to be entered and stored in a session cookie.'

Expected result: A functional Search Console dashboard renders with keyword data, date range tabs, and color-coded position values. Data matches what appears in the Google Search Console web interface.

Common use cases

Keyword Performance Dashboard

Display your site's top-performing search queries with clicks, impressions, average position, and CTR for any date range. Add week-over-week or month-over-month comparison to visualize SEO progress. Marketing teams use this for weekly reporting without manually exporting from Search Console.

Bolt.new Prompt

Build a search performance dashboard using Google Search Console API with service account authentication. Show my site's top 20 keywords by clicks for the last 28 days. Display each query with clicks, impressions, average position, and CTR. Add a date picker to change the lookback period. Color-code position values: green for 1-3, yellow for 4-10, red for 11+.

Copy this prompt to try it in Bolt.new

Page-Level Traffic Analysis

Analyze which pages on your site drive the most organic search traffic and which are underperforming. Fetch data grouped by page instead of by query to understand content performance at the URL level — useful for content audits and identifying pages that need optimization.

Bolt.new Prompt

Create a page performance report using Google Search Console API. Fetch my site's top 20 pages by clicks for the last 90 days with their total clicks, impressions, average position, and CTR. Display as a table with the page path (strip the domain, show just /path) sortable by any column. Highlight pages with position > 20 in orange as optimization opportunities.

Copy this prompt to try it in Bolt.new

Keyword Ranking Drop Alert Tool

Compare keyword rankings between two time periods to identify significant position drops. Enter a site URL and compare the last 7 days against the previous 7 days. Surface keywords where position has dropped by 5+ positions — a signal of potential algorithm impact, crawl issues, or content quality problems.

Bolt.new Prompt

Build a ranking change detector using Google Search Console API. Compare keyword rankings for the last 7 days vs the previous 7 days. Show keywords where average position dropped by 5 or more positions with the before/after position and click change. Sort by largest drop first. This helps me detect algorithm impacts quickly.

Copy this prompt to try it in Bolt.new

Troubleshooting

API returns 403 'The caller does not have permission'

Cause: The service account email has not been added as a user to the Search Console property, or the Search Console API is not enabled in Google Cloud Console.

Solution: In Google Search Console, go to Settings → Users and permissions, and verify the service account's client_email is listed with at least Restricted access. In Google Cloud Console, verify the Search Console API is enabled for your project. Both steps are required.

Token exchange fails with 'invalid_grant' or 'unauthorized_client'

Cause: The service account JSON private_key is malformed, the system clock is significantly skewed (JWT timestamps must be within ±5 minutes of Google's time), or the service account has been deleted or deactivated.

Solution: Re-download a fresh JSON key from Google Cloud Console. Verify system time is accurate. If storing the JSON in environment variables, ensure newlines in the private_key are properly escaped as \n and the JSON remains valid after encoding.

typescript
1// Ensure private key newlines are preserved when parsing
2const sa = JSON.parse(process.env.GOOGLE_SERVICE_ACCOUNT_JSON!.replace(/\\n/g, '\n'));

Search Console returns empty rows even though data exists in the web interface

Cause: The siteUrl parameter doesn't exactly match the property URL as registered in Search Console. The URL format (with or without trailing slash, http vs https) must be identical.

Solution: Copy the exact property URL from the Search Console property selector dropdown — including protocol and trailing slash. URL-encode it before including in the API request. Domain properties (sc-domain:example.com) use a different format than URL-prefix properties (https://example.com/).

typescript
1// For URL-prefix properties: encode the full URL
2const encoded = encodeURIComponent('https://example.com/');
3// For domain properties: use sc-domain: prefix
4const domainProperty = encodeURIComponent('sc-domain:example.com');

OAuth 2.0 flow fails with 'redirect_uri_mismatch' during testing

Cause: Bolt's WebContainer preview URL is not registered as an authorized redirect URI in Google Cloud Console. The preview URL is dynamic and cannot be pre-registered.

Solution: Deploy your Bolt app to Netlify or Bolt Cloud first. Register your deployed URL (e.g., https://myapp.netlify.app/api/auth/callback) as an Authorized redirect URI in Google Cloud Console → Credentials → OAuth 2.0 Client ID. Use the service account approach for development instead of OAuth user flow.

Best practices

  • Use a service account for internal dashboards — it's simpler than per-user OAuth and works during development in Bolt's WebContainer preview
  • Never commit the service account JSON key file to git — store it as GOOGLE_SERVICE_ACCOUNT_JSON in environment variables
  • Account for Search Console's 3-day data delay in date calculations to avoid confusing empty results for recent date ranges
  • Cache API responses for at least 6 hours — Search Console data updates daily, not in real time, and the API rate limit is generous but unnecessary polling wastes it
  • For OAuth user flow (multi-tenant apps), always test on your deployed URL since Bolt's WebContainer cannot receive OAuth callbacks
  • URL-encode the siteUrl parameter exactly as it appears in Search Console — including protocol, slashes, and case — to avoid 403 permission errors
  • Use dimensions: ['query', 'page'] to get keyword + page combinations for the most actionable SEO insights: which keyword on which page

Alternatives

Frequently asked questions

Is the Google Search Console API free?

Yes. The Search Console API has no usage cost and a rate limit of 1,200 queries per minute. The only requirement is a verified Google Search Console property and a Google Cloud project with the API enabled. This makes it the best starting point for any SEO data integration in Bolt.

Why can't I test the OAuth flow in Bolt's WebContainer preview?

OAuth 2.0 requires pre-registering redirect URIs in Google Cloud Console. Bolt's preview uses dynamic URLs that change between sessions — these cannot be registered as stable redirect URIs. For the service account integration in this guide, there's no OAuth callback, so the preview works fine. For per-user OAuth, deploy to Netlify or Bolt Cloud first, then register your deployed URL.

What's the difference between a service account and OAuth 2.0 for Search Console API?

A service account lets your server authenticate directly to Google without any user involvement — you add the service account email as a property user in Search Console. This is ideal for internal dashboards. OAuth 2.0 lets real users authorize your app to access their own Search Console data — required for multi-tenant tools where different users connect their own properties.

How much historical data does Search Console API provide?

Search Console provides 16 months of data via the API. The rowLimit parameter caps results at 25,000 rows per request, but you can paginate through all data using startRow. Note the 3-day delay for the most recent data — if you request data through yesterday, the last 2-3 days may show incomplete numbers.

Can I access Search Console data for sites I don't own?

No. Search Console data is private and only accessible to verified property owners. You must be added as a property user (either directly or as a service account user) to access the data. This is why Search Console is best for analyzing your own properties — for competitor research, use tools like SEMrush, Ahrefs, or Moz that provide third-party data estimates.

RapidDev

Talk to an Expert

Our team has built 600+ apps. Get personalized help with your project.

Book a free consultation

Need help with your project?

Our experts have built 600+ apps and can accelerate your development. Book a free consultation — no strings attached.

Book a free consultation

We put the rapid in RapidDev

Need a dedicated strategic tech and growth partner? Discover what RapidDev can do for your business! Book a call with our team to schedule a free, no-obligation consultation. We'll discuss your project and provide a custom quote at no cost.