# Agents (https://projects.piyushgambhir.com/gsc-cli/docs/agents)
`gsc` is built for coding agents as much as for people. Any agent or harness that can run shell commands
can use it; nothing else is needed.
## Ground rules [#ground-rules]
* Run commands with `-o json --no-input`. Data is on stdout; notes and structured errors are on stderr.
* Use `--read-only` unless the user asked for a change. It blocks every command that changes Search
Console, local credentials, or the binary itself. `query` and `inspect` send POST requests but only
read, so they still work.
* Run writes (`sites add`, `sites remove`, `sitemaps submit`, `sitemaps delete`) only when the user asked,
and with `--dry-run` first to show the request. Destructive commands also need `--yes` under
`--no-input`; pass it only for the exact change the user asked for.
* Check `gsc auth status -o json` first; it reads local configuration only. If nothing is logged in, ask
the user to run `gsc auth login`. Never invent credentials. In CI, `GSC_CREDENTIALS` or
`GSC_ACCESS_TOKEN` may already be set.
```bash
# A safe, non-interactive read for an agent
gsc --read-only top queries --last 28d --limit 20 -o json --no-input
```
## Discover commands [#discover-commands]
`gsc --help` and `gsc COMMAND --help` list every command and flag, and the
[command reference](https://projects.piyushgambhir.com/gsc-cli/docs/reference/commands) has all of them on one page. For an API method without a
dedicated command, `gsc api METHOD PATH` sends an authenticated request with the same redaction and error
handling. `GET` and the two read-only `POST`s (`searchAnalytics/query` and `urlInspection/index:inspect`)
count as reads; every other request is a write, blocked by `--read-only` and printed instead of sent with
`--dry-run`.
## Automation flags [#automation-flags]
| Flag | Environment | Effect |
| -------------- | ----------------- | -------------------------------------------------------------------------------- |
| `--read-only` | `GSC_READ_ONLY=1` | Blocks remote writes, local credential changes, and self-update |
| `--no-input` | `GSC_NO_INPUT=1` | Never prompts or opens a browser; commands fail instead |
| `--dry-run` | | Prints write requests and sends nothing; `--read-only` still refuses the command |
| `--yes` | | Confirms destructive commands without a prompt |
| `--quiet` | `GSC_QUIET=1` | Drops informational notes from stderr |
| `--verbose` | `GSC_VERBOSE=1` | Logs method, URL, and status to stderr (never tokens or bodies) |
| `--site`, `-s` | `GSC_SITE` | Property for this command |
| `--profile` | `GSC_PROFILE` | Saved profile for this command |
Agents never see the update notice: its once-a-day GitHub check runs only when stderr is a terminal, and
`GSC_NO_UPDATE_NOTIFIER=1` turns it off everywhere. `gsc update --check -o json` reports the current and
latest versions.
## Reporting results honestly [#reporting-results-honestly]
* Take totals from `gsc performance`. Never sum query or page rows into site totals: anonymized queries
are withheld and at most 50,000 rows per day are exposed.
* Run `gsc freshness` before trusting the last few days. Never present `--data-state all` numbers as
final.
* `complete: true` only means pagination ended. With `--compare`, `null` means a row was not returned
for that period; treat it as unknown, not zero.
* `gsc inspect` reports Google's indexed version, not a live test, and cannot request indexing. It costs
quota (2,000 per property per day), so inspect only the URLs you need and use `--max`.
* Search Analytics has unpublished load quotas. Prefer narrower date ranges, avoid grouping by page and
query together over long ranges, and do not retry quota errors in a loop.
[Reading the data](https://projects.piyushgambhir.com/gsc-cli/docs/data) covers each of these in detail.
## Agent skill and LLM files [#agent-skill-and-llm-files]
* Install the agent skill with `npx skills add piyush-gambhir/gsc-cli@gsc` (Claude Code, Codex, Cursor,
and other agents that read `~/.agents/skills`), or copy the
[`gsc/`](https://github.com/piyush-gambhir/gsc-cli/tree/main/gsc) directory into your agent's skills
directory. It bundles the command and auth references, so it works on its own.
* `gsc commands -o json` describes every command offline: usage, flags and defaults, whether it reads or
writes (`read`, `remote_write`, `local_write`), whether it may prompt, the API methods it exists to call,
and examples. A bare host given to `-s`, `--verify`, `doctor --online`, and login also list properties
once (`webmasters.sites.list`). `gsc api methods -o json` is the same map from the API side.
* Failures exit `1` with a structured error whose `kind` (`auth`, `permission`, `not_found`,
`rate_limit`, `usage`, `read_only`, `confirmation_required`, ...) says what to do next; see
[Output formats](https://projects.piyushgambhir.com/gsc-cli/docs/output-formats#errors).
* [`llms.txt`](https://projects.piyushgambhir.com/gsc-cli/llms.txt) indexes these docs, and
[`llms-full.txt`](https://projects.piyushgambhir.com/gsc-cli/llms-full.txt) contains all of them,
including the full command reference.
# Authentication (https://projects.piyushgambhir.com/gsc-cli/docs/authentication)
Search Console data belongs to a Google account, so every request needs an OAuth access token. API keys
do not work for this API, and Google's device-code flow does not allow Search Console scopes. `gsc`
supports every other way to get a token.
| Method | Command | Best for | What is stored |
| ------------------------------- | -------------------------------------------------------- | ---------------------------------------------------------- | ----------------------------------------------- |
| Browser login (default) | `gsc auth login` | People on a machine with a browser | Refresh token in the OS keychain |
| Headless browser login | `gsc auth login --no-browser` | SSH sessions, containers | Same |
| Your own OAuth client | `gsc auth login --client-secret-file client_secret.json` | Teams wanting their own quota or branding | Refresh token and client secret in the keychain |
| Service account | `gsc auth login --service-account key.json` | CI, servers | Path to the key file (no secret copied) |
| Application Default Credentials | `gsc auth login --adc` | gcloud users, GitHub Actions federation, GCE and Cloud Run | Nothing |
| Impersonation | `gsc auth login --adc --impersonate SA_EMAIL` | Keyless CI, least privilege | Nothing |
| Credentials file per command | `--credentials FILE` or `GSC_CREDENTIALS` | One-off automation | Nothing |
| Raw access token | `--access-token` or `GSC_ACCESS_TOKEN` | Scripts, debugging | Nothing |
## Credential precedence [#credential-precedence]
Every command uses the first of: `--access-token` / `GSC_ACCESS_TOKEN`, then `--credentials` /
`GSC_CREDENTIALS`, then the selected profile. Profiles are chosen by `--profile`, then `GSC_PROFILE`,
then the saved default. `gsc` never falls back to ambient Application Default Credentials on its own, so
an agent never runs as an identity nobody chose.
## Browser login [#browser-login]
```console
$ gsc auth login
Opening your browser to sign in. If it does not open, visit:
https://accounts.google.com/o/oauth2/v2/auth?...
Logged in as me@example.com (profile "default").
Found 3 properties. Default site set to sc-domain:example.com (change with: gsc sites use SITE).
```
1. `gsc` listens on `127.0.0.1` on a free port and opens Google's consent page (PKCE S256, a random
`state`, offline access). The URL is printed to stderr in case the browser does not open.
2. Approve access on Google's consent screen. The tab then confirms you are signed in; close it and
return to the terminal.
3. The refresh token is saved in the OS keychain (macOS Keychain, Windows Credential Manager, or the
Secret Service on Linux). Access tokens refresh automatically from then on. Keychain entries are
namespaced by config file, so same-named profiles in different configs (`GSC_CONFIG` or a custom
`XDG_CONFIG_HOME`) never overwrite each other.
4. `gsc` lists your properties once and picks a default site: the only property, or your choice from
a list. When it cannot ask (for example a service account or ADC login with `--no-input`), it picks
the first domain property. `--no-verify` skips this call.
`gsc` asks Google for Search Console access (`https://www.googleapis.com/auth/webmasters`) plus `openid`
and `email`, which let it show the signed-in account. What it does with that access is in the
[privacy policy](https://projects.piyushgambhir.com/gsc-cli/privacy).
Options:
* `--profile NAME` saves under a named profile (default: the current profile, or `default`).
* `--scope readonly` requests `webmasters.readonly` only. Write commands then stop early with a message.
The default `full` scope covers every command; `--read-only` still blocks writes for agents.
* `--insecure-storage` keeps the token in `secrets.yaml` (mode `0600`) next to the config instead of
the keychain. Google's OAuth policy asks for encrypted storage, so use it only where no keychain
exists. Without the flag, a missing keychain stops the login (and asks first when you are at a
terminal).
Run `gsc auth login` again any time Google revokes access; errors say exactly which command to run.
The built-in OAuth client is added to release builds when they are built; `gsc version -o json` reports
`builtin_oauth_client` for the binary you have. Everyone using it shares one Google Cloud project's
quota, so heavy users should bring their own client or a service account.
## Headless login [#headless-login]
For a machine without a browser:
1. Run `gsc auth login --no-browser` and open the printed URL on any device.
2. Approve access. The browser then tries to load `http://127.0.0.1:8085/?code=...` and fails. That is
expected.
3. Copy the full address from the browser and paste it into the terminal.
This uses the same loopback redirect and PKCE as the normal flow. It is not Google's retired out-of-band
(copy-a-code) flow.
## Your own OAuth client [#your-own-oauth-client]
1. In Google Cloud Console, create or pick a project and enable the **Google Search Console API**.
2. Configure the OAuth consent screen (External or Internal), then create an OAuth client of type
**Desktop app** and download its JSON.
3. Run `gsc auth login --client-secret-file client_secret_XXXX.json`.
Alternatively set `GSC_CLIENT_ID` and `GSC_CLIENT_SECRET`, which override the built-in client for every
login. Each profile records which client it used, so refreshes always use the same one. Keep the consent
screen **In production**: Google expires refresh tokens after 7 days for apps left in Testing.
## Service accounts [#service-accounts]
1. In Google Cloud Console, enable the Search Console API, create a service account, and create a JSON
key.
2. In Search Console, open the property, then **Settings > Users and permissions > Add user**, and add
the service account email (`...@...iam.gserviceaccount.com`) with **Full** permission (Owner only if
you need owner-only actions).
3. Run `gsc auth login --profile ci --service-account /path/key.json`.
`gsc` stores the key file path, not the key. Google Workspace admins can use domain-wide delegation with
`--subject user@yourdomain.com`; the impersonated user must have access to the property.
For a single command without a profile: `GSC_CREDENTIALS=/path/key.json gsc performance -s ...`.
## Application Default Credentials and impersonation [#application-default-credentials-and-impersonation]
`gsc auth login --adc` saves a profile that resolves Google's Application Default Credentials when a
command runs: `GOOGLE_APPLICATION_CREDENTIALS`, the gcloud ADC file, workload identity federation, or the
GCE and Cloud Run metadata server.
gcloud's own client is meant for Google Cloud scopes, so log in with your Desktop client and the Search
Console scope:
```bash
gcloud auth application-default login \
--client-id-file=client_secret.json \
--scopes=https://www.googleapis.com/auth/cloud-platform,https://www.googleapis.com/auth/webmasters.readonly
gsc auth login --profile gcloud --adc
```
Keyless CI (for example GitHub Actions with `google-github-actions/auth`): give the federated identity
`roles/iam.serviceAccountTokenCreator` on a service account, add that service account to the Search
Console property, then:
```bash
gsc auth login --profile ci --adc --impersonate reporting@my-project.iam.gserviceaccount.com --no-verify
```
## Raw access tokens [#raw-access-tokens]
`GSC_ACCESS_TOKEN=$(gcloud auth print-access-token) gsc sites list` works with any token that carries a
Search Console scope. Nothing is stored and nothing refreshes. `gsc auth token` prints the current
profile's token for debugging; treat it as a secret.
## Status, profiles, and logout [#status-profiles-and-logout]
```bash
gsc auth status # which credential is in use, from local config only
gsc auth status --verify # also lists properties once
gsc auth list # profiles, without secrets
gsc auth use work # switch the default profile
gsc auth logout # delete this profile and its token locally
gsc auth logout --revoke # also revoke at Google (asks; --yes to skip)
```
`--revoke` is opt-in because Google revokes the account's tokens for every client in the OAuth project,
which signs out every other machine and profile using the same client. You can also remove access from
your Google Account at [myaccount.google.com/permissions](https://myaccount.google.com/permissions).
`--read-only` blocks every command that changes local credentials or configuration, as well as remote
writes; tokens refreshed in read-only mode stay in memory. `gsc login` and `gsc status` are aliases of
`gsc auth login` and `gsc auth status`, and `gsc config show`, `config list-profiles`, and
`config use-profile` match the other CLIs in the suite.
## Troubleshooting [#troubleshooting]
| Message | Fix |
| ----------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------ |
| `this build has no built-in OAuth client` | Use a release build, set `GSC_CLIENT_ID` and `GSC_CLIENT_SECRET`, or pass `--client-secret-file` |
| `the OS keychain is unavailable` | Start a Secret Service (for example gnome-keyring), re-run login with `--insecure-storage`, or use `GSC_ACCESS_TOKEN` or `GSC_CREDENTIALS` |
| `OS keychain did not respond within 10s` | Unlock the keychain and retry |
| `Google rejected the saved credentials (invalid_grant ...)` | Run `gsc auth login --profile NAME`. Causes: revoked access, a password change, or a Testing-mode client |
| `The signed-in identity lacks access to this property` | Check `gsc sites list`, or add the user or service account in Search Console |
| `Enable the Google Search Console API ...` | Enable the API in the OAuth client's Google Cloud project |
| `another gsc is on PATH` (doctor) | Ghostscript and Gambit Scheme also install `gsc`. Put this binary's directory first on `PATH`, or alias it |
# Bulk export (https://projects.piyushgambhir.com/gsc-cli/docs/commands/export)
```bash
gsc export --last 16m -d query -d page --out ./gsc-export
gsc export --start 2026-01-01 --end 2026-03-31 -d page --format ndjson --out ./q1
```
`gsc export` splits the range into days, as Google's extraction guide recommends, fetches every row Google
exposes for each day, and writes one file per day (`2026-01-01.csv`, or `.ndjson` with
`--format ndjson`) to the `--out` directory. Each file is written to a temporary file first and renamed
into place, so an interrupted day never leaves a partial file behind.
## Resuming [#resuming]
`manifest.json` in the output directory records the request and the days already written. Run the same
command again to pick up where it stopped.
* Days after the latest final date, and days Google marks incomplete, are recorded as preliminary and
fetched again on the next run.
* If the manifest was written for a different request (another property, dimensions, filters, data
state, or format), `gsc export` stops instead of overwriting it. Pass `--restart` to discard it, or
choose a new `--out` directory.
## Pacing and retries [#pacing-and-retries]
Each day's fetch starts at least `--interval` (default `250ms`) after the previous one, to stay under the
per-property quota; the pages within one day are fetched back to back.
There are no automatic retries by default: an error stops the export, and rerunning resumes it.
`--retry N` retries quota and server errors up to N times per day with backoff, logging each retry on
stderr.
## Options [#options]
`export` takes the same dimensions, filters, search type, data state, and aggregation flags as
[`gsc query`](https://projects.piyushgambhir.com/gsc-cli/docs/commands/query). `--data-state final` is the default, so files hold final data
unless you ask for preliminary days.
`export` only writes the files you asked for and changes nothing in Search Console, so it also runs under
`--read-only`.
Each day is capped by Google at 50,000 rows per search type, and anonymized queries are never
included. Totals come from `gsc performance`, not from summing exported rows.
# Overview (https://projects.piyushgambhir.com/gsc-cli/docs/commands)
Every command has `-h`/`--help`, and every command that returns data accepts `-o json|yaml`, plus
`-o csv` for row-shaped results. The [command reference](https://projects.piyushgambhir.com/gsc-cli/docs/reference/commands) lists every flag;
these pages show how the groups fit together.
## Groups [#groups]
## Global flags [#global-flags]
| Flag | Effect |
| ----------------------------------- | --------------------------------------------------------------------------------------------- |
| `-s`, `--site` | Property: `sc-domain:example.com`, `https://www.example.com/`, or a bare host (or `GSC_SITE`) |
| `--profile` | Saved profile (or `GSC_PROFILE`) |
| `-o`, `--output` | `table` (default), `json`, `yaml`, or `csv` |
| `--read-only` | Block remote writes, local credential changes, and self-update |
| `--dry-run` | For write commands: print the request and send nothing |
| `--yes` | Confirm destructive commands without a prompt |
| `--no-input` | Never prompt or open a browser |
| `-q`, `--quiet` / `-v`, `--verbose` | Fewer notes, or a log line per request, on stderr |
| `--timeout` | HTTP request timeout (default `30s`) |
| `--access-token`, `--credentials` | Credential overrides; prefer `GSC_ACCESS_TOKEN` or `GSC_CREDENTIALS` |
The property comes from `--site`, then `GSC_SITE`, then the profile's default site (set at login or with
`gsc sites use`).
## Account and setup [#account-and-setup]
```bash
gsc auth status # also: login, list, use, logout, token
gsc doctor --online # local checks plus one API call
gsc update --check
gsc completion zsh > "${fpath[1]}/_gsc"
```
## Any API method [#any-api-method]
`gsc api` sends an authenticated request to the Search Console API, so a method Google adds later is
usable the day it ships. `PATH` is relative to `https://searchconsole.googleapis.com` and must already
be URL-escaped:
```bash
gsc api GET /webmasters/v3/sites
gsc api POST /webmasters/v3/sites/sc-domain%3Aexample.com/searchAnalytics/query --data '{"startDate":"2026-09-01","endDate":"2026-09-30"}'
gsc api PUT /webmasters/v3/sites/sc-domain%3Aexample.com/sitemaps/https%3A%2F%2Fexample.com%2Fsitemap.xml --dry-run
```
`GET` and the two read-only `POST`s are reads. Every other request is treated as a write: blocked by
`--read-only`, printed instead of sent with `--dry-run`, and confirmed (or `--yes`) before it is sent.
`--data` takes JSON, `@file`, or `-` for stdin.
# Insights (https://projects.piyushgambhir.com/gsc-cli/docs/commands/insights)
Each insight runs ordinary Search Analytics queries and computes the result locally. The thresholds it
used are printed on stderr and included in JSON output, along with a note on how the result was
computed, and the main thresholds have flags. Results describe the rows Google exposes; anonymized queries
are never included.
All insights take `--last` (default `28d`), `--start`, `--end`, `--type`, `--filter`, `--data-state`,
`--aggregation`, and `--limit`.
## Striking distance [#striking-distance]
```bash
gsc insights striking-distance --last 28d
gsc insights striking-distance --min-position 8 --max-position 15 --min-impressions 500
```
Queries whose average position is between `--min-position` (default 4) and `--max-position` (default
20\), with at least `--min-impressions` (default 100), sorted by impressions. Average position is per
query across all pages.
## Low CTR [#low-ctr]
```bash
gsc insights low-ctr --factor 0.5 --min-impressions 200
```
Queries whose CTR is below `--factor` (default 0.5) of this site's median CTR for the same position
bucket. Buckets are positions 1 to 10 one by one (rounded), then 11 to 20, then 21 and beyond; a
bucket with fewer than three qualifying queries is skipped. `missed_clicks_estimate` is impressions times the gap to the
median: an estimate, not a forecast.
## Cannibalization [#cannibalization]
```bash
gsc insights cannibalization --min-share 0.2
```
Queries with at least `--min-impressions` (default 100) where two or more pages each have at least
`--min-share` (default 0.1) of the query's impressions. Rows list one competing page each, grouped by
query, with that page's `share`.
## Decliners [#decliners]
```bash
gsc insights decliners --by page --compare yoy
```
Queries (or pages with `--by page`) that lost the most clicks against the `--compare` period (`previous`
by default, or `yoy`), among rows with at least `--min-clicks` (default 5) in the comparison period. Only
rows returned in both periods count: a row missing from one period is left out rather than treated as
zero.
## New and lost queries [#new-and-lost-queries]
```bash
gsc insights new-queries --compare previous
gsc insights lost-queries --by page
```
`new-queries` lists rows returned now but not in the comparison period; `lost-queries` lists the reverse.
Both describe exposure within the fetched rows, not rankings: a query can drop out of the returned rows
without losing its position.
# URL Inspection (https://projects.piyushgambhir.com/gsc-cli/docs/commands/inspection)
```bash
gsc inspect https://www.example.com/pricing
gsc inspect --file urls.txt -o json
cat urls.txt | gsc inspect --file - --max 200
```
`gsc inspect` calls the URL Inspection API for each URL. The table shows the verdict, coverage state,
indexing state, last crawl time, page fetch state, robots.txt state, Google-selected and user-declared
canonicals, the crawler used, and rich results. JSON returns Google's full result for each URL.
URL Inspection reports the version Google has **indexed**, as last crawled. It is not a live test of
the page as it is now, and the API cannot request indexing.
## Batches and quota [#batches-and-quota]
Google allows 2,000 inspections per property per day and 600 per minute. `gsc inspect`:
* reads URLs from arguments, or from `--file` (one per line, `-` for stdin), and skips duplicates;
* inspects at most `--max` URLs (default 2,000, the daily quota) and warns when it drops the rest;
* pauses `--interval` between inspections (default `120ms`, which stays under 600 per minute);
* records an error for a single URL and moves on, but stops cleanly when a quota is reached, keeping the
results it has.
Inspect only the URLs you need: every inspection counts against the property's quota.
## Language [#language]
`--language` sets the language of issue messages as a BCP 47 code (default `en-US`):
```bash
gsc inspect https://www.example.com/de/preise --language de-DE
```
# Search performance (https://projects.piyushgambhir.com/gsc-cli/docs/commands/performance)
```bash
gsc performance --last 28d --compare previous # totals, and the change from the 28 days before
gsc top queries --last 3m --limit 50 # top rows for one dimension
gsc trend --last 16m --by month # a time series
gsc freshness # the latest final and preliminary dates
```
## Totals [#totals]
`gsc performance` runs a query with no dimensions, so its clicks, impressions, CTR, and average position
match the Performance report chart, anonymized queries included. Use it whenever you need site totals.
```bash
gsc performance --type discover --last 3m -o json
gsc performance --filter 'page ~ /blog/' --compare yoy
```
## Top rows [#top-rows]
`gsc top` groups by one dimension and returns the top rows by clicks, 25 by default:
```bash
gsc top queries
gsc top pages --limit 100
gsc top countries # ISO 3166-1 alpha-3 codes, such as usa and ind
gsc top devices
gsc top appearance # search appearance types
```
`searchAppearance` cannot be grouped with other dimensions, so `top appearance` lists the values first.
To break one down further, filter on it with `gsc query --filter 'searchAppearance = VALUE'`.
## Trends [#trends]
`gsc trend` reports clicks, impressions, CTR, and position over time:
```bash
gsc trend --last 3m --by week
gsc trend --by hour --last 2d # hourly, preliminary data, last 10 days
gsc trend --last 16m --by month --filter 'page ~ /blog/'
```
`--by date` (the default) and `--by hour` come from Google. `--by week` (ISO weeks) and `--by month` are
rolled up locally from daily rows: clicks and impressions are summed, CTR is recomputed from the sums,
and position is weighted by impressions. The output says so.
## Freshness [#freshness]
```bash
gsc freshness
gsc freshness --type news -o json
```
Two small date-grouped queries over the last 10 days, one for final data and one including preliminary
data. Final data usually lags two to three days; check this before reading the last few days.
## Periods and filters [#periods-and-filters]
These flags work the same on `performance`, `top`, `trend`, `query`, `export`, and the insights:
| Flag | Meaning |
| ------------------ | --------------------------------------------------------------------------------------------- |
| `--last` | `7d`, `28d` (default), `4w`, `3m`, `16m`: a window ending at the latest available date |
| `--start`, `--end` | Inclusive dates, `YYYY-MM-DD`, in Pacific Time |
| `--type` | `web` (default), `image`, `video`, `news`, `discover`, or `googleNews` |
| `--filter` | `'DIM OP VALUE'`; repeat to AND. See [Search Analytics queries](https://projects.piyushgambhir.com/gsc-cli/docs/commands/query#filters) |
| `--data-state` | `final` (default), `all` (includes preliminary data), or `hourly_all` |
| `--aggregation` | `auto`, `byPage`, `byProperty`, or `byNewsShowcasePanel` |
## Comparisons [#comparisons]
`--compare previous` adds the period just before; `--compare yoy` adds the same dates a year earlier.
It works on `performance`, `top`, and `query`:
```bash
gsc performance --compare yoy
gsc top pages --compare previous --limit 50 -o json
```
Rows are joined on their dimension values and gain `_prev`, `_delta`, and `_pct` columns. A row missing
from one period is `null`, not zero; CTR changes are in percentage points; percent change is `null` when
the earlier value is zero.
Never add query or page rows up into site totals; Google withholds anonymized queries and caps the
rows it returns. Use `gsc performance`. See [Reading the data](https://projects.piyushgambhir.com/gsc-cli/docs/data).
# Properties & sitemaps (https://projects.piyushgambhir.com/gsc-cli/docs/commands/properties)
## Properties [#properties]
```bash
gsc sites list # properties you can access and your permission level
gsc sites get # the default property (or name one)
gsc sites use sc-domain:example.com # set the profile's default property
gsc sites add https://www.example.com/ # add to your list (write)
gsc sites remove https://www.example.com/ # remove from your list (write, asks first)
```
A property is a domain property (`sc-domain:example.com`) or a URL prefix (`https://www.example.com/`),
used exactly as `sites list` prints it. Commands pick it from `--site`, then `GSC_SITE`, then the
profile's default. A bare host such as `example.com` is matched against your properties: a domain
property wins, otherwise a single URL-prefix match, and an ambiguous match is an error that lists the
candidates.
`sites use` saves an exact identifier without a network call; a bare host costs one `sites.list` call.
`sites add` needs an exact identifier and only adds the property to your list: it does not verify
ownership, which you complete in Search Console.
## Sitemaps [#sitemaps]
```bash
gsc sitemaps list # submitted sitemaps
gsc sitemaps list --index https://www.example.com/sitemap-index.xml
gsc sitemaps get https://www.example.com/sitemap.xml # status, errors, and warnings
gsc sitemaps submit https://www.example.com/sitemap.xml # write
gsc sitemaps delete https://www.example.com/old-sitemap.xml # write, asks first
```
`sitemaps delete` removes the sitemap from the report; Google may still crawl it.
## Writes [#writes]
`sites add`, `sites remove`, `sitemaps submit`, and `sitemaps delete` change Search Console, so:
* `--dry-run` prints the request and sends nothing;
* `--read-only` blocks them;
* `sites remove` and `sitemaps delete` ask for confirmation, or need `--yes` under `--no-input`;
* a profile logged in with `--scope readonly` stops them early with a message, because the read-only
scope cannot write.
```bash
gsc sitemaps submit https://www.example.com/sitemap.xml --dry-run
```
# Search Analytics queries (https://projects.piyushgambhir.com/gsc-cli/docs/commands/query)
`gsc query` runs `searchAnalytics.query` with every option the API has. The helpers on
[Search performance](https://projects.piyushgambhir.com/gsc-cli/docs/commands/performance) are shortcuts for common shapes of this command.
```bash
gsc query -s sc-domain:example.com -d query -d page --limit 5000 -o csv
gsc query --last 3m -d date --filter 'country = ind' --filter 'query ~ shoes'
gsc query -d hour --data-state hourly_all --last 2d -o json
gsc query -d query --compare previous --limit 500
```
Without dates it covers the last 28 days ending at the latest day with data. Google sorts rows by
clicks.
## Dimensions [#dimensions]
`-d` (`--dimension`) groups the rows; repeat it or pass a comma list. The columns follow the order you
give:
| Dimension | Values |
| ------------------ | --------------------------------------------------------------------------- |
| `date` | `YYYY-MM-DD`, Pacific Time |
| `hour` | Hourly timestamps; needs `--data-state hourly_all` and covers about 10 days |
| `query` | Search queries (anonymized queries are never included) |
| `page` | Page URLs |
| `country` | ISO 3166-1 alpha-3 codes, such as `usa` |
| `device` | `DESKTOP`, `MOBILE`, `TABLET` |
| `searchAppearance` | Rich result and feature types; cannot be grouped with other dimensions |
## Filters [#filters]
`--filter 'DIM OP VALUE'` filters on `query`, `page`, `country`, `device`, or `searchAppearance`; dates come
from `--start`, `--end`, or `--last`. Repeated filters are ANDed; the API has no OR.
| Operator | Meaning |
| -------- | ---------------------------------------------- |
| `=` | Equals (case-sensitive for `page` and `query`) |
| `!=` | Does not equal |
| `~` | Contains |
| `!~` | Does not contain |
| `=~` | Matches an RE2 regular expression |
| `!=~` | Does not match an RE2 regular expression |
```bash
gsc query -d page --filter 'page =~ ^https://www.example.com/blog/' --filter 'device = MOBILE'
gsc query -d query --filter 'query !~ example' # leave out branded queries
```
## Search type, data state, and aggregation [#search-type-data-state-and-aggregation]
* `--type`: `web` (default), `image`, `video`, `news`, `discover`, or `googleNews`.
* `--data-state`: `final` (default), `all` to include preliminary data, or `hourly_all` for the `hour`
dimension. See [Reading the data](https://projects.piyushgambhir.com/gsc-cli/docs/data#final-and-preliminary-data).
* `--aggregation`: `auto`, `byPage`, `byProperty`, or `byNewsShowcasePanel`. Neither `byProperty` nor
`byNewsShowcasePanel` can be combined with page grouping or a page filter. `byProperty` is not
available for `discover` or `googleNews`; `byNewsShowcasePanel` needs one of them and exactly one
`searchAppearance = NEWS_SHOWCASE` filter.
These rules are checked before any request is sent. Anything else is left to Google, whose error is
shown as it is.
## How many rows [#how-many-rows]
`--limit` sets the number of rows (default 1,000). Rows are fetched in pages of up to 25,000 with
`startRow`, and `--all` keeps fetching until Google has nothing more. `complete: true` in JSON means
pagination reached the end of what Google exposes, never that every query is included.
## Raw requests [#raw-requests]
```bash
gsc query -d query --filter 'page ~ /blog/' --print-request # show the body, send nothing
gsc query --request-file body.json --last 7d # start from a body; flags override it
gsc query -d query --raw # Google's response, unchanged
```
`--request-file` takes a JSON request body (or `-` for stdin) for anything flags do not express.
`--raw` prints a single page of Google's response unchanged, so it cannot be combined with `--all` or
`--compare`.
# Compatibility (https://projects.piyushgambhir.com/gsc-cli/docs/compatibility)
`gsc` targets the Search Console API at `https://searchconsole.googleapis.com/`. Older method pages still
show `https://www.googleapis.com/`, which `gsc` does not use.
## Pinned API snapshot [#pinned-api-snapshot]
`gsc` is built and tested against Google's discovery documents, captured from Google's live discovery
service on 2026-10-07 and vendored unchanged in the repository. A coverage test checks their SHA-256 and
revision on every test run.
| API | Discovery revision | Methods |
| --------------------- | ------------------ | -------------------------------------------------------------------------------- |
| Search Console API v1 | `20261005` | 11: the 10 active methods `gsc` implements, and the retired Mobile-Friendly Test |
| Indexing API v3 | `20260923` | 2, skipped by decision |
The test fails when a snapshot method has no command or skip reason, or when a mapped command does not
exist. The [API coverage](https://projects.piyushgambhir.com/gsc-cli/docs/reference/api-coverage) page lists every method, and the
[compatibility record](https://github.com/piyush-gambhir/gsc-cli/blob/main/docs/compatibility.md) has
the hashes and how to refresh the snapshot.
The Indexing API is skipped because Google limits it to pages with `JobPosting` or `BroadcastEvent`
structured data; a general "request indexing" command would invite unsupported use. Google retired the
Mobile-Friendly Test API on 2023-12-01.
## Known differences and choices [#known-differences-and-choices]
* **Metadata spelling.** The method reference spells the incompleteness fields `first_incomplete_date`
and `first_incomplete_hour`; discovery and Google's Go client use camelCase. `gsc` accepts both and
always prints snake_case.
* **Search appearance.** The method reference allows any dimension combination; the extraction guide
says `searchAppearance` cannot be grouped with other dimensions. `gsc` warns instead of rejecting.
`gsc top appearance` lists the values to filter on.
* **Retention.** 16 months (10 days for hourly data) is data availability, not a request rule: explicit
dates beyond it produce a warning, and `--last` and comparison periods are clipped with a notice.
* **Enums.** Discovery lists some enums in upper case; `gsc` sends the method-reference spellings
(`byPage`, `hourly_all`) and accepts any case on input. Returned values are kept as Google sends them.
* **Validation.** Before any request, `gsc` checks only what Google documents: date order, `hour` only
with `hourly_all`, duplicate dimensions, aggregation rules, and regex syntax. Anything else is left to
the server, whose error is shown as it is.
## Not covered [#not-covered]
PageSpeed Insights, the Chrome UX Report, and Search Console's bulk data export to BigQuery are separate
services with their own access and semantics, so `gsc` does not cover them. `gsc export` is the API-side
alternative to the BigQuery export.
## Platforms [#platforms]
Prebuilt binaries for macOS, Linux, and Windows on `amd64` and `arm64`. CI runs the tests on all three
operating systems. Building from source needs Go 1.26 or later.
# Reading the data (https://projects.piyushgambhir.com/gsc-cli/docs/data)
Search Console data has rules that are easy to misread. `gsc` states them in its output, and this page
explains why they matter.
## Dates are Pacific Time [#dates-are-pacific-time]
Search Console reports days in Pacific Time, and every date in `gsc` (`--start`, `--end`, and the `start`
and `end` fields) is a Pacific Time date, inclusive. `--last 28d` ends at the latest day with data for the
chosen data state; one small extra request finds that day, and passing `--end` skips it. Month windows
that end on a month's last day cover whole calendar months.
## Final and preliminary data [#final-and-preliminary-data]
Recent days start out **preliminary** and become **final** later, usually after two to three days.
* `--data-state final` is the default, so numbers are final unless you ask otherwise.
* `--data-state all` includes preliminary days. Never present those numbers as final: they can still
change. JSON reports `first_incomplete_date` when Google provides it.
* `--data-state hourly_all` is required for the `hour` dimension and covers about the last 10 days.
* `gsc freshness` reports the latest final date and the latest preliminary date, so you can check before
trusting the last few days.
## Rows are not totals [#rows-are-not-totals]
Google does not return every query:
* **Anonymized queries are withheld.** Queries too rare to show for privacy reasons count toward the
totals but never appear as rows.
* **Rows are capped.** Google exposes at most 50,000 rows per day per search type, however you page.
So **never add up query or page rows to get site totals.** Use `gsc performance`, a query with no
dimensions whose totals match the Performance report chart, anonymized queries included. In JSON,
`complete: true` only means pagination reached the end of what Google exposes; it never means every
query is there.
## Row limits [#row-limits]
`--limit` defaults to 1,000 rows (25 for `top`). Larger limits are fetched in pages of up to 25,000 rows,
and `--all` fetches every row Google exposes. For the full history of a property, `gsc export` fetches
day by day, as Google's extraction guide recommends.
## Comparisons [#comparisons]
`--compare previous` runs a second query for the period just before; `--compare yoy` uses the same dates
a year earlier.
* A row missing from one period is `null`, **not zero**. Results are capped, so a row can be missing even
when the query had traffic.
* CTR changes are in percentage points. Percent change is `null` when the earlier value is zero.
* `insights new-queries` and `lost-queries` describe rows newly exposed or no longer exposed within the
fetched rows, not changes in ranking.
## Retention [#retention]
Search Console keeps 16 months of daily data and about 10 days of hourly data. Explicit dates beyond
that produce a warning, and `--last` and comparison periods are clipped with a notice.
## URL Inspection is the indexed version [#url-inspection-is-the-indexed-version]
`gsc inspect` reports what Google has **indexed**, as last crawled. It is not a live test of the page as
it is now, and the API cannot request indexing. Each inspection counts against a quota of 2,000 per
property per day and 600 per minute; `inspect` paces itself and stops cleanly at the quota, and `--max`
caps how many URLs it spends.
## Quotas [#quotas]
Search Analytics allows 1,200 queries per minute per property, plus unpublished load quotas: grouping by
page and query together over long ranges is expensive. When a quota is hit, narrow the date range or the
dimensions and wait about 15 minutes. `gsc` never retries automatically; only `gsc export --retry N`
opts in.
## Features without an API [#features-without-an-api]
Some Search Console reports have no public API, so `gsc` cannot read them: the branded query filter,
query groups and the Insights report, generative AI performance for AI Overviews and AI Mode (that
traffic is included in ordinary totals but cannot be separated), Core Web Vitals, Page indexing, manual
actions, links, and crawl stats. The [API coverage](https://projects.piyushgambhir.com/gsc-cli/docs/reference/api-coverage) page has the full list.
## Corrections from Google [#corrections-from-google]
Google occasionally corrects historical data, for example an impression-logging issue affecting
2025-05-13 to 2026-04-27. Check Google's
[data anomalies log](https://support.google.com/webmasters/answer/6211453) before attributing a
historical change to your site or to `gsc`.
# Introduction (https://projects.piyushgambhir.com/gsc-cli/docs)
**gsc-cli** (`gsc`) is a command-line interface for **Google Search Console**. Sign in with
your Google account, then read search performance, see how Google indexed your pages, manage sitemaps
and properties, and export your data from your terminal.
It is built for people and coding agents alike. Every command can print JSON, writes can be previewed
with `--dry-run`, and `--read-only` blocks every change.
The binary is `gsc`. `gsc auth login` opens your browser once and keeps the login in your OS
keychain; access tokens refresh automatically after that.
## What it covers [#what-it-covers]
* **Search performance:** totals that match the Performance report, top queries, pages, countries,
devices, and search appearances, and trends by hour, day, week, or month.
* **Every Search Analytics option:** seven dimensions, six search types, filters with regular
expressions, final, preliminary, and hourly data, and every row Google exposes, well past the web
UI's 1,000-row table.
* **Comparisons:** any period against the one before it or the same dates a year earlier, with rows
missing from one period reported as `null`, not zero.
* **URL Inspection:** Google's indexed view of one URL or a batch, paced under the quota.
* **Sitemaps and properties:** list, check, submit, and delete sitemaps; list, add, and remove
properties.
* **Bulk export:** day-by-day CSV or NDJSON files that resume after an interruption.
* **Insights:** striking-distance queries, low CTR for the position, cannibalization, decliners, and
new and lost queries, computed locally with their thresholds printed.
* **Anything else:** `gsc api` sends an authenticated request to any Search Console API method.
Each of the 10 active Search Console API methods has a command. The
[API coverage](https://projects.piyushgambhir.com/gsc-cli/docs/reference/api-coverage) page maps them one by one. Search Console data has rules
that are easy to miss, such as preliminary days, anonymized queries, and row caps;
[Reading the data](https://projects.piyushgambhir.com/gsc-cli/docs/data) explains them.
## Next steps [#next-steps]
**Independent project.** Not affiliated with, endorsed by, or sponsored by Google. Google and Google
Search Console are trademarks of Google LLC, named here only to identify the service this tool works
with.
# Installation (https://projects.piyushgambhir.com/gsc-cli/docs/installation)
## Install script (recommended) [#install-script-recommended]
For macOS and Linux. The script downloads the latest release for your OS and architecture, checks its
SHA-256 checksum, and installs `gsc` to `~/.local/bin`:
```bash
curl -fsSL https://raw.githubusercontent.com/piyush-gambhir/gsc-cli/main/install.sh | sh
```
Pin a version or change the install directory:
```bash
curl -fsSL https://raw.githubusercontent.com/piyush-gambhir/gsc-cli/main/install.sh | VERSION=v0.1.5 sh
curl -fsSL https://raw.githubusercontent.com/piyush-gambhir/gsc-cli/main/install.sh | INSTALL_DIR=/usr/local/bin sh
```
Ghostscript and Gambit Scheme also install a program named `gsc`. The installer warns when a different
`gsc` comes first on your `PATH`, and `gsc doctor` checks for it too.
## Download a release manually [#download-a-release-manually]
Each [GitHub release](https://github.com/piyush-gambhir/gsc-cli/releases) has
`gsc-cli__.tar.gz` archives for `darwin` and `linux` (`amd64` or `arm64`) and
`gsc-cli_windows_.zip` for Windows. Extract `gsc` and put it on your `PATH`. `checksums.txt`
lists the SHA-256 of every archive.
## Verify a download [#verify-a-download]
Releases are immutable once published. Every archive ships with an SBOM and a signed build-provenance
attestation, so you can confirm it was built by the repository's release workflow:
```bash
gh attestation verify gsc-cli_darwin_arm64.tar.gz --repo piyush-gambhir/gsc-cli
```
## Build from source [#build-from-source]
Requires Go 1.26 or later:
```bash
git clone https://github.com/piyush-gambhir/gsc-cli.git
cd gsc-cli/cli-go
make install # installs to $(go env GOPATH)/bin, or INSTALL_DIR=...
```
A source build has no built-in OAuth client, so `gsc auth login` needs your own
([`--client-secret-file`](https://projects.piyushgambhir.com/gsc-cli/docs/authentication#your-own-oauth-client), or `GSC_CLIENT_ID` and
`GSC_CLIENT_SECRET`). Service accounts, Application Default Credentials, and access tokens work as
usual. `gsc version -o json` reports `builtin_oauth_client`.
## Check and update [#check-and-update]
```bash
gsc version # build info, plus the latest version from the last release check
gsc doctor # configuration, credentials, keychain, scopes, and PATH; no API calls
gsc update --check # is a newer release out? (-o json for scripts)
gsc update # asks, then installs it after SHA-256 verification
```
`gsc update` works on macOS, Linux, and Windows. It downloads the release archive for your platform,
checks it against the release's `checksums.txt`, and replaces the running executable. On Windows the old
executable is renamed to `gsc.exe.old` and deleted the next time `gsc` starts. `--yes` skips the
question, and `--no-input` requires `--yes`. If the install directory is not writable, the update stops and
the old binary keeps working: re-run with `sudo`, or reinstall with the install script into a directory you
own. A `gsc` in a Go bin directory (`$GOBIN`, `$GOPATH/bin`, or `~/go/bin`) came from a source build, so
`gsc update` tells you to run `git pull && make install` in your checkout instead of replacing it.
`--read-only` blocks `gsc update` but still allows `gsc update --check`.
### Update notice [#update-notice]
In an interactive terminal, `gsc` checks the github.com release page for a new release at most once a day
(not the GitHub API, so its per-IP rate limit never applies). When one is out, it
prints a notice on stderr after the command's output, at most once a day per version:
```text
A new version of gsc is available: v0.1.4 -> v0.1.5
Update with: gsc update
Release notes: https://github.com/piyush-gambhir/gsc-cli/releases/tag/v0.1.5
```
Only the one command a day that runs the check waits for its answer, at most 1 second after its output;
every other command uses the cached answer and never waits. `gsc update` and `gsc update --check` store
what they find in the same cache. The check never runs (no network, no output) when:
* stderr is not a terminal (pipes, scripts, coding agents),
* `CI` is set,
* `--quiet` or `GSC_QUIET=1` is on,
* `GSC_NO_UPDATE_NOTIFIER` or `NO_UPDATE_NOTIFIER` is set to any value, or
* the command is `update`, `version`, `completion`, or `help`.
To turn it off for good, add `export GSC_NO_UPDATE_NOTIFIER=1` to your shell profile.
# Output formats (https://projects.piyushgambhir.com/gsc-cli/docs/output-formats)
## Formats [#formats]
`-o table` is the default. `-o json` and `-o yaml` work on every command that returns data, and `-o csv`
works on row-shaped results such as Search Analytics, `top`, `trend`, insights, `inspect`, and
`sites list`; other commands reject it with a message. CSV is one flat row per result, so nested fields
(a sitemap's `contents`, URL Inspection details such as referring URLs) appear only in JSON and YAML.
```bash
gsc sites list # table
gsc top queries -o json # JSON for scripts and agents
gsc query -d page --limit 5000 -o csv > pages.csv # spreadsheet-ready
```
Tables show CTR as a percentage and positions to two decimals, shorten timestamps, and keep wide results
readable: comparisons show current values and changes, sitemaps omit `isSitemapsIndex`, and inspections
show the URL, verdict, coverage, last crawl, canonical, and error. JSON, YAML, and CSV keep the raw values:
`ctr` is a ratio such as `0.0249`. In CSV, text starting with `=`, `+`, `-`, or `@` gets a leading
apostrophe so a spreadsheet never runs a search query as a formula.
## stdout and stderr [#stdout-and-stderr]
Data goes to **stdout**. Notes, warnings, and errors go to **stderr**, so piping stdout always gives you
clean data. `--quiet` drops informational notes, and `--verbose` logs each request's method, URL, and
HTTP status to stderr (never tokens or bodies).
## The Search Analytics envelope [#the-search-analytics-envelope]
`query`, `performance`, `top`, and `trend` print an envelope in JSON and YAML, so the numbers carry what
you need to interpret them:
```json
{
"site": "sc-domain:example.com",
"start": "2026-09-03",
"end": "2026-09-30",
"type": "web",
"data_state": "final",
"dimensions": ["query", "page"],
"aggregation": "byPage",
"first_incomplete_date": null,
"first_incomplete_hour": null,
"metadata_returned": false,
"row_count": 1000,
"complete": false,
"requests": 1,
"rollup": null,
"rows": [
{ "query": "running shoes", "page": "https://example.com/shoes", "clicks": 120, "impressions": 4810, "ctr": 0.0249, "position": 7.42 }
]
}
```
* `start` and `end` are inclusive dates in Pacific Time, Search Console's timezone.
* `complete` is about pagination only: `true` means every row Google exposes for the request was
fetched. It never means every query, because Google withholds anonymized queries.
* `metadata_returned` says whether Google sent the `first_incomplete_date` or `first_incomplete_hour`
fields. Google sends them only for `--data-state all` with date grouping or `hourly_all` with hour
grouping, so `null` with `metadata_returned: false` means "not provided", not "complete".
* `aggregation` is the aggregation Google reports it used. `requests` counts the API requests made.
* `rollup` describes a local week or month rollup from `trend --by week|month`.
## Comparisons [#comparisons]
With `--compare previous` or `--compare yoy`, `current` and `previous` each carry their own envelope
fields, and every row adds `_prev`, `_delta`, and `_pct` columns:
```json
{
"mode": "previous",
"current": { "start": "2026-09-03", "end": "2026-09-30", "row_count": 500, "complete": false },
"previous": { "start": "2026-08-06", "end": "2026-09-02", "row_count": 500, "complete": false },
"dimensions": ["query"],
"note": "null means the row was not returned for that period (not zero); ctr_delta_pp is in percentage points",
"rows": [
{ "query": "running shoes", "clicks": 120, "clicks_prev": null, "clicks_delta": null, "clicks_pct": null }
]
}
```
The example is abridged. A row missing from one period is `null`, never zero. `clicks_pct` and
`impressions_pct` are `null` when the earlier value is zero, CTR changes are in percentage points
(`ctr_delta_pp`), and position changes are absolute differences (`position_delta`).
## Other commands [#other-commands]
* `freshness` prints `latest_final_date`, `latest_preliminary_date`, and `first_incomplete_date`.
* Insights print the insight name, a `note` on how it was computed, the `thresholds` used, the source
queries, and the rows.
* `inspect` prints `site`, `requested`, `inspected`, a `note`, and one entry per URL with Google's full
inspection result or that URL's error. `stopped_reason` appears when a quota or a rejected login
stopped the batch early.
* `sites` and `sitemaps` commands keep Google's field names (`siteUrl`, `permissionLevel`).
## Errors [#errors]
Every failure exits with status `1`, like the other CLIs in this suite. With `-o json` or `-o yaml`, stderr
carries a structured error:
```json
{
"error": "Search Console API: User does not have sufficient permission for site 'sc-domain:example.com'. See also: https://support.google.com/webmasters/answer/2451999. (HTTP 403, forbidden). The signed-in identity lacks access to this property; check gsc sites list or add the user in Search Console > Settings > Users and permissions",
"kind": "permission",
"reason": "forbidden",
"status": 403
}
```
`kind` classifies every failure so agents can branch without parsing the message:
| `kind` | Meaning |
| --------------------------------- | ------------------------------------------------------ |
| `usage` | Unknown command or flag, or wrong arguments |
| `auth` | Not logged in, or the saved credentials no longer work |
| `permission` | The identity lacks access to the property (HTTP 403) |
| `not_found` | Google reports no such property or sitemap (HTTP 404) |
| `rate_limit` | Quota or rate limit (HTTP 429); wait for `retry_after` |
| `invalid_request` | Google rejected the request (other HTTP 4xx) |
| `server` | Google failed (HTTP 5xx) |
| `read_only` | `--read-only` or a read-only scope blocked a write |
| `confirmation_required` | A destructive command needs `--yes` without a terminal |
| `timeout`, `network`, `cancelled` | The request did not complete |
| `error` | Anything else |
Three commands write stdout and still exit `1`, so the work done so far is not lost: `gsc doctor` prints its
check report when a check fails, `gsc inspect` prints the URLs it inspected before a quota stop, and
`gsc export` prints its summary when it stops early (rerun it to resume).
`reason` is Google's error reason when it sends one, and `retry_after` is added when Google sends a
`Retry-After` header. The message ends with a hint for the common cases:
| Situation | What to do |
| ---------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------ |
| HTTP 401 | The access token was rejected; run `gsc auth login` |
| HTTP 403, no permission for the property | Check `gsc sites list`, or add the user or service account in Search Console |
| HTTP 403, API not enabled | Enable the Google Search Console API in the OAuth client's Google Cloud project |
| Quota or rate limit (403 or 429) | Narrow the date range, avoid grouping by page and query together, and wait about 15 minutes. `gsc` never retries automatically |
Tokens and client secrets never appear in errors or logs.
# Quick start (https://projects.piyushgambhir.com/gsc-cli/docs/quickstart)
## 1. Log in [#1-log-in]
```bash
gsc auth login
```
Your browser opens Google's consent page. Approve access, and `gsc` saves the login in your OS keychain,
lists your properties, and picks a default site: your only property, or the one you choose from a list.
On a machine without a browser, use `gsc auth login --no-browser`; [Authentication](https://projects.piyushgambhir.com/gsc-cli/docs/authentication)
covers service accounts and CI.
## 2. Check your properties [#2-check-your-properties]
```bash
gsc sites list # properties and your permission level
gsc sites use sc-domain:example.com # change the default property
gsc auth status # the active credential and property; no network call
```
Properties are either domain properties (`sc-domain:example.com`) or URL prefixes
(`https://www.example.com/`), exactly as `sites list` prints them. `-s` picks one for a single command,
and a bare host such as `example.com` is matched against your properties: a domain property wins,
otherwise a single URL-prefix match.
## 3. Read your performance [#3-read-your-performance]
```bash
gsc freshness # the latest final and preliminary dates
gsc performance --last 28d --compare previous # totals, and the change from the 28 days before
gsc top queries --last 3m --limit 50
gsc trend --last 16m --by month
```
## 4. Dig into the rows [#4-dig-into-the-rows]
```bash
gsc query -d query -d page --filter 'page =~ ^https://www.example.com/blog/' --limit 5000 -o csv > blog.csv
gsc insights striking-distance --last 28d
```
## 5. Check pages and sitemaps [#5-check-pages-and-sitemaps]
```bash
gsc inspect https://www.example.com/pricing
gsc sitemaps list
gsc sitemaps submit https://www.example.com/sitemap.xml --dry-run # see the request first
```
Commands use final data by default, and the last two or three days are usually still preliminary,
so `--last 28d` ends at the latest final day. `gsc freshness` shows where that is, and
[Reading the data](https://projects.piyushgambhir.com/gsc-cli/docs/data) explains the rest.
## Discovering commands [#discovering-commands]
```bash
gsc --help
gsc query --help
gsc doctor # configuration, credentials, keychain, scopes, and PATH
```
# API coverage (https://projects.piyushgambhir.com/gsc-cli/docs/reference/api-coverage)
Every method in the pinned Google API snapshots, mapped to the `gsc` commands that call it, or listed as
skipped with the reason. The snapshots (discovery documents, revisions `20261005` (Search Console) and `20260923` (Indexing), captured 2026-10-07) are
vendored in `cli-go/internal/coverage/testdata/`; see [Compatibility](https://projects.piyushgambhir.com/gsc-cli/docs/compatibility) for hashes and the
refresh procedure.
This page is checked by `TestAPICoverage` (`cli-go/cmd/coverage_test.go`): the test fails if a snapshot
method is missing from the coverage table, if a mapped command does not exist, or if this page does not
list a method or command. Summary: **10 of 10 active Search Console methods implemented**, 1 retired method
skipped, 2 Indexing API methods skipped by decision.
## Search Console API v1 [#search-console-api-v1]
Host `https://searchconsole.googleapis.com`. Read methods accept either OAuth scope; write methods need the
full `webmasters` scope.
| Method | HTTP and path | Status | Commands |
| ------------------------------------------------------ | ---------------------------------------------------------- | -------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `webmasters.sites.list` | `GET webmasters/v3/sites` | Implemented | `gsc sites list` (also used once after login and to resolve bare hosts) |
| `webmasters.sites.get` | `GET webmasters/v3/sites/{siteUrl}` | Implemented | `gsc sites get` |
| `webmasters.sites.add` | `PUT webmasters/v3/sites/{siteUrl}` | Implemented (write) | `gsc sites add` |
| `webmasters.sites.delete` | `DELETE webmasters/v3/sites/{siteUrl}` | Implemented (write) | `gsc sites remove` |
| `webmasters.sitemaps.list` | `GET webmasters/v3/sites/{siteUrl}/sitemaps` | Implemented | `gsc sitemaps list` |
| `webmasters.sitemaps.get` | `GET webmasters/v3/sites/{siteUrl}/sitemaps/{feedpath}` | Implemented | `gsc sitemaps get` |
| `webmasters.sitemaps.submit` | `PUT webmasters/v3/sites/{siteUrl}/sitemaps/{feedpath}` | Implemented (write) | `gsc sitemaps submit` |
| `webmasters.sitemaps.delete` | `DELETE webmasters/v3/sites/{siteUrl}/sitemaps/{feedpath}` | Implemented (write) | `gsc sitemaps delete` |
| `webmasters.searchanalytics.query` | `POST webmasters/v3/sites/{siteUrl}/searchAnalytics/query` | Implemented (read, despite POST) | `gsc query`, `gsc performance`, `gsc top queries`, `gsc top pages`, `gsc top countries`, `gsc top devices`, `gsc top appearance`, `gsc trend`, `gsc freshness`, `gsc export`, `gsc insights striking-distance`, `gsc insights low-ctr`, `gsc insights cannibalization`, `gsc insights decliners`, `gsc insights new-queries`, `gsc insights lost-queries` |
| `searchconsole.urlInspection.index.inspect` | `POST v1/urlInspection/index:inspect` | Implemented (read, despite POST) | `gsc inspect` |
| `searchconsole.urlTestingTools.mobileFriendlyTest.run` | `POST v1/urlTestingTools/mobileFriendlyTest:run` | Retired, skipped | none. Google retired the Mobile-Friendly Test API on 2023-12-01; the method is still in the discovery document but no longer operates. |
Every request field of `searchanalytics.query` is reachable from `gsc query`: dimensions (`date`, `hour`,
`query`, `page`, `country`, `device`, `searchAppearance`), `type`, `dataState` (`final`, `all`,
`hourly_all`), `aggregationType`, `dimensionFilterGroups` with all six operators, `rowLimit`, and
`startRow` (pagination). `--request-file` accepts a raw body for anything flags do not express.
`gsc api METHOD PATH` can call any of these methods directly, and any method Google adds later.
`gsc api methods -o json` prints this table offline (method, HTTP verb, path, read or write effect, status,
and commands).
## Indexing API v3 (skipped by decision) [#indexing-api-v3-skipped-by-decision]
Host `https://indexing.googleapis.com`, a separate service from Search Console.
| Method | HTTP and path | Status | Reason |
| --------------------------------------- | ---------------------------------- | ------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `indexing.urlNotifications.publish` | `POST v3/urlNotifications:publish` | Skipped | Google limits the Indexing API to pages with `JobPosting` or `BroadcastEvent` (in `VideoObject`) structured data, requires an owner-level service account, and enforces against misuse. A general "request indexing" command would invite unsupported use. |
| `indexing.urlNotifications.getMetadata` | `GET v3/urlNotifications/metadata` | Skipped | Same service and restrictions as above. |
If a site you own qualifies, the plan (PLAN.md, decision 4) describes adding it later with a structured-data
eligibility check.
## Related Google APIs and data sources [#related-google-apis-and-data-sources]
| Source | How it is handled |
| ----------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| PageSpeed Insights API (`pagespeedonline/v5/runPagespeed`) | Not covered. It analyzes any URL with Lighthouse, uses an API key, and is unrelated to Search Console property data. A separate performance tool fits it better. |
| Chrome UX Report (CrUX) API | Not covered. Real-user field data with an API key and its own quotas; different semantics from Search Console. |
| Search Console bulk data export to BigQuery | Not covered. Owners turn it on in the Search Console UI; the data is then queried in BigQuery (`bq`). It avoids the API's row limits but does not backfill history. `gsc export` is the API-side alternative. |
| Search Console URL Inspection live test, "Request indexing" | Not available in any public API. `gsc inspect` reports the indexed version only. |
## Search Console features without an API (as of the snapshot) [#search-console-features-without-an-api-as-of-the-snapshot]
These appear in the Search Console UI but have no public API method or field, so `gsc` cannot report them:
* Branded and non-branded query filter (2025-11, worldwide rollout 2026-03). A regex filter on `query` can
approximate it, but that is your classification, not Google's.
* Query groups in Search Console Insights (2025-10) and the Insights report itself (2025-06).
* Generative AI performance reports for AI Overviews and AI Mode in Search and Discover (2026-06, worldwide
2026-08-31). This traffic is included in ordinary performance totals but cannot be separated.
* Web multimodal reporting for Lens, Circle to Search, and image search (2026-09).
* Platform properties for Instagram, TikTok, X, and YouTube (2026-07). They work only as far as
`gsc sites list` returns them; their identifiers are not documented for the API.
* Weekly and monthly UI views (2025-12). `gsc trend --by week` and `--by month` roll up daily rows locally
instead.
* Core Web Vitals, Page indexing, Manual actions, Security issues, Links, Removals, Crawl stats, and user
and permission management reports.
# Command reference (https://projects.piyushgambhir.com/gsc-cli/docs/reference/commands)
Generated from the `gsc` command tree on `main`. The [command guides](https://projects.piyushgambhir.com/gsc-cli/docs/commands) explain when to use each group.
Global flags apply to every command.
```text
--access-token string Use this OAuth access token (prefer GSC_ACCESS_TOKEN)
--credentials string Google credentials JSON: service account, authorized user, or external account (or GSC_CREDENTIALS)
--dry-run For write commands: print the request and send nothing
--no-input Never prompt or open a browser
-o, --output string Output format: table, json, yaml, csv (default "table")
--profile string Named profile (or GSC_PROFILE)
-q, --quiet Suppress informational stderr output
--read-only Block remote writes, local credential changes, and self-update
-s, --site string Property: sc-domain:example.com, https://www.example.com/, or a bare host (or GSC_SITE)
--timeout duration HTTP request timeout (default 30s)
-v, --verbose Log request method, URL, and status to stderr (never tokens or bodies)
-y, --yes Confirm destructive commands and updates without prompting
```
## gsc [#gsc]
Google Search Console from your terminal
Search performance, URL inspection, sitemaps, and properties from Google Search Console.
Run `gsc auth login` once; it opens your browser and remembers the login.
Data goes to stdout and diagnostics to stderr. --read-only blocks every write.
```text
gsc [flags]
```
```text
-h, --help help for gsc
```
## gsc api [#gsc-api]
Send an authenticated request to the Search Console API (escape hatch)
PATH is relative to [https://searchconsole.googleapis.com](https://searchconsole.googleapis.com) and must already be URL-escaped.
GET and the read-only POSTs (searchAnalytics/query, urlInspection/index:inspect) are reads; every other
request is treated as a write: blocked by --read-only, printed instead of sent with --dry-run, and
confirmed interactively (or with --yes) before it is sent.
```text
gsc api METHOD PATH [flags]
```
```bash
gsc api GET /webmasters/v3/sites
gsc api POST /webmasters/v3/sites/sc-domain%3Aexample.com/searchAnalytics/query --data '{"startDate":"2026-09-01","endDate":"2026-09-30"}'
gsc api PUT /webmasters/v3/sites/sc-domain%3Aexample.com/sitemaps/https%3A%2F%2Fexample.com%2Fsitemap.xml --dry-run
```
```text
--data string JSON request body, @file, or - for stdin
-h, --help help for api
```
## gsc api methods [#gsc-api-methods]
List every Search Console API method: HTTP verb, read or write effect, status, and the commands that call it
Offline: reads the method registry pinned to the vendored discovery snapshot. path is what gsc api takes;
status is implemented, retired, or skipped (with a note saying why).
```text
gsc api methods [flags]
```
```bash
gsc api methods
gsc api methods -o json
```
```text
-h, --help help for methods
```
## gsc auth [#gsc-auth]
Log in, inspect credentials, and manage profiles
```text
gsc auth [flags]
```
```text
-h, --help help for auth
```
## gsc auth list [#gsc-auth-list]
List saved profiles (no secrets)
```text
gsc auth list [flags]
```
```bash
gsc auth list
gsc auth list -o json
```
```text
-h, --help help for list
```
## gsc auth login [#gsc-auth-login]
Sign in with Google (opens your browser) or save another credential type
With no flags, opens Google's consent page in your browser and saves the login in the OS keychain.
Tokens refresh automatically afterwards. --no-browser prints a URL to approve on any device and
asks you to paste the final redirected URL back. --service-account and --adc save non-interactive
credentials for CI. See docs/auth.md for every method.
```text
gsc auth login [flags]
```
```bash
gsc auth login
gsc auth login --profile work --scope readonly
gsc auth login --no-browser
gsc auth login --client-secret-file client_secret.json
gsc auth login --profile ci --service-account key.json
gsc auth login --profile gcloud --adc --impersonate reporting@my-project.iam.gserviceaccount.com
```
```text
--adc Use Application Default Credentials (gcloud, workload identity federation, metadata server)
--client-secret-file string Use your own Desktop OAuth client (JSON from Google Cloud Console)
-h, --help help for login
--impersonate string With --adc: service account email to impersonate through IAM Credentials
--insecure-storage Store the login in a 0600 plaintext file instead of the OS keychain
--no-browser Headless login: approve on any device, then paste the redirected URL
--no-verify Skip listing properties after login (no API call)
--scope string Search Console access to request: full or readonly (default "full")
--service-account string Save a service account JSON key file for this profile
--subject string With --service-account: Workspace user to impersonate (domain-wide delegation)
```
## gsc auth logout [#gsc-auth-logout]
Remove a profile and its saved login from this machine
Deletes the profile and its stored token locally. --revoke also revokes access at Google, which
signs this Google account out of every machine and profile that uses the same OAuth client project;
it asks for confirmation unless --yes is given.
```text
gsc auth logout [flags]
```
```bash
gsc auth logout
gsc auth logout --profile work --revoke --yes
```
```text
-h, --help help for logout
--revoke Also revoke access at Google (signs out every machine using the same OAuth client)
```
## gsc auth status [#gsc-auth-status]
Show which credential is in use, without revealing it
Reads local configuration only. --verify additionally lists properties with the credential (one API call).
```text
gsc auth status [flags]
```
```bash
gsc auth status
gsc auth status --verify -o json
```
```text
-h, --help help for status
--verify Check the credential by listing properties (one API call)
```
## gsc auth token [#gsc-auth-token]
Print a fresh access token for debugging (keep it secret)
```text
gsc auth token [flags]
```
```bash
curl -H "Authorization: Bearer $(gsc auth token)" https://searchconsole.googleapis.com/webmasters/v3/sites
gsc auth token --profile ci
```
```text
-h, --help help for token
```
## gsc auth use [#gsc-auth-use]
Set the default profile
```text
gsc auth use NAME [flags]
```
```bash
gsc auth use work
```
```text
-h, --help help for use
```
## gsc commands [#gsc-commands]
Describe every command, its flags, and whether it reads or writes (for agents)
Offline: lists every runnable command with its usage, flags and defaults, effect (read, remote_write,
or local_write), whether it may prompt, the Search Console API methods it exists to call, and examples.
Global flags are listed once. Use -o json for the full description. Some calls are incidental and not
listed: a bare host given to -s, --verify, doctor --online, and login each list properties once
(webmasters.sites.list).
```text
gsc commands [flags]
```
```bash
gsc commands
gsc commands -o json | jq -r '.commands[] | select(.effect != "read") | .command'
```
```text
-h, --help help for commands
```
## gsc completion [#gsc-completion]
Generate shell completion script
```text
gsc completion [bash|zsh|fish|powershell] [flags]
```
```bash
source <(gsc completion zsh)
gsc completion bash > /usr/local/etc/bash_completion.d/gsc
gsc completion fish > ~/.config/fish/completions/gsc.fish
```
```text
-h, --help help for completion
```
## gsc config [#gsc-config]
Inspect configuration and select profiles
```text
gsc config [flags]
```
```text
-h, --help help for config
```
## gsc config list-profiles [#gsc-config-list-profiles]
List saved profiles (no secrets)
```text
gsc config list-profiles [flags]
```
```bash
gsc config list-profiles
gsc config list-profiles -o json
```
```text
-h, --help help for list-profiles
```
## gsc config show [#gsc-config-show]
Show which credential is in use, without revealing it
Reads local configuration only. --verify additionally lists properties with the credential (one API call).
```text
gsc config show [flags]
```
```bash
gsc config show
gsc config show --verify -o json
```
```text
-h, --help help for show
--verify Check the credential by listing properties (one API call)
```
## gsc config use-profile [#gsc-config-use-profile]
Set the default profile
```text
gsc config use-profile NAME [flags]
```
```bash
gsc config use-profile work
```
```text
-h, --help help for use-profile
```
## gsc doctor [#gsc-doctor]
Check configuration, credentials, and PATH (local unless --online)
```text
gsc doctor [flags]
```
```bash
gsc doctor
gsc doctor --online -o json
```
```text
-h, --help help for doctor
--online Also call the API once to list properties
```
## gsc export [#gsc-export]
Export Search Analytics day by day to CSV or NDJSON files (resumable)
Splits the range into days, as Google's extraction guide recommends, fetches every exposed row for each
day, and writes one file per day through a temp file and rename. manifest.json records finished days;
rerun the same command to resume. Days after the latest final date, and days Google marks incomplete,
are recorded as preliminary and re-fetched on resume.
No automatic retries unless --retry is given.
```text
gsc export [flags]
```
```bash
gsc export --last 16m -d query -d page --out ./gsc-export
gsc export --start 2026-01-01 --end 2026-03-31 -d page --format ndjson --out ./q1
```
```text
--aggregation string auto, byPage, byProperty, or byNewsShowcasePanel
--data-state string final (default), all (includes preliminary data), or hourly_all
-d, --dimension strings Group by: date, hour, query, page, country, device, searchAppearance (repeat or comma list)
--end string End date YYYY-MM-DD (Pacific Time, inclusive)
--filter stringArray Filter 'DIM OP VALUE'; OP is = != ~ !~ =~ !=~ (contains/regex); repeat to AND
--format string File format: csv or ndjson (default "csv")
-h, --help help for export
--interval duration Minimum time between the start of one day and the next (a day's pages are fetched back to back) (default 250ms)
--last string Window ending at the latest available date: 7d, 28d, 4w, 3m, 16m (default 28d)
--out string Output directory (created if missing)
--restart Discard a manifest written for a different request
--retry int Retry quota and server errors this many times per day, with backoff (default 0)
--start string Start date YYYY-MM-DD (Pacific Time, inclusive)
--type string Search type: web (default), image, video, news, discover, googleNews
```
## gsc freshness [#gsc-freshness]
Latest dates with final and with preliminary data
Two small date-grouped queries over the last 10 days: one for final data and one including preliminary
data. Final data usually lags two to three days.
```text
gsc freshness [flags]
```
```bash
gsc freshness
gsc freshness --type discover -o json
```
```text
-h, --help help for freshness
--type string Search type (default web)
```
## gsc insights [#gsc-insights]
Opportunities computed locally from explicit queries (thresholds are printed)
Each insight runs ordinary Search Analytics queries and computes the result locally. The thresholds used
are printed on stderr and included in JSON output. Results reflect rows Google exposes; anonymized queries
are never included.
```text
gsc insights [flags]
```
```text
-h, --help help for insights
```
## gsc insights cannibalization [#gsc-insights-cannibalization]
Queries where two or more pages split the impressions
```text
gsc insights cannibalization [flags]
```
```bash
gsc insights cannibalization --last 3m
gsc insights cannibalization --min-share 0.2 --filter 'query ~ pricing'
```
```text
--aggregation string auto, byPage, byProperty, or byNewsShowcasePanel
--data-state string final (default), all (includes preliminary data), or hourly_all
--end string End date YYYY-MM-DD (Pacific Time, inclusive)
--filter stringArray Filter 'DIM OP VALUE'; OP is = != ~ !~ =~ !=~ (contains/regex); repeat to AND
-h, --help help for cannibalization
--last string Window ending at the latest available date: 7d, 28d, 4w, 3m, 16m (default 28d)
--limit int Maximum queries to show (default 25)
--min-impressions float Minimum total impressions for a query (default 100)
--min-share float Minimum share of a query's impressions for a page to count as competing (default 0.1)
--start string Start date YYYY-MM-DD (Pacific Time, inclusive)
--type string Search type: web (default), image, video, news, discover, googleNews
```
## gsc insights decliners [#gsc-insights-decliners]
Queries or pages that lost the most clicks versus the comparison period
```text
gsc insights decliners [flags]
```
```bash
gsc insights decliners --compare yoy
gsc insights decliners --by page --min-clicks 20 -o json
```
```text
--aggregation string auto, byPage, byProperty, or byNewsShowcasePanel
--by string Compare queries or pages (default "query")
--compare string Comparison period: previous or yoy (default "previous")
--data-state string final (default), all (includes preliminary data), or hourly_all
--end string End date YYYY-MM-DD (Pacific Time, inclusive)
--filter stringArray Filter 'DIM OP VALUE'; OP is = != ~ !~ =~ !=~ (contains/regex); repeat to AND
-h, --help help for decliners
--last string Window ending at the latest available date: 7d, 28d, 4w, 3m, 16m (default 28d)
--limit int Maximum rows to show (default 50)
--min-clicks float Minimum clicks in the comparison period (default 5)
--start string Start date YYYY-MM-DD (Pacific Time, inclusive)
--type string Search type: web (default), image, video, news, discover, googleNews
```
## gsc insights lost-queries [#gsc-insights-lost-queries]
Queries or pages returned in the comparison period but not now (no longer exposed)
```text
gsc insights lost-queries [flags]
```
```bash
gsc insights lost-queries --last 7d
gsc insights lost-queries --by page -o json
```
```text
--aggregation string auto, byPage, byProperty, or byNewsShowcasePanel
--by string Compare queries or pages (default "query")
--compare string Comparison period: previous or yoy (default "previous")
--data-state string final (default), all (includes preliminary data), or hourly_all
--end string End date YYYY-MM-DD (Pacific Time, inclusive)
--filter stringArray Filter 'DIM OP VALUE'; OP is = != ~ !~ =~ !=~ (contains/regex); repeat to AND
-h, --help help for lost-queries
--last string Window ending at the latest available date: 7d, 28d, 4w, 3m, 16m (default 28d)
--limit int Maximum rows to show (default 50)
--start string Start date YYYY-MM-DD (Pacific Time, inclusive)
--type string Search type: web (default), image, video, news, discover, googleNews
```
## gsc insights low-ctr [#gsc-insights-low-ctr]
Queries whose CTR is well below this site's median for the same position
```text
gsc insights low-ctr [flags]
```
```bash
gsc insights low-ctr
gsc insights low-ctr --factor 0.4 --min-impressions 200 -o json
```
```text
--aggregation string auto, byPage, byProperty, or byNewsShowcasePanel
--data-state string final (default), all (includes preliminary data), or hourly_all
--end string End date YYYY-MM-DD (Pacific Time, inclusive)
--factor float Flag queries with CTR below this fraction of the bucket median (default 0.5)
--filter stringArray Filter 'DIM OP VALUE'; OP is = != ~ !~ =~ !=~ (contains/regex); repeat to AND
-h, --help help for low-ctr
--last string Window ending at the latest available date: 7d, 28d, 4w, 3m, 16m (default 28d)
--limit int Maximum rows to show (default 50)
--min-impressions float Minimum impressions for a query to count (default 100)
--start string Start date YYYY-MM-DD (Pacific Time, inclusive)
--type string Search type: web (default), image, video, news, discover, googleNews
```
## gsc insights new-queries [#gsc-insights-new-queries]
Queries or pages returned now but not in the comparison period (newly exposed)
```text
gsc insights new-queries [flags]
```
```bash
gsc insights new-queries --last 7d
gsc insights new-queries --by page --compare yoy
```
```text
--aggregation string auto, byPage, byProperty, or byNewsShowcasePanel
--by string Compare queries or pages (default "query")
--compare string Comparison period: previous or yoy (default "previous")
--data-state string final (default), all (includes preliminary data), or hourly_all
--end string End date YYYY-MM-DD (Pacific Time, inclusive)
--filter stringArray Filter 'DIM OP VALUE'; OP is = != ~ !~ =~ !=~ (contains/regex); repeat to AND
-h, --help help for new-queries
--last string Window ending at the latest available date: 7d, 28d, 4w, 3m, 16m (default 28d)
--limit int Maximum rows to show (default 50)
--start string Start date YYYY-MM-DD (Pacific Time, inclusive)
--type string Search type: web (default), image, video, news, discover, googleNews
```
## gsc insights striking-distance [#gsc-insights-striking-distance]
Queries ranking just off the top (average position 4 to 20) with real impressions
```text
gsc insights striking-distance [flags]
```
```bash
gsc insights striking-distance
gsc insights striking-distance --min-impressions 500 --max-position 15 -o json
```
```text
--aggregation string auto, byPage, byProperty, or byNewsShowcasePanel
--data-state string final (default), all (includes preliminary data), or hourly_all
--end string End date YYYY-MM-DD (Pacific Time, inclusive)
--filter stringArray Filter 'DIM OP VALUE'; OP is = != ~ !~ =~ !=~ (contains/regex); repeat to AND
-h, --help help for striking-distance
--last string Window ending at the latest available date: 7d, 28d, 4w, 3m, 16m (default 28d)
--limit int Maximum rows to show (default 50)
--max-position float Highest average position to include (default 20)
--min-impressions float Minimum impressions in the period (default 100)
--min-position float Lowest average position to include (default 4)
--start string Start date YYYY-MM-DD (Pacific Time, inclusive)
--type string Search type: web (default), image, video, news, discover, googleNews
```
## gsc inspect [#gsc-inspect]
URL Inspection: Google's indexed view of URLs (not a live test)
Calls the URL Inspection API for each URL. It reports the indexed version only: there is no live test and
no request-indexing in the API. Google allows 2,000 inspections per property per day and 600 per minute;
batches are paced below that and stop cleanly when a quota is reached.
```text
gsc inspect [URL...] [flags]
```
```bash
gsc inspect https://www.example.com/pricing
gsc inspect --file urls.txt -o json
cat urls.txt | gsc inspect --file - --max 200
```
```text
--file string Read URLs from a file, one per line (- for stdin)
-h, --help help for inspect
--interval duration Pause between inspections (stays under 600 per minute) (default 120ms)
--language string BCP 47 language for issue messages (default en-US)
--max int Inspect at most this many URLs (the per-property daily quota is 2,000) (default 2000)
```
## gsc login [#gsc-login]
Alias of auth login
With no flags, opens Google's consent page in your browser and saves the login in the OS keychain.
Tokens refresh automatically afterwards. --no-browser prints a URL to approve on any device and
asks you to paste the final redirected URL back. --service-account and --adc save non-interactive
credentials for CI. See docs/auth.md for every method.
```text
gsc login [flags]
```
```bash
gsc login
gsc login --no-browser
gsc login --profile work --scope readonly
```
```text
--adc Use Application Default Credentials (gcloud, workload identity federation, metadata server)
--client-secret-file string Use your own Desktop OAuth client (JSON from Google Cloud Console)
-h, --help help for login
--impersonate string With --adc: service account email to impersonate through IAM Credentials
--insecure-storage Store the login in a 0600 plaintext file instead of the OS keychain
--no-browser Headless login: approve on any device, then paste the redirected URL
--no-verify Skip listing properties after login (no API call)
--scope string Search Console access to request: full or readonly (default "full")
--service-account string Save a service account JSON key file for this profile
--subject string With --service-account: Workspace user to impersonate (domain-wide delegation)
```
## gsc performance [#gsc-performance]
Totals for a period: clicks, impressions, CTR, average position
A query with no dimensions, so totals match the Performance report chart (anonymized queries included).
```text
gsc performance [flags]
```
```bash
gsc performance --last 28d --compare previous
gsc performance --type discover --last 3m -o json
```
```text
--aggregation string auto, byPage, byProperty, or byNewsShowcasePanel
--compare string Also query a comparison period: previous or yoy
--data-state string final (default), all (includes preliminary data), or hourly_all
--end string End date YYYY-MM-DD (Pacific Time, inclusive)
--filter stringArray Filter 'DIM OP VALUE'; OP is = != ~ !~ =~ !=~ (contains/regex); repeat to AND
-h, --help help for performance
--last string Window ending at the latest available date: 7d, 28d, 4w, 3m, 16m (default 28d)
--start string Start date YYYY-MM-DD (Pacific Time, inclusive)
--type string Search type: web (default), image, video, news, discover, googleNews
```
## gsc query [#gsc-query]
Search Analytics with every API option (dimensions, filters, types, data state)
Runs searchAnalytics.query. Without dates it covers the last 28 days ending at the latest day with data
(one small extra request finds that day; pass --end to skip it). Results are sorted by clicks by Google.
"complete" in the output means every row Google exposes was fetched; anonymized queries are never included.
```text
gsc query [flags]
```
```bash
gsc query -s sc-domain:example.com -d query -d page --limit 5000 -o csv
gsc query --last 3m -d date --filter 'country = ind' --filter 'query ~ shoes'
gsc query -d hour --data-state hourly_all --last 2d -o json
gsc query -d query --compare previous --limit 500
```
```text
--aggregation string auto, byPage, byProperty, or byNewsShowcasePanel
--all Fetch every row Google exposes for the query
--compare string Also query a comparison period: previous or yoy
--data-state string final (default), all (includes preliminary data), or hourly_all
-d, --dimension strings Group by: date, hour, query, page, country, device, searchAppearance (repeat or comma list)
--end string End date YYYY-MM-DD (Pacific Time, inclusive)
--filter stringArray Filter 'DIM OP VALUE'; OP is = != ~ !~ =~ !=~ (contains/regex); repeat to AND
-h, --help help for query
--last string Window ending at the latest available date: 7d, 28d, 4w, 3m, 16m (default 28d)
--limit int Maximum rows to return (pages of up to 25,000 are fetched) (default 1000)
--print-request Print the request body and send nothing
--raw Print the API response unchanged (single page, no --all or --compare)
--request-file string JSON request body to start from (flags override its fields; - for stdin)
--start string Start date YYYY-MM-DD (Pacific Time, inclusive)
--type string Search type: web (default), image, video, news, discover, googleNews
```
## gsc sitemaps [#gsc-sitemaps]
List, inspect, submit, and delete sitemaps for a property
```text
gsc sitemaps [flags]
```
```text
-h, --help help for sitemaps
```
## gsc sitemaps delete [#gsc-sitemaps-delete]
Delete a sitemap from the report (Google may still crawl it)
```text
gsc sitemaps delete SITEMAP_URL [flags]
```
```bash
gsc sitemaps delete https://www.example.com/old-sitemap.xml --yes
```
```text
-h, --help help for delete
```
## gsc sitemaps get [#gsc-sitemaps-get]
Show one sitemap's status, errors, and warnings
```text
gsc sitemaps get SITEMAP_URL [flags]
```
```bash
gsc sitemaps get https://www.example.com/sitemap.xml
```
```text
-h, --help help for get
```
## gsc sitemaps list [#gsc-sitemaps-list]
List submitted sitemaps
```text
gsc sitemaps list [flags]
```
```bash
gsc sitemaps list
gsc sitemaps list --index https://www.example.com/sitemap_index.xml -o json
```
```text
-h, --help help for list
--index string Only sitemaps listed in this sitemap index URL
```
## gsc sitemaps submit [#gsc-sitemaps-submit]
Submit a sitemap URL for the property
```text
gsc sitemaps submit SITEMAP_URL [flags]
```
```bash
gsc sitemaps submit https://www.example.com/sitemap.xml
gsc sitemaps submit https://www.example.com/sitemap.xml --dry-run
```
```text
-h, --help help for submit
```
## gsc sites [#gsc-sites]
List and manage Search Console properties
```text
gsc sites [flags]
```
```text
-h, --help help for sites
```
## gsc sites add [#gsc-sites-add]
Add a property to your Search Console list (does not verify ownership)
SITE must be exact: sc-domain:example.com or a URL-prefix such as [https://www.example.com/](https://www.example.com/).
Adding a property does not verify it. A URL-prefix inside a domain property you already own is verified
at once; otherwise complete verification in Search Console. The output reports the resulting permission.
```text
gsc sites add SITE [flags]
```
```bash
gsc sites add https://blog.example.com/
gsc sites add sc-domain:example.org --dry-run
```
```text
-h, --help help for add
```
## gsc sites get [#gsc-sites-get]
Show one property (default: the profile's site)
```text
gsc sites get [SITE] [flags]
```
```bash
gsc sites get
gsc sites get https://www.example.com/ -o json
```
```text
-h, --help help for get
```
## gsc sites list [#gsc-sites-list]
List properties you can access and your permission level
```text
gsc sites list [flags]
```
```bash
gsc sites list
gsc sites list -o json
```
```text
-h, --help help for list
```
## gsc sites remove [#gsc-sites-remove]
Remove a property from your Search Console list
```text
gsc sites remove SITE [flags]
```
```bash
gsc sites remove https://old.example.com/ --yes
```
```text
-h, --help help for remove
```
## gsc sites use [#gsc-sites-use]
Set the profile's default site
Exact property identifiers are saved without a network call; a bare host is resolved with one sites.list call.
```text
gsc sites use SITE [flags]
```
```bash
gsc sites use sc-domain:example.com
gsc sites use www.example.com
```
```text
-h, --help help for use
```
## gsc status [#gsc-status]
Alias of auth status
Reads local configuration only. --verify additionally lists properties with the credential (one API call).
```text
gsc status [flags]
```
```bash
gsc status
gsc status --verify
```
```text
-h, --help help for status
--verify Check the credential by listing properties (one API call)
```
## gsc top [#gsc-top]
Top queries, pages, countries, devices, or search appearances
```text
gsc top [flags]
```
```text
-h, --help help for top
```
## gsc top appearance [#gsc-top-appearance]
Search appearance types (filter on one with --filter 'searchAppearance = VALUE' in gsc query)
```text
gsc top appearance [flags]
```
```bash
gsc top appearance --last 3m -o json
```
```text
--aggregation string auto, byPage, byProperty, or byNewsShowcasePanel
--all Fetch every row Google exposes for the query
--compare string Also query a comparison period: previous or yoy
--data-state string final (default), all (includes preliminary data), or hourly_all
--end string End date YYYY-MM-DD (Pacific Time, inclusive)
--filter stringArray Filter 'DIM OP VALUE'; OP is = != ~ !~ =~ !=~ (contains/regex); repeat to AND
-h, --help help for appearance
--last string Window ending at the latest available date: 7d, 28d, 4w, 3m, 16m (default 28d)
--limit int Maximum rows to return (pages of up to 25,000 are fetched) (default 25)
--start string Start date YYYY-MM-DD (Pacific Time, inclusive)
--type string Search type: web (default), image, video, news, discover, googleNews
```
## gsc top countries [#gsc-top-countries]
Clicks and impressions by country (ISO 3166-1 alpha-3)
```text
gsc top countries [flags]
```
```bash
gsc top countries --last 3m
```
```text
--aggregation string auto, byPage, byProperty, or byNewsShowcasePanel
--all Fetch every row Google exposes for the query
--compare string Also query a comparison period: previous or yoy
--data-state string final (default), all (includes preliminary data), or hourly_all
--end string End date YYYY-MM-DD (Pacific Time, inclusive)
--filter stringArray Filter 'DIM OP VALUE'; OP is = != ~ !~ =~ !=~ (contains/regex); repeat to AND
-h, --help help for countries
--last string Window ending at the latest available date: 7d, 28d, 4w, 3m, 16m (default 28d)
--limit int Maximum rows to return (pages of up to 25,000 are fetched) (default 25)
--start string Start date YYYY-MM-DD (Pacific Time, inclusive)
--type string Search type: web (default), image, video, news, discover, googleNews
```
## gsc top devices [#gsc-top-devices]
Clicks and impressions by device
```text
gsc top devices [flags]
```
```bash
gsc top devices --compare yoy
```
```text
--aggregation string auto, byPage, byProperty, or byNewsShowcasePanel
--all Fetch every row Google exposes for the query
--compare string Also query a comparison period: previous or yoy
--data-state string final (default), all (includes preliminary data), or hourly_all
--end string End date YYYY-MM-DD (Pacific Time, inclusive)
--filter stringArray Filter 'DIM OP VALUE'; OP is = != ~ !~ =~ !=~ (contains/regex); repeat to AND
-h, --help help for devices
--last string Window ending at the latest available date: 7d, 28d, 4w, 3m, 16m (default 28d)
--limit int Maximum rows to return (pages of up to 25,000 are fetched) (default 25)
--start string Start date YYYY-MM-DD (Pacific Time, inclusive)
--type string Search type: web (default), image, video, news, discover, googleNews
```
## gsc top pages [#gsc-top-pages]
Top pages by clicks
```text
gsc top pages [flags]
```
```bash
gsc top pages --filter 'page ~ /blog/'
gsc top pages --all -o csv > pages.csv
```
```text
--aggregation string auto, byPage, byProperty, or byNewsShowcasePanel
--all Fetch every row Google exposes for the query
--compare string Also query a comparison period: previous or yoy
--data-state string final (default), all (includes preliminary data), or hourly_all
--end string End date YYYY-MM-DD (Pacific Time, inclusive)
--filter stringArray Filter 'DIM OP VALUE'; OP is = != ~ !~ =~ !=~ (contains/regex); repeat to AND
-h, --help help for pages
--last string Window ending at the latest available date: 7d, 28d, 4w, 3m, 16m (default 28d)
--limit int Maximum rows to return (pages of up to 25,000 are fetched) (default 25)
--start string Start date YYYY-MM-DD (Pacific Time, inclusive)
--type string Search type: web (default), image, video, news, discover, googleNews
```
## gsc top queries [#gsc-top-queries]
Top search queries by clicks
```text
gsc top queries [flags]
```
```bash
gsc top queries --last 7d
gsc top queries --compare previous --limit 50 -o json
```
```text
--aggregation string auto, byPage, byProperty, or byNewsShowcasePanel
--all Fetch every row Google exposes for the query
--compare string Also query a comparison period: previous or yoy
--data-state string final (default), all (includes preliminary data), or hourly_all
--end string End date YYYY-MM-DD (Pacific Time, inclusive)
--filter stringArray Filter 'DIM OP VALUE'; OP is = != ~ !~ =~ !=~ (contains/regex); repeat to AND
-h, --help help for queries
--last string Window ending at the latest available date: 7d, 28d, 4w, 3m, 16m (default 28d)
--limit int Maximum rows to return (pages of up to 25,000 are fetched) (default 25)
--start string Start date YYYY-MM-DD (Pacific Time, inclusive)
--type string Search type: web (default), image, video, news, discover, googleNews
```
## gsc trend [#gsc-trend]
Clicks, impressions, CTR, and position over time
Groups by date (default), hour (last 10 days, preliminary data), ISO week, or calendar month.
Week and month totals are rolled up locally from daily rows: clicks and impressions summed; CTR recomputed from the sums; position weighted by impressions.
```text
gsc trend [flags]
```
```bash
gsc trend --last 3m --by week
gsc trend --by hour --last 2d
gsc trend --last 16m --by month --filter 'page ~ /blog/'
```
```text
--aggregation string auto, byPage, byProperty, or byNewsShowcasePanel
--by string Granularity: date, hour, week, or month (default "date")
--data-state string final (default), all (includes preliminary data), or hourly_all
--end string End date YYYY-MM-DD (Pacific Time, inclusive)
--filter stringArray Filter 'DIM OP VALUE'; OP is = != ~ !~ =~ !=~ (contains/regex); repeat to AND
-h, --help help for trend
--last string Window ending at the latest available date: 7d, 28d, 4w, 3m, 16m (default 28d)
--start string Start date YYYY-MM-DD (Pacific Time, inclusive)
--type string Search type: web (default), image, video, news, discover, googleNews
```
## gsc update [#gsc-update]
Update gsc to the latest release (SHA-256 verified)
Downloads the latest GitHub release for this OS and architecture, verifies it against the release's
checksums.txt, and replaces the running gsc executable. Works on macOS, Linux, and Windows; on Windows
the old executable is renamed to gsc.exe.old and deleted on a later run.
Asks "Update now? \[Y/n]" when stdin is a terminal; --yes skips the question, and --no-input requires
\--yes. --check only reports the current and latest versions. A gsc in a Go bin directory ($GOBIN,
$GOPATH/bin, or \~/go/bin) is not replaced: rebuild it from your checkout instead. --read-only blocks
installing but allows --check.
Update notice: in an interactive terminal, gsc checks the github.com releases page (not the GitHub API,
so its rate limit never applies) for a new release at most once a day and, after a command's output,
prints a notice on stderr. The command that runs the day's check waits up to 1 second after its output
for the answer; other commands never wait. gsc update and update --check store their result in the same
cache. It never checks when stderr is not a terminal, when CI is set, with --quiet, or when
GSC_NO_UPDATE_NOTIFIER or NO_UPDATE_NOTIFIER is set (to anything).
```text
gsc update [flags]
```
```bash
gsc update --check
gsc update
gsc update --yes --no-input
```
```text
--check Only report the current and latest versions (always checks the github.com releases page)
-h, --help help for update
```
## gsc version [#gsc-version]
Print build information
Prints the version, commit, build date, and whether a built-in OAuth client is present. latest and
update_available come from the last release check (see gsc update --help) and appear only when one is
cached; version never uses the network.
```text
gsc version [flags]
```
```bash
gsc version
gsc version -o json
```
```text
-h, --help help for version
```