Google Analytics

Overview

The Google Analytics Core API mirrors the GA4 Data API and GA4 Admin API 1:1 — arbitrary reports, batched reports, realtime active users, and property discovery.

Base path/api/core/google-analytics
Auththe calling organization's own stored Google OAuth credentials, resolved from its google_analytics integration. You never pass a Google token yourself
Every requestrequires ?organizationId=<uuid>

This is the one Core API domain that is read-only. GA4 is a reporting surface — there is nothing to mutate — so a read-scoped API token reaches all four routes.

Choosing a property. Calls default to the GA4 property stored on the integration. An org holding several property rows can target any of them with ?propertyId= (the bare numeric id, no properties/ prefix). Discover them with GET /api/core/google-analytics/properties.

API Endpoints

EndpointGA4 Real EndpointDescription
GET /propertiesAdmin accountSummaries.listEvery GA4 property the token can read
POST /reports/runData properties/{id}:runReportRun one report
POST /reports/batch-runData properties/{id}:batchRunReportsRun up to 5 reports in one call
GET /reports/realtimeData properties/{id}:runRealtimeReportActive users, last 30 minutes

Properties

GET /api/core/google-analytics/properties

The raw accountSummaries.list response — every account and the GA4 properties under it. Use a property's numeric id as ?propertyId= on the report routes.

curl "https://analify.scalyax.ai/api/core/google-analytics/properties?organizationId={organizationId}" \
  -H "Authorization: Bearer {anlfy_token}"

Reports

POST /api/core/google-analytics/reports/run

The request body is the GA4 runReport body, forwarded verbatim — dimensions, metrics, dateRanges, dimensionFilter, metricFilter, orderBys, limit, offset, keepEmptyRows. Analify adds nothing.

curl -X POST "https://analify.scalyax.ai/api/core/google-analytics/reports/run?organizationId={organizationId}" \
  -H "Authorization: Bearer {anlfy_token}" -H "Content-Type: application/json" \
  -d '{
    "dimensions": [{ "name": "date" }, { "name": "sessionDefaultChannelGroup" }],
    "metrics": [{ "name": "sessions" }, { "name": "totalRevenue" }],
    "dateRanges": [{ "startDate": "2026-08-01", "endDate": "2026-08-16" }]
  }'

The response is GA4's own runReport payload — dimensionHeaders, metricHeaders, rows[].dimensionValues[].value / metricValues[].value. Every GA4 metric value arrives as a string, including numbers; parse before arithmetic.

POST /api/core/google-analytics/reports/batch-run

Same contract, but the body carries an array of report requests. GA4 caps a batch at 5 — a sixth request is rejected by Google, not by Analify.

curl -X POST "https://analify.scalyax.ai/api/core/google-analytics/reports/batch-run?organizationId={organizationId}" \
  -H "Authorization: Bearer {anlfy_token}" -H "Content-Type: application/json" \
  -d '{
    "requests": [
      {
        "dimensions": [{ "name": "date" }],
        "metrics": [{ "name": "sessions" }],
        "dateRanges": [{ "startDate": "2026-08-01", "endDate": "2026-08-16" }]
      },
      {
        "dimensions": [{ "name": "landingPage" }],
        "metrics": [{ "name": "bounceRate" }],
        "dateRanges": [{ "startDate": "2026-08-01", "endDate": "2026-08-16" }]
      }
    ]
  }'

Batching is one HTTP round trip and one quota charge per contained report — prefer it over N sequential run calls.

GET /api/core/google-analytics/reports/realtime

Active users over the last 30 minutes — GA4's realtime window, which is fixed and takes no date range.

curl "https://analify.scalyax.ai/api/core/google-analytics/reports/realtime?organizationId={organizationId}" \
  -H "Authorization: Bearer {anlfy_token}"

Things GA4 itself constrains

  • History depends on the property, roughly 2–14 months depending on its retention setting. A startDate beyond retention returns fewer rows rather than an error.
  • Sampling and thresholding. GA4 may sample large queries and will withhold rows that risk identifying individuals; check metadata.samplingMetadatas and (other) rows before treating a total as exact.
  • Metric values are strings. See above — this is GA4's contract, preserved.

Error Shape

CodeStatusMeaning
GA4_NOT_CONNECTED400The organization has no connected Google Analytics integration
GA4_API_ERROR502Any failure reported by GA4, carrying Google's own message
GA4_UNKNOWN_ERROR500A non-platform failure inside the route
{
  "success": false,
  "error": {
    "message": "Field metrics[0].name contains an invalid metric: 'sessionz'.",
    "code": "GA4_API_ERROR"
  }
}

Did this page help you?