LW IT Solutions logo LW IT Solutions logo LW IT Solutions
« Blog Overview /Digital Analytics / Tutorial: Querying GA4 Analytics Advisor via API...
Read this article in other languages:

Tutorial: Querying GA4 Analytics Advisor via API and Checking the Answer with runReport

Tutorial: Querying GA4 Analytics Advisor via API and Checking the Answer with runReport

Management Summary

Analytics Advisor is a Google Analytics assistant that answers questions about website results in everyday language. It can make figures easier to access without building every report manually. The article explains how another program can ask these questions and compare the answer with the underlying report. This matters for marketing decisions: a plausible answer still needs the right period and visitor group. Matching figures support the answer, but do not establish why a change happened.

Contents
  1. 1. Prepare a limited reporting question
  2. 2. Authorize and save the first answer
  3. 3. Ask a follow-up in the returned session
  4. 4. Reproduce the count with runReport
  5. 5. Inspect limitations before using the result
  6. 6. Keep the output reviewable
  7. Sources

How many sessions came from search results, paid ads or other channels? This tutorial asks Analytics Advisor, then adds a comparison week. A separate runReport request checks the numbers directly against a GA4 report. The questions and responses remain available as files.

Documentation checked: 25 October 2026. The current Google guide calls the assistant Ask Advisor. Programmatic access uses properties.chat in v1alpha. Access and permissions must be available for the intended GA4 property. [1] A saved test record from 27 September 2026 documents three real requests: all four channel groups for 1–7 September matched between the chat table and runReport. That was one historical test, not a guarantee for future responses. Private measurement values are not published.

1. Prepare a limited reporting question

The example compares 1–7 September 2026 with 25–31 August 2026. Both weeks start on Tuesday and end on Monday. The start and end dates are included; the property reporting time zone applies. sessions counts sessions, while sessionDefaultChannelGroup divides that count by the standard channel groups assigned to sessions. The API requires these exact field names. [4] [7]

A channel groups traffic sources, such as unpaid search results under Organic Search. Sessions are visits under the GA4 session rules, not a count of distinct people. A lower session count shows a decline in this report. The count alone cannot establish whether a new landing page or a marketing change caused the decline.

2. Authorize and save the first answer

The three code blocks run in order in the same Bash session. Required tools are gcloud for signing in, curl for HTTP requests and jq for reading and generating JSON files. A new private working folder avoids overwriting files from an earlier run. The placeholders in the first block need the actual project ID, numeric property ID and client-file path. The Cloud project provides API access; the property ID selects the GA4 data. The Analytics Data API must be enabled in the project. The signed-in account needs property access and serviceusage.services.use on the project assigned to API usage.

OAuth permissions determine which API functions the sign-in can access. Chat needs analytics.chatbot.read; the later report needs analytics.readonly. The example requests both. gcloud stores credentials locally, and a new sign-in can replace existing Application Default Credentials. Credentials and access tokens must remain outside public webpages. [1][3][6]

The example uses a separate Desktop app OAuth client for the Analytics permissions. The downloaded JSON file is supplied through --client-id-file. Google documents this approach for permissions outside the usual Cloud set. [6] Before each HTTP request, the script obtains an access token. A failed token command or an empty token stops the script before the request. HTTP failures also stop execution; automatic retries are not included.

Shell
# Bash; gcloud, curl and jq required
set -euo pipefail
export PROJECT_ID='REPLACE_WITH_CLOUD_PROJECT'
export PROPERTY_ID='REPLACE_WITH_NUMERIC_PROPERTY_ID'
export OAUTH_CLIENT_FILE='REPLACE_WITH_DOWNLOADED_DESKTOP_CLIENT_JSON'
gcloud auth application-default login --client-id-file="$OAUTH_CLIENT_FILE" --scopes="https://www.googleapis.com/auth/cloud-platform,https://www.googleapis.com/auth/analytics.chatbot.read,https://www.googleapis.com/auth/analytics.readonly"
gcloud auth application-default set-quota-project "$PROJECT_ID"

cat > question.json <<'JSON'
{
  "userQuery": "For 2026-09-01 through 2026-09-07, report sessions by sessionDefaultChannelGroup. Use these exact dates and API field names. State any limitation and do not infer causes.",
  "returnPropertyQuota": true
}
JSON
ACCESS_TOKEN=$(gcloud auth application-default print-access-token)
[[ -n "$ACCESS_TOKEN" ]] || { echo "Empty access token" >&2; exit 1; }
curl --fail-with-body --silent --show-error \
  "https://analyticsdata.googleapis.com/v1alpha/properties/${PROPERTY_ID}:chat" \
  -H "Authorization: Bearer ${ACCESS_TOKEN}" \
  -H "x-goog-user-project: ${PROJECT_ID}" \
  -H "Content-Type: application/json" \
  --data-binary @question.json -o answer.json
jq '{sessionId, blocks, propertyQuota}' answer.json

