Google Analytics
Architecture
ga4 is a standalone Python CLI that wraps three GA4 APIs:
- Data API (v1beta + v1alpha) —
ga4 data … — reporting, realtime, metadata, funnels, audience exports
- Admin API (v1beta + v1alpha) —
ga4 admin … — accounts, properties, streams, key events, custom dims/metrics, measurement secrets, links, audiences, access bindings, annotations
- Measurement Protocol —
ga4 mp … — event ingestion (send + validate against debug endpoint)
All commands emit JSON to stdout with indent=2. Errors go to stderr as plain text with exit 1. Destructive operations require --yes/-y on the CLI; the skill adds an additional user-confirmation layer (see Destructive operations below).
Auth is OAuth-user-only via ga4 auth login --client-secret <Desktop-OAuth-client.json>. The CLI owns its own state at ~/.config/skills/ga4/ (not under the skill's path — ga4 is usable outside this skill).
Before First Use
- Install —
uv sync --project /abs/path/to/skills/google-analytics
- Create an OAuth Desktop client in GCP — https://console.cloud.google.com/apis/credentials → Create Credentials → OAuth client ID → Desktop app → download the JSON. ADC (
gcloud auth application-default login) is deliberately not supported — Google is phasing out analytics scopes on the default gcloud client ID.
- Auth —
ga4 auth login --client-secret /path/to/downloaded.json. A browser opens for consent. On a headless machine, open an SSH tunnel first: ssh -L 8086:localhost:8086 <this-host> and open the printed URL on a machine with a browser. Scopes default to readonly + edit + manage-users; override with --scope (repeatable) for provision / user-deletion.
- Grant GA access — in GA Admin → Access Management, add the Google account you just authed with as Viewer (reads) or Editor/Administrator (writes, user management). See
references/setup.md for click-path.
- Default property (optional, recommended) —
ga4 config set-property 123456789.
- Verify —
ga4 doctor should be all-green.
Canonical invocation
uv run --project /path/to/skills/google-analytics ga4 <command> [args]
For brevity, examples below use ga4 directly. When running from outside the skill directory, always use the full uv run --project <abs-path> form.
Destructive operations
Before running any delete or archive subcommand, show the user the exact command (including target resource name) and ask for confirmation in conversation. Only pass --yes/-y after an affirmative answer in the current turn. Never chain multiple destructive ops without re-confirming each one.
The CLI's --yes flag is a non-interactive guardrail. The skill's job is to make sure a human explicitly agrees before a properties delete, key-events delete, custom-dimensions archive, custom-metrics archive, audiences archive, access-bindings delete, access-bindings batch-delete, annotations delete, data-streams delete, measurement-secrets delete, links firebase-delete, links ads-delete, links bigquery-delete, properties submit-user-deletion, or accounts delete is executed. If the user is in a hurry, still ask — a single clear confirmation is cheap compared to clobbering a production property.
Command index
ga4 auth, ga4 config, ga4 doctor
| Command |
Purpose |
Min scope |
ga4 auth login --client-secret <path> |
OAuth Desktop flow |
n/a |
ga4 auth status |
Report OAuth credential status |
n/a |
ga4 auth whoami |
Scopes and expiry on the current token |
readonly |
ga4 auth logout |
Remove OAuth credentials |
n/a |
ga4 config set-property <id> |
Persist default property (stored as properties/<id>) |
n/a |
ga4 config get default-property |
Read default |
n/a |
ga4 config show |
Dump all config |
n/a |
ga4 doctor |
Health check (config, OAuth credentials, scopes, Admin API ping) |
readonly |
Property ids accept either 123456789 or properties/123456789.
ga4 data — Data API
| Command |
Purpose |
Min scope |
ga4 data run-report -p <id> -d <dims> -m <metrics> -s <start> -e <end> |
Standard report |
readonly |
ga4 data run-pivot-report -p <id> --request-json @body.json |
Pivot report |
readonly |
ga4 data batch-run-reports -p <id> --requests-json @bodies.json |
Up to 5 reports in one call |
readonly |
ga4 data batch-run-pivot-reports -p <id> --requests-json @bodies.json |
Up to 5 pivot reports |
readonly |
ga4 data run-realtime-report -p <id> -d <dims> -m <metrics> |
Last 30 (or 60 for GA360) minutes |
readonly |
ga4 data run-funnel-report -p <id> --request-json @funnel.json |
Funnel analysis. Alpha. |
readonly |
ga4 data check-compatibility -p <id> -d <dims> -m <metrics> |
Validate dim+metric combo |
readonly |
ga4 data get-metadata -p <id> |
Dimensions + metrics catalog for the property (pass properties/0 for universal) |
readonly |
ga4 data audience-exports create --audience <name> -d <dims> |
Create audience export |
readonly |
ga4 data audience-exports get <name> / list / query <name> |
Audience export lifecycle |
readonly |
Common run-report flags: --dimension-filter-json, --metric-filter-json, --order-by-json, --metric-aggregation, --cohort-spec-json, --comparisons-json, --limit, --offset, --currency-code, --keep-empty-rows, --return-property-quota, --request-json (full body override).
Filter/order/cohort JSON can be inline (--dimension-filter-json '{"filter":...}') or a file (--dimension-filter-json @/tmp/f.json). See references/data-api.md for the filter grammar.
ga4 admin accounts — Accounts
| Command |
Purpose |
Min scope |
accounts list / get <id> / summaries-list |
Read accounts |
readonly |
accounts update <id> --display-name … |
Rename |
edit |
accounts delete <id> --yes |
Soft-delete |
edit |
accounts search-change-history <id> |
Audit trail |
readonly |
accounts run-access-report <id> -d <dims> -m <metrics> |
Who-accessed-what audit |
readonly |
accounts get-data-sharing-settings <id> |
Read sharing settings |
readonly |
accounts provision-ticket --display-name … --redirect-uri … |
New-account provisioning ticket |
edit |
ga4 admin properties — Properties
| Command |
Purpose |
Min scope |
properties list --account <id> / get -p <id> |
Read properties |
readonly |
properties create --parent <account> --display-name … --time-zone … |
Create |
edit |
properties update -p <id> --display-name … |
Update (or --body-json + --update-mask) |
edit |
properties delete -p <id> --yes |
Soft-delete |
edit |
properties get-data-retention -p <id> / update-data-retention … |
Retention settings |
edit |
properties get-attribution-settings -p <id> / update… |
Attribution model. Alpha. |
edit |
properties get-signals-settings -p <id> / update… |
Google Signals. Alpha. |
edit |
properties run-access-report -p <id> … |
Access audit at property scope |
readonly |
properties search-change-history -p <id> |
Change history |
readonly |
properties acknowledge-user-data -p <id> --acknowledgement … |
Prereq for Measurement Protocol secrets |
edit |
properties submit-user-deletion -p <id> --user-id … |
GDPR deletion. Alpha. Irreversible. |
user.deletion |
ga4 admin data-streams, measurement-secrets
| Command |
Purpose |
Min scope |
data-streams list -p <id> / get -p <id> <stream> |
Read streams |
readonly |
data-streams create -p <id> --type WEB_DATA_STREAM --display-name … --uri … |
Create web stream |
edit |
data-streams create --type ANDROID_APP_DATA_STREAM --package-name … |
Android |
edit |
data-streams create --type IOS_APP_DATA_STREAM --bundle-id … |
iOS |
edit |
data-streams update -p <id> <stream> --display-name … |
Rename |
edit |
data-streams delete -p <id> <stream> --yes |
Delete |
edit |
data-streams get-global-site-tag -p <id> <stream> |
gtag snippet. Alpha. |
readonly |
data-streams get-enhanced-measurement -p <id> <stream> / update… |
EM settings. Alpha. |
edit |
data-streams get-data-redaction -p <id> <stream> / update… |
PII redaction. Alpha. |
edit |
measurement-secrets list -p <id> <stream> / get, create --display-name …, update, delete |
MP secrets CRUD (requires acknowledge-user-data first) |
edit |
ga4 admin key-events, custom-dimensions, custom-metrics
| Command |
Purpose |
Min scope |
key-events list -p <id> / get / create --event-name … --counting-method … / update / delete --yes |
Conversion events (replaces conversionEvents) |
edit |
custom-dimensions list -p <id> / get / create --parameter-name … --display-name … --scope EVENT / update / archive --yes |
Custom dims |
edit |
custom-metrics list -p <id> / get / create --parameter-name … --measurement-unit … / update / archive --yes |
Custom metrics |
edit |
ga4 admin links — Third-party links
| Command |
Purpose |
Min scope |
links firebase-list -p <id> / firebase-create --firebase-project projects/… / firebase-delete <id> --yes |
Firebase links |
edit |
links ads-list -p <id> / ads-create --customer-id … / ads-update / ads-delete <id> --yes |
Google Ads links |
edit |
links bigquery-list -p <id> / bigquery-get / bigquery-create --project projects/… [--daily-export …] / bigquery-update / bigquery-delete <id> --yes |
BigQuery links. Alpha. |
edit |
ga4 admin audiences, access-bindings, annotations — all alpha
| Command |
Purpose |
Min scope |
audiences list -p <id> / get / create --body-json @audience.json / archive --yes |
Audiences. Alpha. |
edit |
access-bindings list --account <id> (or --property) / get / create --user <email> -r predefinedRoles/admin / update / delete --yes |
Per-user roles. Alpha. |
manage.users |
access-bindings batch-{create,get,update,delete} --bindings-json @list.json |
Batch operations. Alpha. |
manage.users |
annotations list -p <id> / get / create --title … --annotation-date YYYY-MM-DD (or --start-date/--end-date) / update / delete --yes |
Reporting-data annotations. Alpha. |
edit |
Alpha endpoints may change shape without notice; keep an eye on google-analytics-admin release notes if you script against them.
ga4 mp — Measurement Protocol
| Command |
Purpose |
ga4 mp send --measurement-id G-XXX --api-secret … --events-json @events.json |
Fire-and-forget send to /mp/collect |
ga4 mp validate --measurement-id G-XXX --api-secret … --events-json @events.json |
Debug endpoint — returns per-event validation findings |
Single-event shortcut: --event-name purchase --event-params '{"value":12.34,"currency":"USD"}'. For Firebase/app streams, replace --measurement-id with --firebase-app-id. --endpoint eu routes through the EU regional endpoint.
MP limits (enforced locally before sending): 25 events/request, 40-char event names, 25 params/event. See references/admin-api.md for full limits including param value length.
Writing reports
# Last 7 days of active users by country
ga4 data run-report -p 123456789 -d country -m activeUsers -s 7daysAgo -e today
# Week-over-week comparison with two date ranges via request JSON
cat <<'EOF' > /tmp/wow.json
{
"dimensions": [{"name": "deviceCategory"}],
"metrics": [{"name": "engagementRate"}, {"name": "sessions"}],
"date_ranges": [
{"start_date": "7daysAgo", "end_date": "yesterday", "name": "this_week"},
{"start_date": "14daysAgo", "end_date": "8daysAgo", "name": "last_week"}
]
}
EOF
ga4 data run-report -p 123456789 --request-json @/tmp/wow.json
# Filter: only mobile, only pages matching /blog/*
cat <<'EOF' > /tmp/filter.json
{"andGroup": {"expressions": [
{"filter": {"fieldName": "deviceCategory", "stringFilter": {"value": "mobile"}}},
{"filter": {"fieldName": "pagePath", "stringFilter": {"matchType": "BEGINS_WITH", "value": "/blog/"}}}
]}}
EOF
ga4 data run-report -p 123456789 -d pagePath -m sessions -s 7daysAgo -e today \
--dimension-filter-json @/tmp/filter.json
For deep filter grammar (AND/OR/NOT, string/numeric/between/in-list filter types), the valid metric-aggregation set, cohort specs, and the "quota token" math, read references/data-api.md.
Admin safety
- Always
list/get first before update/delete/archive. Validate you're targeting the right resource.
- Use
search-change-history to audit recent changes before making new ones, and to find who made the last change to a resource.
- Alpha endpoints may change shape. If you get an unexpected schema error, check the
google-analytics-admin package version and the alpha changelog.
update_mask — most admin updates require a field mask. The CLI auto-computes it from flag-based updates, but for --body-json updates you must pass --update-mask explicitly.
- Scopes — the CLI requests the union of scopes its commands need. If a command 403s with "insufficient permissions", re-auth with a broader scope (see Before First Use).
Funnel / audience / access patterns
- Funnels (
run-funnel-report) are built as an array of steps, each with a filter expression. The response has two sub-reports — funnel table and funnel visualization. See references/data-api.md for step-design patterns (sequential vs. any-order steps, breakdown dimensions, continuous vs. direct).
- Audiences (
audiences create) have a deeply nested schema (filter clauses, scopes, sequences). Build the JSON separately and pass --body-json @audience.json. Start from an existing audience (audiences get) to see the shape.
- Access bindings use
predefinedRoles/* role names. Common: predefinedRoles/no-cost-data, predefinedRoles/read-and-analyze, predefinedRoles/editor, predefinedRoles/admin. Changes at account scope apply to all properties.
Interpreting output
Engagement benchmarks, attribution model differences, funnel drop-off heuristics, audience sequence vs. condition patterns, realtime reporting caveats, and BigQuery long-term-storage tradeoffs are all in references/playbook.md. Read it before drawing conclusions about whether numbers are "good" — GA4's sampling and attribution defaults differ meaningfully from UA and from other analytics tools.
Troubleshooting
- "No credentials. Run: ga4 auth login …" — Exactly what it says. Get a Desktop OAuth client JSON from GCP and log in.
- "Permission denied" — The Google account you auth'd with doesn't have GA access. Grant Viewer/Editor in GA Admin → Access Management. Verify with
ga4 admin accounts summaries-list.
- "Quota exhausted" — Pass
--return-property-quota on a report to see live consumption. Data API tokens reset hourly/daily. GA360 gets 10x limits.
- "insufficient_scope" — The stored token was obtained with fewer scopes than this command needs. Re-run
ga4 auth login --client-secret <path> --scope <extra-scope> (repeatable) to include them.
- "No property id" — Pass
--property/-p, set GA_PROPERTY_ID, or ga4 config set-property <id>.
- Alpha schema changed — Pin
google-analytics-admin and google-analytics-data in pyproject.toml, then uv sync.
- Realtime report is empty — The realtime window is only the last 30 minutes (60 for GA360). If there's genuinely no active traffic, the report will be empty; this is correct.
- MP send returned 2xx but the event didn't appear —
mp send always returns 2xx regardless of validity. Use mp validate first to check for rejection reasons.
Configuration files
~/.config/skills/ga4/config.json — default property and other CLI settings
~/.config/skills/ga4/credentials.json — OAuth user creds (written by ga4 auth login)
- Override the config dir with
GA4_CONFIG_DIR=/path/to/dir
Migrating from earlier versions
If upgrading from a build that stored config at ~/.config/ga4/, run ga4 config migrate --apply to move the credentials and config to the new location. ga4 auth status emits a deprecation_warning until the migration runs.
1---2name: google-analytics3description: Query GA4 reports (users, sessions, conversions, funnels, realtime), manage properties / data streams / key events / custom dimensions / audiences / access bindings, and send Measurement Protocol events via the `ga4` CLI. Use this skill whenever the user mentions GA4, Google Analytics, property IDs starting with `properties/`, tracking events, engagement or traffic metrics, attribution, conversions, key events, audiences, BigQuery links, access roles, or realtime users — even if they don't explicitly say "GA4". Do not use for Google Search Console (see google-search-console skill) or generic web analytics where the source isn't GA4 (ask first).4---56# Google Analytics78## Architecture910`ga4` is a standalone Python CLI that wraps three GA4 APIs:1112- **Data API** (v1beta + v1alpha) — `ga4 data …` — reporting, realtime, metadata, funnels, audience exports13- **Admin API** (v1beta + v1alpha) — `ga4 admin …` — accounts, properties, streams, key events, custom dims/metrics, measurement secrets, links, audiences, access bindings, annotations14- **Measurement Protocol** — `ga4 mp …` — event ingestion (send + validate against debug endpoint)1516All commands emit JSON to stdout with `indent=2`. Errors go to stderr as plain text with exit 1. Destructive operations require `--yes/-y` on the CLI; the skill adds an additional user-confirmation layer (see **Destructive operations** below).1718Auth is OAuth-user-only via `ga4 auth login --client-secret <Desktop-OAuth-client.json>`. The CLI owns its own state at `~/.config/skills/ga4/` (not under the skill's path — `ga4` is usable outside this skill).1920## Before First Use21221. **Install** — `uv sync --project /abs/path/to/skills/google-analytics`232. **Create an OAuth Desktop client in GCP** — https://console.cloud.google.com/apis/credentials → Create Credentials → OAuth client ID → Desktop app → download the JSON. ADC (`gcloud auth application-default login`) is deliberately not supported — Google is phasing out analytics scopes on the default gcloud client ID.243. **Auth** — `ga4 auth login --client-secret /path/to/downloaded.json`. A browser opens for consent. On a headless machine, open an SSH tunnel first: `ssh -L 8086:localhost:8086 <this-host>` and open the printed URL on a machine with a browser. Scopes default to readonly + edit + manage-users; override with `--scope` (repeatable) for provision / user-deletion.254. **Grant GA access** — in GA Admin → Access Management, add the Google account you just authed with as Viewer (reads) or Editor/Administrator (writes, user management). See `references/setup.md` for click-path.265. **Default property** (optional, recommended) — `ga4 config set-property 123456789`.276. **Verify** — `ga4 doctor` should be all-green.2829## Canonical invocation3031```bash32uv run --project /path/to/skills/google-analytics ga4 <command> [args]33```3435For brevity, examples below use `ga4` directly. When running from outside the skill directory, always use the full `uv run --project <abs-path>` form.3637## Destructive operations3839**Before running any `delete` or `archive` subcommand, show the user the exact command (including target resource name) and ask for confirmation in conversation. Only pass `--yes/-y` after an affirmative answer in the current turn. Never chain multiple destructive ops without re-confirming each one.**4041The CLI's `--yes` flag is a non-interactive guardrail. The skill's job is to make sure a human explicitly agrees before a `properties delete`, `key-events delete`, `custom-dimensions archive`, `custom-metrics archive`, `audiences archive`, `access-bindings delete`, `access-bindings batch-delete`, `annotations delete`, `data-streams delete`, `measurement-secrets delete`, `links firebase-delete`, `links ads-delete`, `links bigquery-delete`, `properties submit-user-deletion`, or `accounts delete` is executed. If the user is in a hurry, still ask — a single clear confirmation is cheap compared to clobbering a production property.4243## Command index4445### `ga4 auth`, `ga4 config`, `ga4 doctor`4647| Command | Purpose | Min scope |48|---|---|---|49| `ga4 auth login --client-secret <path>` | OAuth Desktop flow | n/a |50| `ga4 auth status` | Report OAuth credential status | n/a |51| `ga4 auth whoami` | Scopes and expiry on the current token | readonly |52| `ga4 auth logout` | Remove OAuth credentials | n/a |53| `ga4 config set-property <id>` | Persist default property (stored as `properties/<id>`) | n/a |54| `ga4 config get default-property` | Read default | n/a |55| `ga4 config show` | Dump all config | n/a |56| `ga4 doctor` | Health check (config, OAuth credentials, scopes, Admin API ping) | readonly |5758Property ids accept either `123456789` or `properties/123456789`.5960### `ga4 data` — Data API6162| Command | Purpose | Min scope |63|---|---|---|64| `ga4 data run-report -p <id> -d <dims> -m <metrics> -s <start> -e <end>` | Standard report | readonly |65| `ga4 data run-pivot-report -p <id> --request-json @body.json` | Pivot report | readonly |66| `ga4 data batch-run-reports -p <id> --requests-json @bodies.json` | Up to 5 reports in one call | readonly |67| `ga4 data batch-run-pivot-reports -p <id> --requests-json @bodies.json` | Up to 5 pivot reports | readonly |68| `ga4 data run-realtime-report -p <id> -d <dims> -m <metrics>` | Last 30 (or 60 for GA360) minutes | readonly |69| `ga4 data run-funnel-report -p <id> --request-json @funnel.json` | Funnel analysis. **Alpha**. | readonly |70| `ga4 data check-compatibility -p <id> -d <dims> -m <metrics>` | Validate dim+metric combo | readonly |71| `ga4 data get-metadata -p <id>` | Dimensions + metrics catalog for the property (pass `properties/0` for universal) | readonly |72| `ga4 data audience-exports create --audience <name> -d <dims>` | Create audience export | readonly |73| `ga4 data audience-exports get <name>` / `list` / `query <name>` | Audience export lifecycle | readonly |7475Common `run-report` flags: `--dimension-filter-json`, `--metric-filter-json`, `--order-by-json`, `--metric-aggregation`, `--cohort-spec-json`, `--comparisons-json`, `--limit`, `--offset`, `--currency-code`, `--keep-empty-rows`, `--return-property-quota`, `--request-json` (full body override).7677Filter/order/cohort JSON can be inline (`--dimension-filter-json '{"filter":...}'`) or a file (`--dimension-filter-json @/tmp/f.json`). See `references/data-api.md` for the filter grammar.7879### `ga4 admin accounts` — Accounts8081| Command | Purpose | Min scope |82|---|---|---|83| `accounts list` / `get <id>` / `summaries-list` | Read accounts | readonly |84| `accounts update <id> --display-name …` | Rename | edit |85| `accounts delete <id> --yes` | Soft-delete | edit |86| `accounts search-change-history <id>` | Audit trail | readonly |87| `accounts run-access-report <id> -d <dims> -m <metrics>` | Who-accessed-what audit | readonly |88| `accounts get-data-sharing-settings <id>` | Read sharing settings | readonly |89| `accounts provision-ticket --display-name … --redirect-uri …` | New-account provisioning ticket | edit |9091### `ga4 admin properties` — Properties9293| Command | Purpose | Min scope |94|---|---|---|95| `properties list --account <id>` / `get -p <id>` | Read properties | readonly |96| `properties create --parent <account> --display-name … --time-zone …` | Create | edit |97| `properties update -p <id> --display-name …` | Update (or `--body-json` + `--update-mask`) | edit |98| `properties delete -p <id> --yes` | Soft-delete | edit |99| `properties get-data-retention -p <id>` / `update-data-retention …` | Retention settings | edit |100| `properties get-attribution-settings -p <id>` / `update…` | Attribution model. **Alpha**. | edit |101| `properties get-signals-settings -p <id>` / `update…` | Google Signals. **Alpha**. | edit |102| `properties run-access-report -p <id> …` | Access audit at property scope | readonly |103| `properties search-change-history -p <id>` | Change history | readonly |104| `properties acknowledge-user-data -p <id> --acknowledgement …` | Prereq for Measurement Protocol secrets | edit |105| `properties submit-user-deletion -p <id> --user-id …` | GDPR deletion. **Alpha**. Irreversible. | user.deletion |106107### `ga4 admin data-streams`, `measurement-secrets`108109| Command | Purpose | Min scope |110|---|---|---|111| `data-streams list -p <id>` / `get -p <id> <stream>` | Read streams | readonly |112| `data-streams create -p <id> --type WEB_DATA_STREAM --display-name … --uri …` | Create web stream | edit |113| `data-streams create --type ANDROID_APP_DATA_STREAM --package-name …` | Android | edit |114| `data-streams create --type IOS_APP_DATA_STREAM --bundle-id …` | iOS | edit |115| `data-streams update -p <id> <stream> --display-name …` | Rename | edit |116| `data-streams delete -p <id> <stream> --yes` | Delete | edit |117| `data-streams get-global-site-tag -p <id> <stream>` | gtag snippet. **Alpha**. | readonly |118| `data-streams get-enhanced-measurement -p <id> <stream>` / `update…` | EM settings. **Alpha**. | edit |119| `data-streams get-data-redaction -p <id> <stream>` / `update…` | PII redaction. **Alpha**. | edit |120| `measurement-secrets list -p <id> <stream>` / `get`, `create --display-name …`, `update`, `delete` | MP secrets CRUD (requires `acknowledge-user-data` first) | edit |121122### `ga4 admin key-events`, `custom-dimensions`, `custom-metrics`123124| Command | Purpose | Min scope |125|---|---|---|126| `key-events list -p <id>` / `get` / `create --event-name … --counting-method …` / `update` / `delete --yes` | Conversion events (replaces `conversionEvents`) | edit |127| `custom-dimensions list -p <id>` / `get` / `create --parameter-name … --display-name … --scope EVENT` / `update` / `archive --yes` | Custom dims | edit |128| `custom-metrics list -p <id>` / `get` / `create --parameter-name … --measurement-unit …` / `update` / `archive --yes` | Custom metrics | edit |129130### `ga4 admin links` — Third-party links131132| Command | Purpose | Min scope |133|---|---|---|134| `links firebase-list -p <id>` / `firebase-create --firebase-project projects/…` / `firebase-delete <id> --yes` | Firebase links | edit |135| `links ads-list -p <id>` / `ads-create --customer-id …` / `ads-update` / `ads-delete <id> --yes` | Google Ads links | edit |136| `links bigquery-list -p <id>` / `bigquery-get` / `bigquery-create --project projects/… [--daily-export …]` / `bigquery-update` / `bigquery-delete <id> --yes` | BigQuery links. **Alpha**. | edit |137138### `ga4 admin audiences`, `access-bindings`, `annotations` — all alpha139140| Command | Purpose | Min scope |141|---|---|---|142| `audiences list -p <id>` / `get` / `create --body-json @audience.json` / `archive --yes` | Audiences. **Alpha**. | edit |143| `access-bindings list --account <id>` (or `--property`) / `get` / `create --user <email> -r predefinedRoles/admin` / `update` / `delete --yes` | Per-user roles. **Alpha**. | manage.users |144| `access-bindings batch-{create,get,update,delete} --bindings-json @list.json` | Batch operations. **Alpha**. | manage.users |145| `annotations list -p <id>` / `get` / `create --title … --annotation-date YYYY-MM-DD` (or `--start-date/--end-date`) / `update` / `delete --yes` | Reporting-data annotations. **Alpha**. | edit |146147Alpha endpoints may change shape without notice; keep an eye on `google-analytics-admin` release notes if you script against them.148149### `ga4 mp` — Measurement Protocol150151| Command | Purpose |152|---|---|153| `ga4 mp send --measurement-id G-XXX --api-secret … --events-json @events.json` | Fire-and-forget send to `/mp/collect` |154| `ga4 mp validate --measurement-id G-XXX --api-secret … --events-json @events.json` | Debug endpoint — returns per-event validation findings |155156Single-event shortcut: `--event-name purchase --event-params '{"value":12.34,"currency":"USD"}'`. For Firebase/app streams, replace `--measurement-id` with `--firebase-app-id`. `--endpoint eu` routes through the EU regional endpoint.157158MP limits (enforced locally before sending): 25 events/request, 40-char event names, 25 params/event. See `references/admin-api.md` for full limits including param value length.159160## Writing reports161162```bash163# Last 7 days of active users by country164ga4 data run-report -p 123456789 -d country -m activeUsers -s 7daysAgo -e today165166# Week-over-week comparison with two date ranges via request JSON167cat <<'EOF' > /tmp/wow.json168{169 "dimensions": [{"name": "deviceCategory"}],170 "metrics": [{"name": "engagementRate"}, {"name": "sessions"}],171 "date_ranges": [172 {"start_date": "7daysAgo", "end_date": "yesterday", "name": "this_week"},173 {"start_date": "14daysAgo", "end_date": "8daysAgo", "name": "last_week"}174 ]175}176EOF177ga4 data run-report -p 123456789 --request-json @/tmp/wow.json178179# Filter: only mobile, only pages matching /blog/*180cat <<'EOF' > /tmp/filter.json181{"andGroup": {"expressions": [182 {"filter": {"fieldName": "deviceCategory", "stringFilter": {"value": "mobile"}}},183 {"filter": {"fieldName": "pagePath", "stringFilter": {"matchType": "BEGINS_WITH", "value": "/blog/"}}}184]}}185EOF186ga4 data run-report -p 123456789 -d pagePath -m sessions -s 7daysAgo -e today \187 --dimension-filter-json @/tmp/filter.json188```189190For deep filter grammar (AND/OR/NOT, string/numeric/between/in-list filter types), the valid metric-aggregation set, cohort specs, and the "quota token" math, read `references/data-api.md`.191192## Admin safety193194- **Always `list`/`get` first** before `update`/`delete`/`archive`. Validate you're targeting the right resource.195- **Use `search-change-history`** to audit recent changes before making new ones, and to find who made the last change to a resource.196- **Alpha endpoints may change shape.** If you get an unexpected schema error, check the `google-analytics-admin` package version and the alpha changelog.197- **`update_mask`** — most admin updates require a field mask. The CLI auto-computes it from flag-based updates, but for `--body-json` updates you must pass `--update-mask` explicitly.198- **Scopes** — the CLI requests the union of scopes its commands need. If a command 403s with "insufficient permissions", re-auth with a broader scope (see **Before First Use**).199200## Funnel / audience / access patterns201202- **Funnels (`run-funnel-report`)** are built as an array of steps, each with a filter expression. The response has two sub-reports — funnel table and funnel visualization. See `references/data-api.md` for step-design patterns (sequential vs. any-order steps, breakdown dimensions, continuous vs. direct).203- **Audiences** (`audiences create`) have a deeply nested schema (filter clauses, scopes, sequences). Build the JSON separately and pass `--body-json @audience.json`. Start from an existing audience (`audiences get`) to see the shape.204- **Access bindings** use `predefinedRoles/*` role names. Common: `predefinedRoles/no-cost-data`, `predefinedRoles/read-and-analyze`, `predefinedRoles/editor`, `predefinedRoles/admin`. Changes at account scope apply to all properties.205206## Interpreting output207208Engagement benchmarks, attribution model differences, funnel drop-off heuristics, audience sequence vs. condition patterns, realtime reporting caveats, and BigQuery long-term-storage tradeoffs are all in `references/playbook.md`. Read it before drawing conclusions about whether numbers are "good" — GA4's sampling and attribution defaults differ meaningfully from UA and from other analytics tools.209210## Troubleshooting211212- **"No credentials. Run: ga4 auth login …"** — Exactly what it says. Get a Desktop OAuth client JSON from GCP and log in.213- **"Permission denied"** — The Google account you auth'd with doesn't have GA access. Grant Viewer/Editor in GA Admin → Access Management. Verify with `ga4 admin accounts summaries-list`.214- **"Quota exhausted"** — Pass `--return-property-quota` on a report to see live consumption. Data API tokens reset hourly/daily. GA360 gets 10x limits.215- **"insufficient_scope"** — The stored token was obtained with fewer scopes than this command needs. Re-run `ga4 auth login --client-secret <path> --scope <extra-scope>` (repeatable) to include them.216- **"No property id"** — Pass `--property/-p`, set `GA_PROPERTY_ID`, or `ga4 config set-property <id>`.217- **Alpha schema changed** — Pin `google-analytics-admin` and `google-analytics-data` in `pyproject.toml`, then `uv sync`.218- **Realtime report is empty** — The realtime window is only the last 30 minutes (60 for GA360). If there's genuinely no active traffic, the report will be empty; this is correct.219- **MP send returned 2xx but the event didn't appear** — `mp send` always returns 2xx regardless of validity. Use `mp validate` first to check for rejection reasons.220221## Configuration files222223- `~/.config/skills/ga4/config.json` — default property and other CLI settings224- `~/.config/skills/ga4/credentials.json` — OAuth user creds (written by `ga4 auth login`)225- Override the config dir with `GA4_CONFIG_DIR=/path/to/dir`226227### Migrating from earlier versions228229If upgrading from a build that stored config at `~/.config/ga4/`, run `ga4 config migrate --apply` to move the credentials and config to the new location. `ga4 auth status` emits a `deprecation_warning` until the migration runs.