@twentylabs/gads
v0.3.2
Published
Scriptable CLI for the Google Ads API: profiles, GAQL queries, and validated changes.
Readme
gads
A scriptable CLI for the Google Ads API (REST, v25), with no runtime dependencies.
It does three jobs:
- Profiles. Each Google Ads account is one profile, and any project can use any of them.
gads query. Runs GAQL and prints the rows as a table, CSV, JSON or JSONL, for scripts and reports.gads apply. Changes the account from a JSON change file.- By default it only asks Google to validate the change against the live account (
validateOnly), and nothing is written. --yeswrites the change, reads the account again, and saves the state before and after as evidence.
- By default it only asks Google to validate the change against the live account (
For conversational analysis, use Google's official read-only MCP server.
gads mcp-credentials hands it this CLI's OAuth credentials, so you only log in once.
Setup (once per Google Cloud project)
Google sunset developer tokens on 2026-09-09. API access now belongs to the Google Cloud project that owns your OAuth client. You need neither a manager account nor the API Center.
Cloud project: in https://console.cloud.google.com, create a project or reuse one.
- Under APIs & Services → Library, enable Google Ads API.
- The project must be on a paid billing account, or have no billing at all. Google rejects access applications from projects on the free trial.
Access level: open https://console.cloud.google.com/google/ads-apis/overview.
| Level | What it reaches | | --- | --- | | Test | Test accounts only. This is what a new project gets. | | Explorer | Production accounts, up to 2,880 operations a day. This is enough for one person's management work. Apply here; Google often grants it automatically. | | Basic | 15,000 operations a day. Needs brand verification. |
If you call a real account with Test access, the API returns
CLOUD_PROJECT_NOT_APPROVED_FOR_PRODUCTION.OAuth consent screen: use user type External and add yourself as a test user.
- Then set the publishing status to "In production". While it is in "Testing", Google expires refresh tokens after 7 days.
- An unverified app only shows a warning screen, which you can click through.
OAuth client: under Credentials → Create credentials → OAuth client ID, choose Desktop app, then download the JSON file.
Install
Requires Node 20 or later. Published on npm as @twentylabs/gads.
npm install -g @twentylabs/gads@^0.3
gads version@^0.3installs the newest release in the0.3.xline.- With nvm, global packages belong to one Node version, so reinstall after switching Node.
- Before 0.3.1, gads was installed from git tags (
git+ssh://…/gads.git#v0.3.0); uninstall that copy first (npm uninstall -g gads).
Versions and upgrading
Releases are npm versions of @twentylabs/gads, tagged vX.Y.Z in git, with notes in CHANGELOG.md and on the GitHub releases page. Below 1.0.0, a minor bump (0.3 → 0.4) may break change files or scripts; a patch never does.
npm install -g @twentylabs/gads@^0.3 # newest 0.3.x
npm install -g @twentylabs/[email protected] # pin or roll back
npm ls -g @twentylabs/gads # what is installedConsumers declare what they need. A project that keeps change files puts the version range in each file, and its README says gads >= X.Y.Z:
{ "gads": ">=0.2.0", "title": "...", ... }An older gads refuses that file instead of misreading it, and prints the exact install command: needs gads >=0.2.0; you have 0.1.0. Upgrade: npm install -g "@twentylabs/gads@>=0.2.0". "schemaVersion" (default 1) guards the file format itself.
Cutting a release (maintainers):
- In the release PR, bump
package.jsonand add the## [X.Y.Z]entry to CHANGELOG.md. - Merge to
main. - Run
scripts/release.sh X.Y.Z. It refuses unless main is clean and pushed, the version is above the last tag, the entry exists and the tests pass. It pushes the tagvX.Y.Z; thereleaseworkflow then publishes that version to npm with trusted publishing (no token).
Log in
gads auth login --profile my-app \
--customer-id 123-456-7890 \
--client-json ~/Downloads/client_secret_XXXX.json- A browser opens. Approve access with the Google user who can see the Ads account.
- If that user reaches the account through a manager account, add
--login-customer-id <manager ID>. - The first profile you create becomes the default. Pass
--defaultto make any other profile the default.
gads auth status # who you are, what you reach, the account currency and time zone
gads profiles # list profiles
gads profiles use other # change the default profileProfiles live in ~/.config/gads/config.json, which only you can read.
Any profile field can also come from an environment variable instead:
GADS_CUSTOMER_IDGADS_LOGIN_CUSTOMER_IDGADS_CLIENT_IDGADS_CLIENT_SECRETGADS_REFRESH_TOKENGADS_PROFILE, to choose a profileGADS_CONFIG, to use a different config file
Read
gads campaigns
gads query "SELECT campaign.name, metrics.cost_micros, metrics.conversions
FROM campaign WHERE segments.date DURING LAST_7_DAYS" --format csv --out week.csv
gads query --file report.gaql --profile other- In a terminal the output defaults to a table; in a pipe or with
--outit defaults to JSON. - Every
*_microscolumn gets a readable money column next to it. --format jsonreturns Google's rows untouched.
Change
A change file is a list of googleAds:mutate operations, plus the context a reviewer needs:
{
"gads": ">=0.2.0",
"title": "Pause Android TH",
"reason": "250-install test reached; CPA 3x VN",
"customerId": "123-456-7890",
"operations": [
{ "campaignOperation": {
"update": { "resourceName": "customers/1234567890/campaigns/111", "status": "PAUSED" },
"updateMask": "status" } }
],
"verify": [
"SELECT campaign.id, campaign.status FROM campaign WHERE campaign.id = 111"
]
}gads apply pause-th.json # dry run: shows the operations and current state, then Google validates
gads apply pause-th.json --yes # writes, re-reads verify, saves pause-th.result.jsonReady-made templates are in examples/: pause, budget, target CPA, and swapping a country.
Safety rules, enforced before anything is sent
| Rule | Why |
| --- | --- |
| customerId must match the profile | A change written for one account cannot run against another. |
| Every resource name must belong to that customer | Same reason. |
| No REMOVED status, and no removing campaigns, budgets, ad groups or ads, unless allow.remove is set | Google never lets you undo a remove. |
| Micros must be positive whole numbers, and in VND whole dong (multiples of 1,000,000) | Catches the most common budget typo. |
| No updating a shared budget unless allow.sharedBudget is set | It would move money in every campaign that uses the budget. |
| No change that leaves a campaign with zero positive locations | A campaign with no locations serves worldwide. |
| Google validateOnly must pass before --yes writes anything | Google checks the change against the live account. |
| All operations go in one atomic request (partialFailure: false) | Either every operation lands or none does. |
| A write is never retried | A retried create can land twice. |
| A change that already has a .result.json is refused without --force | Prevents applying the same change twice. |
| --read-only or GADS_READ_ONLY=1 refuses every write; validation still runs | Lets you work on an account without any risk of changing it. |
Every apply run appends a line to ~/.config/gads/audit.jsonl with the mode, the file's SHA-256, the account and the outcome.
Official MCP server (read-only analysis)
gads mcp-credentials --profile my-app --out ~/.config/gads/adc-my-app.jsonThis writes the profile's OAuth credentials as an authorized_user file and prints the matching claude mcp add command.
- Both tools use the same Cloud project, so they share its daily operation quota.
- If the profile's refresh token expires, both stop working at once.
Exit codes
| Code | Meaning |
| --- | --- |
| 0 | OK |
| 1 | Usage error or refusal |
| 2 | The API returned an error |
| 6 | Refused by read-only mode |
Development
pnpm test # node --test; never calls GoogleThe API version is pinned in lib/api.js. v25 sunsets around August 2027.
The reference implementations studied (all Apache-2.0) were google-ads-open-cli, BeckerEnterprises/google-ads-api-cli, mcc-gaql-rs, icoolqin/google-ads-cli and Benmore-Studio/gads. Their designs were consulted; no code was copied.
