LW IT Solutions
« Blog Overview /Digital Analytics / Tutorial: Querying GA4 Analytics Advisor via API...
This post 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
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

A useful analytics assistant needs an answer that can be checked. This walkthrough asks Analytics Advisor for sessions by channel, adds a comparison period, and checks the numbers with an explicit Data API request. The result is a small investigation with saved inputs and outputs, rather than an automatically trusted explanation.

Documentation checked: 25 September 2026. The chat endpoint is an early-preview v1alpha feature. Availability and access must be checked for the intended property. On 27 September 2026, the three documented API requests were executed from the command line against an authorized GA4 property. The structured table for 1–7 September matched all four channel-group rows from runReport exactly; no property values are reproduced here. [1]

1. Prepare a limited reporting question

The example compares 1–7 September 2026 with 25–31 August 2026. Both periods have seven days and the same weekday composition. The metric is sessions, grouped by sessionDefaultChannelGroup. These names come from the API schema. [4] Explicit dates avoid moving meanings such as “last week”.

A channel is an attribution grouping, not a person or a campaign budget. The first question deliberately asks for a count rather than a cause. A lower count can justify investigation, but cannot independently show which marketing action caused it.

2. Authorize and save the first answer

The example runs in Bash with gcloud, curl and jq installed. The Cloud project must have the Analytics Data API enabled, and the signed-in account needs access to the property and permission to use the quota project. Cloud project ID and numeric Analytics property ID are different identifiers.

Chat needs analytics.chatbot.read; the later report check needs analytics.readonly. The sample requests both. Credentials remain in the local gcloud flow, not in article code or a public webpage. [1][3]

Current Cloud CLI versions can block these Analytics scopes for the built-in OAuth client. A Desktop app OAuth client should be created in the intended Cloud project, downloaded as JSON and supplied with --client-id-file. Google documents this flag for scopes outside the default Cloud set. [6]

# 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
curl --fail-with-body --silent --show-error \
  "https://analyticsdata.googleapis.com/v1alpha/properties/${PROPERTY_ID}:chat" \
  -H "Authorization: Bearer $(gcloud auth application-default print-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

A successful response can contain text and table blocks. Keeping the complete response preserves its context. A missing table is not a table full of zeros. Currency signs, percentage signs and formatted cell values should not be silently converted into bare numbers. The response contract and session fields are documented separately. [2]

3. Ask a follow-up in the returned session

The next command reads the returned session identifier; an absent identifier stops the script. It then asks for the earlier period. For further turns, the latest response supplies the next session ID. Different properties, clients and access contexts should have separate session storage.

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
curl --fail-with-body --silent --show-error \
  "https://analyticsdata.googleapis.com/v1alpha/properties/${PROPERTY_ID}:chat" \
  -H "Authorization: Bearer $(gcloud auth application-default print-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

Session context helps connect questions, but the comparison still needs explicit dates and definitions. “Organic Search declined” describes a result. “The new landing page caused the decline” requires additional evidence. Saving both prompts makes that distinction reviewable.

4. Reproduce the count with runReport

The second API call requests the two date ranges, the same dimension and the same metric directly. It is a separate report, not an instruction to the assistant to agree with itself. The report API documents date ranges, headers, row counts and metadata. [3]

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
curl --fail-with-body --silent --show-error \
  "https://analyticsdata.googleapis.com/v1beta/properties/${PROPERTY_ID}:runReport" \
  -H "Authorization: Bearer $(gcloud auth application-default print-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

Header order determines how row values are interpreted. With multiple ranges, the date-range dimension distinguishes the periods. Comparison by row position alone is unsuitable because channels can appear in different orders or be absent. For each channel, the arithmetic is current minus previous. A percentage change needs a nonzero previous value; a missing row and a measured zero are different cases.

5. Inspect limitations before using the result

The GA4 API Response Inspector accepts the saved report-response.json for metadata and quota review. It does not parse chat responses. The assistant output is checked against the report separately. A reported threshold, sampling information or incomplete pagination needs to remain visible in the investigation.

returnPropertyQuota requests the chat quota state; it does not reserve capacity. An absent quota object means unknown, not unlimited. Authentication, permission and quota failures should be recorded before retrying. Automatic repeated chat calls can consume additional quota and produce different answers. [1]

6. Keep the output reviewable

The working record consists of the two prompts, two chat responses, report request and report response, plus the time of retrieval. Access should follow the sensitivity of the property data. Google’s privacy notice states that conversations may be processed by human reviewers to improve the product. Customer names and confidential campaign plans therefore do not belong in an otherwise numerical question. [5]

A successful review ends with a documented number and an explanation of any mismatch. It may also end with “not reproducible”. Both outcomes are more useful than an unsupported recommendation to change campaign 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

Briefly describe your project or inquiry for a tailored response. This site is protected by reCAPTCHA.

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.

ALL ARTICLES & CATEGORIES

CCTV

Follow this category by RSS

Cloud & AI

Follow this category by RSS

Data Privacy

All 14 articles in this category Follow this category by RSS

Digital Analytics

All 50 articles in this category Follow this category by RSS

Digital Marketing

All 34 articles in this category Follow this category by RSS

IT & Networks

All 17 articles in this category Follow this category by RSS

Music Production

All 11 articles in this category Follow this category by RSS

Raspberry PI

Follow this category by RSS

Smart Home

All 18 articles in this category Follow this category by RSS

Web Development

Follow this category by RSS

WordPress Plugins & Tricks

Follow this category by RSS