Skip to main content

Google Analytics

This connector reaches the Google Analytics Data API at https://analyticsdata.googleapis.com/v1beta and exposes GA4 reporting to an agent. It's good for the everyday question — sessions by channel last month, which pages converted, what's happening on the site right now — without giving an agent your Google account. The reporting calls are reads, so they're on the moment you connect; the audience-export calls, which touch user-level data, are not.

The connector covers reporting only. Listing the properties in your account, editing data streams and anything else administrative lives on a different host (the Admin API at analyticsadmin.googleapis.com) and would be its own connector. You'll need to know your numeric property id before you start.

What you need

An OAuth app of your own, registered in Google Cloud, and a Google account with access to the property.

The Data API returns your own analytics data, so Google won't accept a plain API key for it — OAuth is the only route.

Needs gateway 1.1.0 or later

The authorisation flow below ships in gateway 1.1.0. An older gateway has no oauth/start endpoint, and the only way to connect it is to paste an access token that Google expires within the hour.

Register the app exactly as you would for Google Drive — same console, same consent screen, same client id, client secret and redirect URI — with two differences:

  • Enable the Google Analytics Data API rather than the Drive API.
  • Ask for the scope https://www.googleapis.com/auth/analytics.readonly. That's read-only and is all this connector needs — don't ask for the broader https://www.googleapis.com/auth/analytics. It's also what the connector asks for by default, which is why the call below passes no scopes field at all — add one only if you deliberately want something else.

Keep the client id, the client secret and the redirect URI to hand. The redirect URI has to match what you registered with Google exactly — a mismatch is the most common way this fails. Use http://127.0.0.1:7376/oauth/callback for a gateway on your own machine, or https://<your-gateway-host>/oauth/callback for a tunnelled one; loopback may be plain http, anything else must be https.

An app left in Testing breaks every seven days

A refresh token issued while your OAuth app's publishing status is Testing expires seven days from issuance, and refreshing it does not extend that — only a fresh consent resets the clock. Nothing on the Permaura side can change it. Before you connect, either set the app's user type to Internal (Google Workspace organisations only, where verification isn't required and publishing status doesn't apply), publish it to In production (unverified is fine for your own use — you accept a warning screen on the consent page), or have a Workspace admin mark it Trusted, which lifts both the 100-test-user cap and the seven-day expiry. In production, refresh tokens keep working; Google revokes them only after six months of disuse.

Add the connection

Both halves of this are gateway-side today, for the reasons the Drive page sets out. Create the connection against the gateway:

BASE=http://127.0.0.1:7376
curl -s -X POST $BASE/v1/connections/from-preset \
-H "authorization: Bearer $TOKEN" -H 'content-type: application/json' -d '{
"preset_id": "google-analytics",
"name": "ga-work"
}'

Then authorise it with an operator token, using the connection_id from that response:

curl -s -X POST $BASE/v1/connections/<connection_id>/oauth/start \
-H "authorization: Bearer $TOKEN" -H 'content-type: application/json' -d '{
"client_id": "<your-client-id>.apps.googleusercontent.com",
"client_secret": "<your-client-secret>",
"redirect_uri": "http://127.0.0.1:7376/oauth/callback"
}'

Open the authorize_url it returns in a browser and approve. Google redirects back to /oauth/callback on your gateway, which exchanges the code itself, seals the access token, the refresh token and your client secret as the connection's credential, and shows a plain Connected page. From then on the gateway refreshes the access token on its own, two minutes before it expires, as calls come through. There's nothing to re-paste and nothing to babysit, and permaura.com is never part of the exchange: your client secret, the tokens and the data all stay on your gateway.

A few things worth knowing about the flow:

  • The link is single-use and expires after ten minutes. Start again if you lose it.
  • PKCE is automatic. This connector's manifest records that Google supports it, so the gateway generates the verifier and challenge for you.
  • No refresh token, no credential. If Google hands back only an access token, the gateway refuses the exchange rather than sealing something that dies within the hour, and says so. It asks for access_type=offline and prompt=consent precisely so that doesn't happen.
  • Start it from the gateway's own host if you have a device enrolled for approvals. Sealing a credential changes the upstream identity the connection acts as, which is held for a device signature, and a browser redirect can't carry one — so the gateway refuses to start the flow over a tunnel in that setup.

Adding the connection makes the actions possible, not permitted. An agent still can't call anything until a grant allows it.

What the agent can do

ActionWhat it doesOn by default
reports.runRuns a GA4 report over a date rangeYes
reports.batch_runRuns up to five reports in one callYes
reports.run_pivotRuns a pivot reportYes
realtime.runRuns a realtime report over the last 30 minutesYes
compatibility.checkChecks which dimensions and metrics can be combinedYes
metadata.readLists the dimensions and metrics the property supportsYes
audience_exports.listLists audience export jobs (metadata only)Yes
audience_exports.createStarts an audience export jobNo
audience_exports.queryReads the user-level rows of a finished exportNo

Every action takes the numeric property — just the digits, not the properties/ prefix. The reporting calls are POSTs by Google's design but they only read, so the connector marks them as reads and they're usable straight away. The two audience-export actions are the exceptions: creating one charges quota tokens against the property, and querying one returns user-level rows, so both stay off until you deliberately switch them on.

Only the request fields listed in the connector reach Google; anything else an agent sends is dropped before the call leaves your gateway. For reports.run that's dimensions, metrics, dateRanges, dimensionFilter, metricFilter, orderBys, metricAggregations, returnPropertyQuota, limit and offset — enough for the reports people actually ask for, and not the whole schema.

Good to know

  • You must supply the property id. There is no capability here that lists your properties, because that lives on the Admin API host and this connection is pinned to the Data API host. Find it in the GA4 admin screen under Property details, or in the URL when you're looking at the property.
  • A 401 means access was withdrawn, not a token that timed out. The gateway keeps the access token fresh by itself, so a 401 points at the OAuth grant being revoked, the client deleted, the property access removed, or the seven-day Testing expiry above. Run the authorisation flow again to reconnect.
  • A service account is still an option for a purely machine-to-machine setup. Create one in the Cloud console, add its email as a Viewer on the GA4 property, then gcloud auth activate-service-account --key-file=… and gcloud auth print-access-token, and seal the result as the connection's credential. Nothing refreshes a pasted token, though — it lasts an hour and then you paste another, which is exactly what the OAuth flow above exists to avoid.
  • Quotas are per property and per project, and they're tiered. Standard GA4 properties get a much smaller token allowance than 360 properties, and realtime reports draw on a separate bucket. A wide report with many dimensions costs far more tokens than a narrow one — send returnPropertyQuota if you want the response to tell you where you stand.
  • Data is not instant. Standard GA4 reporting data can lag by several hours, and freshly created properties report nothing at all for the first day or so. Use realtime.run for the last 30 minutes; don't expect reports.run to match it.
  • Not every dimension pairs with every metric. GA4 rejects incompatible combinations outright. compatibility.check exists precisely so an agent can find that out before spending quota on a report that can't run.