Periscope — SEO Data Tooling logo

Periscope — SEO Data Tooling

A TypeScript CLI that unifies Google Search Console, GA4, Bing Webmaster Tools, and Google Ads Keyword Planner into a single local snapshot for editorial and landing-page decisions.


What it is

Periscope is a command-line tool for SEO research across websites and client projects. It unifies data from Google Search Console, Google Analytics 4, Bing Webmaster Tools, and the Google Ads API (Keyword Planner) into a single local snapshot, so editorial and landing-page decisions can be made from one source instead of toggling between four dashboards.

How Periscope uses the Google Ads API

Periscope calls the Google Ads API solely to retrieve keyword ideas and historical search-volume metrics from the Keyword Planner. Specifically, it uses the KeywordPlanIdeaService and invokes exactly two methods, both read-only.

POST generateKeywordIdeas

Description: Executes seed-based keyword expansion. Returns an adjacent keyword universe suggested by Google Ads based on provided seed keywords, a seed URL, or both.

HTTP Request: POST /v21/customers/{customerId}:generateKeywordIdeas

Required Headers:

  • Authorization: Bearer <oauth-access-token> (or service-account access token in the legacy fallback path)
  • developer-token: <google-ads-developer-token>
  • login-customer-id: <manager-customer-id>
  • Content-Type: application/json

Request Body (JSON): Provide exactly one of the following seed configurations:

{
  "keywordSeed": {
    "keywords": ["<string>"]
  },
  "urlSeed": {
    "url": "https://<string>"
  },
  "keywordAndUrlSeed": {
    "keywords": ["<string>"],
    "url": "https://<string>"
  }
}

POST generateKeywordHistoricalMetrics

Description Retrieves historical performance data—including average monthly search volume, competition tier, and CPC bid ranges—for a specified array of keywords.

HTTP Request POST /v21/customers/{customerId}:generateKeywordHistoricalMetrics

Headers

  • Authorization: Bearer <oauth-access-token> (or service-account access token in the legacy fallback path)
  • developer-token: <google-ads-developer-token>
  • login-customer-id: <manager-customer-id>
  • Content-Type: application/json

Path Parameters

  • customerId (string): The unique identifier for the target customer account.

Request Payload

{
  "keywords": ["term one", "term two"]
}

Output

ℹ️

Read-only by design. No other Google Ads API service is called. Periscope does not create, read, update, or delete campaigns, ad groups, ads, budgets, conversions, audiences, or billing data. It performs zero mutations of any kind on the Google Ads account.

CLI commands that consume the Ads API

