Platforms
Connect a GA4 property and run reports, realtime reports and dimension lookups against it with your Pozy API key.
Connect a Google Analytics 4 property to a profile and read its data with the same key you use for everything else. One account is one GA4 property. Access is read-only: Pozy can run reports but never changes the property, its events or its audiences. Use it to put website traffic next to your social and ads numbers.
#What you can do
| Job | Endpoint |
|---|---|
| Run a report (sessions by source, conversions by campaign, page views by path...) | POST /v1/analytics/googleanalytics/report |
| See activity in the last 30 minutes | POST /v1/analytics/googleanalytics/realtime |
| List every dimension and metric the property accepts, custom ones included | GET /v1/analytics/googleanalytics/metadata |
| Property | Value |
|---|---|
| Platform id | googleanalytics |
| Connect | OAuth with a Google account that has at least Viewer access to a GA4 property |
| Scope | https://www.googleapis.com/auth/analytics.readonly |
| Properties per account | One. Connect another property to a different profile |
| List the accounts | GET /v1/accounts?category=analytics |
| Universal Analytics | Not supported |
#Connect a property
curl "https://api.pozy.ai/v1/connect/googleanalytics?profileId=6710c3a9e4b0d2f1a8c95e21&redirect_url=https://app.example.com/connected" \
-H "Authorization: Bearer $POZY_API_KEY"const res = await fetch("https://api.pozy.ai/v1/connect/googleanalytics?profileId=6710c3a9e4b0d2f1a8c95e21&redirect_url=https:%2F%2Fapp.example.com%2Fconnected", {
headers: { Authorization: `Bearer ${process.env.POZY_API_KEY}` },
});
const data = await res.json();
if (!res.ok) throw new Error(data.error);import os, requests
resp = requests.get(
"https://api.pozy.ai/v1/connect/googleanalytics",
headers={"Authorization": f"Bearer {os.environ['POZY_API_KEY']}"},
params={"profileId": "6710c3a9e4b0d2f1a8c95e21", "redirect_url": "https://app.example.com/connected"},
)The user signs in with Google and allows read access. If their Google account can read one property, it connects straight away. If it can read several, they pick one on a page hosted by Pozy. Either way they return to your redirect_url with connected=googleanalytics, profileId, accountId and username (the property name).
#Use your own property picker
To keep users in your app, add headless=true to your redirect_url. Instead of Pozy's picker, the user lands on your URL with step=select_property, profileId, platform=googleanalytics and a pendingDataToken. List the properties, show them, then connect the one the user picks:
curl "https://api.pozy.ai/v1/connect/googleanalytics/properties?pendingDataToken=$TOKEN" \
-H "Authorization: Bearer $POZY_API_KEY"
curl -X POST https://api.pozy.ai/v1/connect/googleanalytics/select-property \
-H "Authorization: Bearer $POZY_API_KEY" \
-H "Content-Type: application/json" \
-d '{ "pendingDataToken": "'"$TOKEN"'", "propertyId": "312456789" }'const listRes = await fetch(`https://api.pozy.ai/v1/connect/googleanalytics/properties?${new URLSearchParams({ pendingDataToken: token })}`, {
headers: { Authorization: `Bearer ${process.env.POZY_API_KEY}` },
});
const list = await listRes.json();
if (!listRes.ok) throw new Error(list.error);
const doneRes = await fetch("https://api.pozy.ai/v1/connect/googleanalytics/select-property", {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.POZY_API_KEY}`,
"Content-Type": "application/json",
},
body: JSON.stringify({ pendingDataToken: token, propertyId: list.properties[0].propertyId }),
});
const done = await doneRes.json();
if (!doneRes.ok) throw new Error(done.error);
// Send the user to done.redirect_urlprops = requests.get(
"https://api.pozy.ai/v1/connect/googleanalytics/properties",
headers={"Authorization": f"Bearer {os.environ['POZY_API_KEY']}"},
params={"pendingDataToken": token},
).json()["properties"]
done = requests.post(
"https://api.pozy.ai/v1/connect/googleanalytics/select-property",
headers={"Authorization": f"Bearer {os.environ['POZY_API_KEY']}"},
json={"pendingDataToken": token, "propertyId": props[0]["propertyId"]},
).json()Both calls need your API key (or a connect token) for the same profile; the pending token alone is not enough. It expires after one hour. Listing does not use it up, and a failed select leaves it valid for a retry. More on this pattern in Headless connect.
#Run a report
Ask for 1 to 10 metrics and up to 9 dimensions by their GA4 API names:
curl -X POST https://api.pozy.ai/v1/analytics/googleanalytics/report \
-H "Authorization: Bearer $POZY_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"accountId": "6710c4f2e4b0d2f1a8c95e6b",
"metrics": ["sessions", "conversions"],
"dimensions": ["sessionSource"],
"dateRanges": [{ "startDate": "28daysAgo", "endDate": "yesterday" }],
"orderBys": [{ "metric": { "metricName": "sessions" }, "desc": true }],
"limit": 10
}'const res = await fetch("https://api.pozy.ai/v1/analytics/googleanalytics/report", {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.POZY_API_KEY}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
accountId: "6710c4f2e4b0d2f1a8c95e6b",
metrics: ["sessions", "conversions"],
dimensions: ["sessionSource"],
dateRanges: [{ startDate: "28daysAgo", endDate: "yesterday" }],
limit: 10,
}),
});
const data = await res.json();
if (!res.ok) throw new Error(data.error);
for (const row of data.rows) console.log(row.dimensionValues[0].value, Number(row.metricValues[0].value));report = requests.post(
"https://api.pozy.ai/v1/analytics/googleanalytics/report",
headers={"Authorization": f"Bearer {os.environ['POZY_API_KEY']}"},
json={
"accountId": "6710c4f2e4b0d2f1a8c95e6b",
"metrics": ["sessions", "conversions"],
"dimensions": ["sessionSource"],
"dateRanges": [{"startDate": "28daysAgo", "endDate": "yesterday"}],
"limit": 10,
},
).json()The response is Google's report with accountId and propertyId added: dimensionHeaders, metricHeaders, rows, rowCount, metadata and propertyQuota. Every value in rows is a string, numbers included. Convert using the type in metricHeaders.
| Field | What it does |
|---|---|
metrics |
1 to 10 metric names. Required |
dimensions |
Up to 9 dimension names |
dateRanges |
Up to 4 ranges. Dates are YYYY-MM-DD, today, yesterday or NdaysAgo. Default: 28daysAgo to yesterday |
dimensionFilter, metricFilter, orderBys |
Google's own filter and sort shapes |
limit, offset |
Up to 10,000 rows per page. Page with offset until you reach rowCount |
keepEmptyRows |
Include rows whose metrics are all zero |
currencyCode |
ISO 4217 code for money metrics. Defaults to the property's currency |
The realtime endpoint takes the same metrics, dimensions, filters, orderBys and limit, plus optional minuteRanges, and no date ranges or offset.
#Quotas and caching
| Limit | Value |
|---|---|
| Identical report requests | Cached for 10 minutes |
| Metadata | Cached for 1 hour |
| Realtime reports | Never cached |
| Google tokens per property | 200,000 a day and 40,000 an hour |
| Tokens per property across all Pozy requests | 14,000 an hour |
Bigger date ranges, more dimensions and higher limits cost more tokens. Cached responses cost none. propertyQuota in each response shows what is left. Hourly quotas refill within the hour, daily quotas at midnight Pacific time.
#Errors
| Error | Cause | Fix |
|---|---|---|
googleanalytics_scope_missing on the redirect |
The Analytics permission was unticked | Connect again and keep it ticked |
googleanalytics_no_properties on the redirect |
The Google account cannot read any GA4 property | Grant it access in Google Analytics first |
googleanalytics_access_denied, googleanalytics_invalid_state |
Declined, or the link expired | Request a fresh authUrl |
400 on a report |
A field or a dimension or metric name is invalid; Google's message names it | Check names with the metadata endpoint |
403 on a report |
The Google account lost access, or removed the app | Reconnect |
429 rate_limited |
The property's Google quota is used up | Retry after it refills; reuse identical requests to hit the cache |
#Endpoints
| Endpoint | What it does |
|---|---|
GET /v1/connect/googleanalytics |
Get Google Analytics OAuth connect URL |
GET /v1/connect/googleanalytics/properties |
List Google Analytics properties for a pending connect |
POST /v1/connect/googleanalytics/select-property |
Connect the chosen Google Analytics property |
POST /v1/analytics/googleanalytics/report |
Run a Google Analytics (GA4) report |
POST /v1/analytics/googleanalytics/realtime |
Run a Google Analytics (GA4) realtime report |
GET /v1/analytics/googleanalytics/metadata |
List Google Analytics (GA4) dimensions and metrics |