@akson/cortex-gsc
v0.5.0
Published
Google Search Console API client and MCP server for search analytics and performance data
Maintainers
Readme
@akson/api-gsc
TypeScript client for Google Search Console API, enabling programmatic SEO monitoring, performance analysis, crawl management, and search optimization.
User Stories
Site Management Stories
As an SEO Manager, I want to programmatically add new sites to Search Console, so that I can scale SEO monitoring across multiple properties.
As a Technical SEO Specialist, I want to verify site ownership automatically, so that I can streamline the onboarding process for new domains.
As a Multi-Site SEO Director, I want to list all sites in my Search Console account, so that I can audit and manage my property portfolio.
As a Digital Marketing Manager, I want to remove outdated sites from monitoring, so that I can keep my Search Console account organized.
Performance Analysis Stories
As an SEO Analyst, I want to retrieve search performance data by query, so that I can identify keyword opportunities and trends.
As a Content Marketing Manager, I want to analyze page performance data, so that I can optimize high-potential pages for better rankings.
As an SEO Director, I want to track click-through rates and impressions over time, so that I can measure the impact of SEO optimizations.
As a Digital Marketing Analyst, I want to segment performance by device and country, so that I can optimize for different user segments and markets.
Search Analytics Stories
As a Keyword Research Specialist, I want to export query performance data, so that I can identify long-tail keyword opportunities.
As an SEO Consultant, I want to compare performance across date ranges, so that I can measure the impact of SEO changes.
As a Content Strategist, I want to identify top-performing content by search metrics, so that I can replicate successful content patterns.
As a Technical SEO Manager, I want to analyze search appearance types (rich snippets, featured snippets), so that I can optimize for SERP features.
Crawl Management Stories
As a Technical SEO Specialist, I want to monitor crawl errors, so that I can quickly identify and fix indexing issues.
As a Site Reliability Engineer, I want to track crawl statistics, so that I can optimize server resources for search bot traffic.
As an SEO Manager, I want to identify blocked resources, so that I can ensure search bots can access important site assets.
As a Web Developer, I want to monitor crawl rate changes, so that I can adjust server capacity during high-traffic periods.
Sitemap Management Stories
As a Technical SEO Manager, I want to submit sitemaps programmatically, so that I can automate sitemap updates for dynamic content sites.
As an E-commerce SEO Specialist, I want to monitor sitemap processing status, so that I can ensure product pages are being discovered.
As a Content Management System Developer, I want to automatically resubmit sitemaps after content updates, so that I can ensure fresh content gets indexed quickly.
As an SEO Consultant, I want to track sitemap errors and warnings, so that I can maintain optimal site crawlability.
URL Inspection Stories
As a Technical SEO Analyst, I want to inspect specific URLs for indexing status, so that I can troubleshoot indexing issues.
As a Content Manager, I want to request indexing for new or updated pages, so that I can expedite their appearance in search results.
As an E-commerce Manager, I want to check product page indexing status, so that I can ensure new products are discoverable.
As a Website Owner, I want to validate page accessibility for search bots, so that I can ensure my content can be properly crawled.
Mobile Usability Stories
As a UX Designer, I want to monitor mobile usability issues, so that I can prioritize mobile optimization efforts.
As a Technical SEO Specialist, I want to track mobile-first indexing status, so that I can ensure mobile versions are properly optimized.
As a Web Developer, I want to identify pages with mobile usability problems, so that I can fix responsive design issues.
Security & Spam Stories
As a Website Security Manager, I want to monitor manual actions and penalties, so that I can quickly respond to security or quality issues.
As an SEO Manager, I want to track spam or hacking notifications, so that I can protect site reputation and rankings.
As a Brand Protection Specialist, I want to monitor for security issues across multiple properties, so that I can maintain brand integrity.
Reporting & Automation Stories
As an SEO Director, I want to generate automated SEO performance reports, so that I can provide regular stakeholder updates.
As a Marketing Operations Manager, I want to integrate Search Console data with other analytics platforms, so that I can create comprehensive performance dashboards.
As a Client Services Manager, I want to automatically alert clients about significant ranking changes, so that I can provide proactive SEO support.
Competitive Analysis Stories
As an SEO Consultant, I want to analyze search visibility trends, so that I can identify competitive opportunities and threats.
As a Digital Marketing Manager, I want to track branded search performance, so that I can measure brand awareness and reputation.
As a Market Research Analyst, I want to analyze search trends by geography, so that I can identify expansion opportunities.
Installation
npm install @akson/api-gscQuick Start
import { GSCClient } from '@akson/api-gsc';
const client = new GSCClient({
config: {
siteUrl: 'https://example.com',
serviceAccount: {
keyFile: 'path/to/service-account.json',
email: '[email protected]'
}
}
});
// Authenticate and get sites
await client.authenticate();
const sites = await client.getSites();
// Get search analytics data
const searchData = await client.getSearchAnalytics({
startDate: '2025-01-01',
endDate: '2025-01-31',
dimensions: ['query', 'page'],
metrics: ['clicks', 'impressions', 'ctr', 'position']
});
// Check URL indexing status
const urlInspection = await client.inspectUrl('https://example.com/important-page');
// Submit sitemap
await client.submitSitemap('https://example.com/sitemap.xml');Configuration
Environment Variables
GSC_SITE_URL=https://example.com
GSC_SERVICE_ACCOUNT_EMAIL=gsc-service@project.iam.gserviceaccount.com
GSC_SERVICE_ACCOUNT_KEY_FILE=path/to/key.jsonConfiguration File
Create gsc-config.json:
{
"siteUrl": "https://example.com",
"serviceAccount": {
"email": "[email protected]",
"keyFile": "path/to/service-account.json"
}
}API Reference
Authentication
// Authenticate with service account
const authResult = await client.authenticate();
if (!authResult.success) {
throw new Error(authResult.error);
}Site Management
// Get all sites
const sitesResult = await client.getSites();
if (sitesResult.success) {
sitesResult.data.forEach(site => {
console.log(`Site: ${site.siteUrl}, Permission: ${site.permissionLevel}`);
});
}
// Add new site
await client.addSite('https://newsite.com');
// Delete site
await client.deleteSite('https://oldsite.com');Search Analytics
// Basic search analytics
const analyticsResult = await client.getSearchAnalytics({
startDate: '2025-01-01',
endDate: '2025-01-31',
dimensions: ['query'],
metrics: ['clicks', 'impressions', 'ctr', 'position'],
rowLimit: 1000
});
// Search analytics with filters
const filteredAnalytics = await client.getSearchAnalytics({
startDate: '2025-01-01',
endDate: '2025-01-31',
dimensions: ['query', 'page'],
filters: [
{
dimension: 'country',
operator: 'equals',
expression: 'USA'
},
{
dimension: 'device',
operator: 'equals',
expression: 'MOBILE'
}
]
});
// Performance by page
const pagePerformance = await client.getPagePerformance({
startDate: '2025-01-01',
endDate: '2025-01-31',
page: '/product-category',
metrics: ['clicks', 'impressions', 'ctr', 'position']
});
// Query performance
const queryPerformance = await client.getQueryPerformance({
startDate: '2025-01-01',
endDate: '2025-01-31',
query: 'best running shoes',
groupBy: ['page', 'device']
});URL Inspection
// Inspect URL indexing status
const inspectionResult = await client.inspectUrl('https://example.com/product/123');
if (inspectionResult.success) {
const status = inspectionResult.data;
console.log('Indexing status:', status.indexStatusResult?.verdict);
console.log('Coverage state:', status.indexStatusResult?.coverageState);
console.log('Last crawl time:', status.indexStatusResult?.lastCrawlTime);
}
// Request indexing for URL
const indexingRequest = await client.requestIndexing('https://example.com/new-product');
if (indexingRequest.success) {
console.log('Indexing requested successfully');
}Sitemap Management
// List sitemaps
const sitemapsResult = await client.getSitemaps();
if (sitemapsResult.success) {
sitemapsResult.data.forEach(sitemap => {
console.log(`Sitemap: ${sitemap.path}, Status: ${sitemap.type}`);
console.log(`Submitted: ${sitemap.submitted}, Indexed: ${sitemap.indexed}`);
});
}
// Submit sitemap
await client.submitSitemap('https://example.com/sitemap.xml');
// Delete sitemap
await client.deleteSitemap('https://example.com/old-sitemap.xml');
// Get sitemap status
const sitemapStatus = await client.getSitemapStatus('https://example.com/sitemap.xml');Mobile Usability
// Get mobile usability issues
const mobileIssues = await client.getMobileUsabilityIssues();
if (mobileIssues.success) {
mobileIssues.data.forEach(issue => {
console.log(`Issue: ${issue.issueType}`);
console.log(`Severity: ${issue.severity}`);
console.log(`Sample URLs: ${issue.sampleUrls?.length || 0}`);
});
}Messages & Manual Actions
// Get messages (manual actions, security issues, etc.)
const messagesResult = await client.getMessages();
if (messagesResult.success) {
messagesResult.data.forEach(message => {
console.log(`Message: ${message.subject}`);
console.log(`Type: ${message.messageType}`);
console.log(`Date: ${message.publishTime}`);
});
}
// Mark message as read
await client.markMessageAsRead('message-id');Error Handling
All methods return GSCOperationResult<T>:
const result = await client.getSearchAnalytics({
startDate: '2025-01-01',
endDate: '2025-01-31',
dimensions: ['query']
});
if (result.success) {
console.log('Data retrieved:', result.data.length, 'rows');
console.log('Total clicks:', result.data.reduce((sum, row) => sum + row.clicks, 0));
} else {
console.error('Error retrieving data:', result.error);
if (result.details) {
console.error('API Details:', result.details);
}
}Advanced Usage
SEO Performance Monitoring
class SEOMonitor {
constructor(private client: GSCClient) {}
async dailyPerformanceCheck() {
const yesterday = new Date();
yesterday.setDate(yesterday.getDate() - 1);
const yesterdayStr = yesterday.toISOString().split('T')[0];
const performance = await this.client.getSearchAnalytics({
startDate: yesterdayStr,
endDate: yesterdayStr,
dimensions: ['query'],
metrics: ['clicks', 'impressions', 'ctr', 'position'],
rowLimit: 100
});
if (performance.success) {
const totalClicks = performance.data.reduce((sum, row) => sum + row.clicks, 0);
const avgPosition = performance.data.reduce((sum, row) => sum + row.position, 0) / performance.data.length;
console.log(`📊 Yesterday's Performance:`);
console.log(`- Total clicks: ${totalClicks}`);
console.log(`- Average position: ${avgPosition.toFixed(1)}`);
// Alert on significant changes
if (totalClicks < 100) {
console.warn('⚠️ Low traffic alert: Less than 100 clicks yesterday');
}
}
}
async trackKeywordRankings(keywords: string[]) {
const results = [];
for (const keyword of keywords) {
const performance = await this.client.getQueryPerformance({
startDate: '2025-01-01',
endDate: '2025-01-31',
query: keyword
});
if (performance.success && performance.data.length > 0) {
results.push({
keyword,
position: performance.data[0].position,
clicks: performance.data[0].clicks,
impressions: performance.data[0].impressions
});
}
}
return results;
}
}Automated SEO Reporting
class SEOReporter {
constructor(private client: GSCClient) {}
async generateMonthlyReport(year: number, month: number) {
const startDate = new Date(year, month - 1, 1).toISOString().split('T')[0];
const endDate = new Date(year, month, 0).toISOString().split('T')[0];
// Get overall performance
const performance = await this.client.getSearchAnalytics({
startDate,
endDate,
dimensions: ['date'],
metrics: ['clicks', 'impressions', 'ctr', 'position']
});
// Get top queries
const topQueries = await this.client.getSearchAnalytics({
startDate,
endDate,
dimensions: ['query'],
metrics: ['clicks', 'impressions', 'ctr', 'position'],
rowLimit: 50
});
// Get top pages
const topPages = await this.client.getSearchAnalytics({
startDate,
endDate,
dimensions: ['page'],
metrics: ['clicks', 'impressions', 'ctr', 'position'],
rowLimit: 50
});
const report = {
period: `${year}-${month.toString().padStart(2, '0')}`,
summary: {
totalClicks: performance.success ? performance.data.reduce((sum, d) => sum + d.clicks, 0) : 0,
totalImpressions: performance.success ? performance.data.reduce((sum, d) => sum + d.impressions, 0) : 0,
avgCTR: 0,
avgPosition: 0
},
topQueries: topQueries.success ? topQueries.data.slice(0, 20) : [],
topPages: topPages.success ? topPages.data.slice(0, 20) : []
};
if (performance.success && performance.data.length > 0) {
report.summary.avgCTR = performance.data.reduce((sum, d) => sum + d.ctr, 0) / performance.data.length;
report.summary.avgPosition = performance.data.reduce((sum, d) => sum + d.position, 0) / performance.data.length;
}
return report;
}
async comparePerformance(period1: string, period2: string) {
const [start1, end1] = period1.split(' to ');
const [start2, end2] = period2.split(' to ');
const performance1 = await this.client.getSummaryData(start1, end1);
const performance2 = await this.client.getSummaryData(start2, end2);
if (performance1.success && performance2.success) {
return {
clicksChange: ((performance2.data.totalClicks - performance1.data.totalClicks) / performance1.data.totalClicks) * 100,
impressionsChange: ((performance2.data.totalImpressions - performance1.data.totalImpressions) / performance1.data.totalImpressions) * 100,
ctrChange: performance2.data.averageCTR - performance1.data.averageCTR,
positionChange: performance2.data.averagePosition - performance1.data.averagePosition
};
}
return null;
}
}Content Optimization Insights
class ContentOptimizer {
constructor(private client: GSCClient) {}
async findOptimizationOpportunities() {
// Get pages with high impressions but low CTR
const analytics = await this.client.getSearchAnalytics({
startDate: '2025-01-01',
endDate: '2025-01-31',
dimensions: ['page', 'query'],
metrics: ['clicks', 'impressions', 'ctr', 'position'],
rowLimit: 1000
});
if (!analytics.success) return [];
const opportunities = analytics.data
.filter(row => row.impressions > 100 && row.ctr < 0.02) // High impressions, low CTR
.sort((a, b) => b.impressions - a.impressions)
.map(row => ({
page: row.page,
query: row.query,
impressions: row.impressions,
ctr: (row.ctr * 100).toFixed(2) + '%',
position: row.position.toFixed(1),
opportunity: 'Low CTR despite high impressions - optimize title/meta description'
}));
return opportunities.slice(0, 20);
}
async findRankingOpportunities() {
const analytics = await this.client.getSearchAnalytics({
startDate: '2025-01-01',
endDate: '2025-01-31',
dimensions: ['page', 'query'],
metrics: ['clicks', 'impressions', 'ctr', 'position'],
rowLimit: 1000
});
if (!analytics.success) return [];
const opportunities = analytics.data
.filter(row => row.position > 10 && row.position <= 20 && row.impressions > 50) // Page 2 rankings
.sort((a, b) => b.impressions - a.impressions)
.map(row => ({
page: row.page,
query: row.query,
position: row.position.toFixed(1),
impressions: row.impressions,
opportunity: 'Page 2 ranking with decent impressions - optimize for first page'
}));
return opportunities.slice(0, 20);
}
}Site Health Monitoring
class SiteHealthMonitor {
constructor(private client: GSCClient) {}
async checkSiteHealth() {
const health = {
indexing: { status: 'unknown', issues: [] },
mobile: { status: 'unknown', issues: [] },
sitemaps: { status: 'unknown', issues: [] },
security: { status: 'unknown', issues: [] }
};
// Check for crawl errors
const crawlErrors = await this.client.getCrawlErrorsCount();
if (crawlErrors.success) {
const totalErrors = Object.values(crawlErrors.data).reduce((sum, count) => sum + count, 0);
health.indexing.status = totalErrors === 0 ? 'healthy' : 'issues';
if (totalErrors > 0) {
health.indexing.issues.push(`${totalErrors} crawl errors found`);
}
}
// Check mobile usability
const mobileIssues = await this.client.getMobileUsabilityIssues();
if (mobileIssues.success) {
health.mobile.status = mobileIssues.data.length === 0 ? 'healthy' : 'issues';
health.mobile.issues = mobileIssues.data.map(issue => issue.issueType);
}
// Check sitemaps
const sitemaps = await this.client.getSitemaps();
if (sitemaps.success) {
const hasErrors = sitemaps.data.some(sitemap => sitemap.errors && sitemap.errors > 0);
health.sitemaps.status = !hasErrors ? 'healthy' : 'issues';
if (hasErrors) {
health.sitemaps.issues = sitemaps.data
.filter(s => s.errors && s.errors > 0)
.map(s => `${s.path}: ${s.errors} errors`);
}
}
// Check for security issues
const messages = await this.client.getMessages();
if (messages.success) {
const securityMessages = messages.data.filter(m =>
m.messageType === 'SECURITY_AND_MANUAL_ACTION'
);
health.security.status = securityMessages.length === 0 ? 'healthy' : 'critical';
health.security.issues = securityMessages.map(m => m.subject);
}
return health;
}
}Integration Examples
SEO Dashboard Integration
import { GSCClient } from '@akson/api-gsc';
import { createDashboard } from './dashboard';
async function updateSEODashboard() {
const client = new GSCClient();
await client.authenticate();
const thirtyDaysAgo = new Date();
thirtyDaysAgo.setDate(thirtyDaysAgo.getDate() - 30);
const performance = await client.getSearchAnalytics({
startDate: thirtyDaysAgo.toISOString().split('T')[0],
endDate: new Date().toISOString().split('T')[0],
dimensions: ['date'],
metrics: ['clicks', 'impressions', 'ctr', 'position']
});
if (performance.success) {
await createDashboard({
totalClicks: performance.data.reduce((sum, d) => sum + d.clicks, 0),
totalImpressions: performance.data.reduce((sum, d) => sum + d.impressions, 0),
avgCTR: (performance.data.reduce((sum, d) => sum + d.ctr, 0) / performance.data.length) * 100,
avgPosition: performance.data.reduce((sum, d) => sum + d.position, 0) / performance.data.length
});
}
}Alert System
class SEOAlertSystem {
constructor(private client: GSCClient, private thresholds: any) {}
async checkAlerts() {
const alerts = [];
// Check for traffic drops
const lastWeek = await this.getWeeklyData(-1);
const previousWeek = await this.getWeeklyData(-2);
if (lastWeek && previousWeek) {
const trafficChange = ((lastWeek.clicks - previousWeek.clicks) / previousWeek.clicks) * 100;
if (trafficChange < this.thresholds.trafficDropPercent) {
alerts.push({
type: 'traffic_drop',
severity: 'high',
message: `Traffic dropped by ${Math.abs(trafficChange).toFixed(1)}% last week`,
data: { lastWeek: lastWeek.clicks, previousWeek: previousWeek.clicks }
});
}
}
// Check for ranking drops
const rankingIssues = await this.checkRankingDrops();
alerts.push(...rankingIssues);
return alerts;
}
private async getWeeklyData(weeksAgo: number) {
const endDate = new Date();
endDate.setDate(endDate.getDate() + (weeksAgo * 7));
const startDate = new Date(endDate);
startDate.setDate(startDate.getDate() - 6);
const result = await this.client.getSummaryData(
startDate.toISOString().split('T')[0],
endDate.toISOString().split('T')[0]
);
return result.success ? result.data : null;
}
}TypeScript Support
Full TypeScript definitions included:
import type {
GSCSite,
GSCSearchAnalyticsResponse,
GSCPerformanceData,
GSCSitemap,
GSCUrlInspectionResponse,
GSCMobileUsabilityIssue,
GSCMessage,
GSCOperationResult,
GSCListResult
} from '@akson/api-gsc';Requirements
- Node.js ≥18.0.0
- Google Search Console API access
- Service account with Search Console permissions
- Verified site ownership in Google Search Console
License
MIT