The response may contain text and tables. answer.json saves the complete response; jq displays selected fields. A missing table does not establish zero values. Table cells can contain currency or percentage signs, for example. A numeric comparison first requires the unit and meaning of each column. [2]

3. Ask a follow-up in the returned session

The chat sessionId connects the next question to the conversation so far. This conversation identifier is unrelated to the sessions reporting metric. The next block stops if a nonempty identifier is missing. Further questions need the identifier from the latest response. Conversations for different properties or clients must remain separate.

Shell
SESSION_ID=$(jq -er '.sessionId | strings | select(length > 0)' answer.json)
jq -n --arg sid "$SESSION_ID" '{
  sessionId: $sid,
  userQuery: "Compare those sessions by sessionDefaultChannelGroup with 2026-08-25 through 2026-08-31. Show both totals and absolute differences. Separate observations from hypotheses.",
  returnPropertyQuota: true
}' > followup.json
ACCESS_TOKEN=$(gcloud auth application-default print-access-token)
[[ -n "$ACCESS_TOKEN" ]] || { echo "Empty access token" >&2; exit 1; }
curl --fail-with-body --silent --show-error \
  "https://analyticsdata.googleapis.com/v1alpha/properties/${PROPERTY_ID}:chat" \
  -H "Authorization: Bearer ${ACCESS_TOKEN}" \
  -H "x-goog-user-project: ${PROJECT_ID}" \
  -H "Content-Type: application/json" \
  --data-binary @followup.json -o followup-answer.json
jq '{sessionId, blocks, propertyQuota}' followup-answer.json

The follow-up explicitly names the earlier period. “Organic Search declined” describes an observation. “The new landing page caused the decline” remains a hypothesis without further evidence. The saved questions show what was actually requested.

4. Reproduce the count with runReport

After the two chat calls, the third request uses runReport to retrieve the same metric by the same channels for both weeks. The comparison therefore uses an explicitly defined report. Both results still come from GA4: agreement proves neither complete data collection nor the cause of a change. [3]

Shell
cat > report-request.json <<'JSON'
{
  "dateRanges": [
    {"startDate":"2026-09-01","endDate":"2026-09-07","name":"current"},
    {"startDate":"2026-08-25","endDate":"2026-08-31","name":"previous"}
  ],
  "dimensions": [{"name":"sessionDefaultChannelGroup"}],
  "metrics": [{"name":"sessions"}],
  "limit": "1000",
  "returnPropertyQuota": true
}
JSON
ACCESS_TOKEN=$(gcloud auth application-default print-access-token)
[[ -n "$ACCESS_TOKEN" ]] || { echo "Empty access token" >&2; exit 1; }
curl --fail-with-body --silent --show-error \
  "https://analyticsdata.googleapis.com/v1beta/properties/${PROPERTY_ID}:runReport" \
  -H "Authorization: Bearer ${ACCESS_TOKEN}" \
  -H "x-goog-user-project: ${PROJECT_ID}" \
  -H "Content-Type: application/json" \
  --data-binary @report-request.json -o report-response.json
jq '{dimensionHeaders,metricHeaders,rows,rowCount,metadata,propertyQuota}' report-response.json

Column names appear in dimensionHeaders and metricHeaders; row values follow that order. The additional dateRange column contains current or previous here. Comparison therefore uses the channel and period together, not row position. For example, 100 sessions instead of 80 means 20 additional sessions and a 25 percent increase. That percentage calculation is undefined when the earlier value is zero. A missing row cannot be treated as a measured zero without further checks.

5. Inspect limitations before using the result

The GA4 API Response Inspector is a supplementary tool for saved report responses. Checking the chat numbers remains a separate step. The metadata in report-response.json also needs review: thresholds can hide small groups, while sampling can base calculations on only part of the events. If rowCount exceeds the number of returned rows, more results remain. This example requests at most 1,000 rows and does not fetch further pages automatically. [8]

returnPropertyQuota requests information about quota, meaning API usage limits. The setting does not reserve more calls. Missing quota details leave the available allowance unknown. Before retrying, the cause needs checking: sign-in failure, insufficient permission or a usage limit. Further chat calls can consume more quota and return different answers. [1]

6. Keep the output reviewable

The test record consists of two questions, two chat responses, the report request, the report response and the retrieval time. Access to these files should be limited to authorized people. Google’s privacy notice says human reviewers may process conversations to improve the product. Customer names and confidential campaign plans are unnecessary for this numerical question. [5]

The result records whether each channel’s chat value matches the report. Any mismatch retains both values and the unresolved questions. “Not reproducible” is also a useful result. The week-to-week comparison alone does not support a recommendation to change advertising spend.

Lukas Wojcik

Lukas Wojcik

Systems architect and technology enthusiast specializing in scalable tracking solutions, GMP Stack (GA4 & GTM), and robust backend architectures. Advocate for clean code and privacy-first design.

Get in touch

A question or an idea for a web project? Tell me a little about it. This form uses reCAPTCHA to protect against spam.

Write a comment

Differing figures from other accounts and questions about the setup are welcome here.

The email address is not published. Required fields are marked with an asterisk.