CommandUse of Ads API
periscope snapshotPulls Keyword Planner volume + competition for the configured keyword set
periscope discover topicsSeeds Keyword Planner with category names + top GSC queries to surface gaps
periscope audit articlesCompares MDX article keywords against Keyword Planner metrics
periscope validate lpsValidates /lp/* landing pages against Keyword Planner demand
periscope probe <url>One-shot URL-seeded keyword ideas for a competitor URL
periscope doctor adsDiagnostic that verifies credentials and API reachability
periscope auth adsOne-time OAuth setup helper. Mints a refresh token via the standard Desktop-app loopback flow and prints credentials to stdout for the operator to place in their preferred env manager.

Authentication

Periscope authenticates to the Google Ads API via OAuth 2.0 user-flow with a developer token. The operator runs periscope auth ads once on their local machine; the tool mints a refresh token via the standard Desktop-app loopback redirect flow and prints the three resulting env vars to stdout for the operator to place in their preferred env manager (a .env file, a shell profile, 1Password CLI, Doppler — the tool does not assume).

The same operator-side credentials are then read from environment variables on every CLI invocation:

  • GOOGLE_ADS_DEVELOPER_TOKEN — the Basic-access developer token issued under the operator's MCC
  • GOOGLE_ADS_CLIENT_ID — OAuth 2.0 client ID (Desktop application type)
  • GOOGLE_ADS_CLIENT_SECRET — OAuth 2.0 client secret
  • GOOGLE_ADS_REFRESH_TOKEN — refresh token, minted once by periscope auth ads
  • GOOGLE_ADS_LOGIN_CUSTOMER_ID — the operator's manager (MCC) customer ID
  • GOOGLE_ADS_CUSTOMER_ID — the operating customer (typically a child of the MCC)

A legacy service-account fallback is preserved for environments without a domain-policy constraint (Workspace MCCs, test accounts): when GSC_SERVICE_ACCOUNT_KEY_PATH or GSC_SERVICE_ACCOUNT_JSON is set instead of the three OAuth vars above, periscope authenticates as the service account. Personal-Gmail MCCs reject service-account emails at the Ads API layer (an "allowed domains" policy that the UI tolerates but the API enforces), which is why OAuth user-flow is the primary path in v1.3+.

The OAuth user flow authenticates the operator as themselves against their own Google Ads account. The tool has no backend, no third-party identity broker, no end-user consent surface — it is local CLI tooling for a single operator at a time. No credentials are transmitted anywhere other than Google's own OAuth + Ads API endpoints.

Data handling

  • Scope: Read-only access to Keyword Planner data for the developer's own Manager-account-linked accounts.
  • Storage: Responses are written to a local outputDir on the developer's machine as JSON and Markdown snapshots. Nothing is uploaded to any third-party service.
  • Sharing: Data is not shared with, sold to, or exposed to any third party. There are no end users.
  • Retention: Snapshots are retained locally at the developer's discretion and can be deleted at any time.
  • Compliance: Use complies with the Google Ads API Terms of Service, including the Required Minimum Functionality requirements for tools that surface Keyword Planner data.

Call volume

Call volume is low — on the order of dozens to low hundreds of requests per day, invoked manually from the CLI during editorial reviews. There is no automated continuous polling.

Architecture

Language and build

TypeScript in strict mode. Compiled to ESM via tsup with typed snapshot JSON contracts. Targets Node 22+; the published artifact is the dist/ directory plus the README.

CLI

Built on commander, with nested subcommands and a per-command --help that includes copy-pasteable examples. The entry point is a single bin/periscope that resolves to a small bundled script.

Engine modules

One file per upstream API under src/engines/:

  • gsc.ts — Google Search Console
  • ga4.ts — Google Analytics 4
  • bing.ts — Bing Webmaster Tools
  • ads.ts — Google Ads Keyword Planner

Each engine takes its config explicitly (site URL, property id, API key, bot regions) rather than reading env vars itself. The orchestrator command reads env vars and a periscope.config.mjs from the consumer repo and threads them through. That's what makes the same package usable across any number of consumer projects, each with its own config.

Shared library

src/lib/* holds the cross-cutting pieces:

  • Auth resolver: OAuth user-flow (primary) and service-account JWT (fallback) for Google Ads; service-account JWT for GSC + GA4
  • Ads error classifier (collapses ~20 raw error codes into 8 actionable categories)
  • Seed-phrase composition and topical filtering for keyword ideas
  • Bucket math for keyword volume tiers
  • Snapshot markdown renderer
  • Config loader (zod-validated)
  • ANSI color helpers
  • Snapshot store (load / write / list)
  • Natural-language date resolver (for the diff command)

Tests

159 unit tests on vitest covering bucket math, config schema, frontmatter parser, color helpers, diagnostics, snapshot ref resolution, Ads auth resolution, error classification, retry backoff, error-budget tripping, keyword-ideas cache, GSC-first verdict scoring, and the OAuth + seed-phrase work added in v1.3 – v1.4. No live API in tests — engines are mocked at the HTTP boundary.

Distribution

Private npm package @anthonycoffey/periscope on GitHub Packages, published via a tag-triggered GitHub Actions workflow. Tags follow v* (semver from 1.0.0); consumers pin with ^1.0.0 and pick up minors with npm update.

Command surface

CommandDescription
periscope snapshot --window=180 --asof=YYYY-MM-DDPull a multi-engine snapshot using defaults.
periscope diff yesterdayDiff the latest snapshot against yesterday's
periscope diff 7dDiff the latest snapshot against ~7 days ago.
periscope diff "last month"Diff the latest snapshot against ~30 days ago.
periscope diff 2026-05-10 2026-05-17Diff two explicit snapshot dates.
periscope discover topicsSurface editorial topic backlog from Ads + GSC.
periscope audit articlesFlag existing articles whose keyword fit has drifted.
periscope validate lpsAudit landing pages against target keywords.
periscope probe <url>Competitor URL probe via Ads Keyword Planner.
periscope doctor adsDiagnose Google Ads API credentials and access.

What's next

  • An optional Ahrefs engine as a fifth data source (SPEC-022)
  • Topic-gap clustering across articles (SPEC-025)
  • Week-over-week movement diff (SPEC-026)
  • Guided multi-property onboarding flow (SPEC-028)

Source Code