# Where to look: reports, explorations, Data API and BigQuery

Names below were checked against Google's docs on 2026-10-17. UI labels move; if a menu or dimension is not where this says, search the live help page before guessing.

## Pick one scope and stay in it

GA4 has three traffic-source scopes, and mixing them is the most common false alarm.

- First user (prefix "First user", for example First user source / medium): where a user was first acquired. Used by the User acquisition report. Pair with Total users or New users.
- Session (prefix "Session", for example Session source / medium): where each session came from, for new and returning users. Used by the Traffic acquisition report. Pair with Sessions. Direct sessions take the user's earlier non-direct source when GA4 has one within the property's lookback window; Google says the key event lookback window also applies to session attribution.
- Event (no prefix, for example Source / medium, Default channel group): which source gets credit for a key event under the property's attribution model. For events that are not key events, source and medium are `(not set)` by design.

Write the question first ("which source started the sessions that bought?" is session scope; "how were buyers first acquired?" is first-user scope) and choose the scope from it.

## Exploration build

Create a Free-form exploration with two tabs. Keep the channel group the same in both and write down which one it is (default, primary or custom), and the reporting identity.

Explorations only reach back as far as the property's event data retention setting (2 or 14 months on standard properties on 2026-10-17; verify the setting and its menu path at run time). For an older baseline, use standard reports, which the setting does not affect, or the export. Never read an empty exploration date as zero.

Looker Studio charts built on the GA4 connector use the same dimensions. Rebuild the chart's dimension and scope here before trusting a difference.

Tab 1, session scope:

- Rows: Session default channel group, then Session source / medium.
- Add as needed: Session manual source / medium (the manual or referrer-parsed pair; compare it with Landing page + query string, because only a `utm_` parameter in the landing URL proves UTMs), Session campaign, Landing page + query string, Stream name, Hostname, Device category.
- Values: Sessions, Engaged sessions, Key events (or the one key event that matters).
- Filter: the affected channel row, for example Session default channel group exactly matches Unassigned.

Tab 2, first-user scope:

- Rows: First user default channel group, First user source / medium, First user campaign.
- Values: Total users, New users.
- Use this only for acquisition questions. It will not match Tab 1 and is not meant to.

Reading the result:

- Pair readable and a `utm_` parameter in Landing page + query string: the pair came from UTMs and missed the rules (cause A). Manual source / medium alone is not proof, because GA4 also fills the manual fields from referrers; values in parentheses such as `(referral)` are referrer-derived.
- One of source or medium readable, the other `(not set)`, with a `utm_` parameter in the landing URL: a partial UTM set (cause A, or J for an assistant source).
- Both `(not set)`, or no `utm_` parameter: collection branch (causes B to H).
- Landing page + query string missing UTMs that the published link had: cause B.
- One hostname, stream or device category holding most of the growth: the defect is in that implementation, not property-wide.

## Data API and the GA4 MCP server

Google's GA4 MCP server (`googleanalytics/google-analytics-mcp`, Experimental, local, `analytics.readonly`) exposes `get_account_summaries`, `get_property_details`, `list_google_ads_links`, `run_report`, `run_funnel_report`, `get_custom_dimensions_and_metrics` and `run_realtime_report`. All of them read; none change settings. Build `run_report` calls from these API names (UI name in brackets):

- Session scope: `sessionDefaultChannelGroup` (Session default channel group), `sessionPrimaryChannelGroup`, `sessionCustomChannelGroup:<channel group ID>` for a custom group, `sessionSourceMedium`, `sessionSource`, `sessionMedium`, `sessionManualSource`, `sessionManualMedium`, `sessionManualCampaignName`, `sessionCampaignName`, `sessionSourcePlatform`.
- First-user scope: `firstUserDefaultChannelGroup`, `firstUserSourceMedium`, `firstUserCampaignName`.
- Event scope: `defaultChannelGroup`, `sourceMedium`.
- Page and stream: `landingPage`, `landingPagePlusQueryString`, `pageReferrer`, `hostName`, `streamId`, `streamName`, `platform`, `eventName`.
- Metrics: `sessions`, `engagedSessions`, `engagementRate`, `keyEvents`, `eventCount`, `totalUsers`, `newUsers`.
- Direct and bot checks (cause K): `city`, `browser`, `browserVersion`, `operatingSystemWithVersion`, `screenResolution`, `deviceCategory`.

Source Group (released 11 June 2026) merges variant source values and is filled retroactively. It was not on the Data API schema page on 2026-10-17; use it in the UI and check the schema again at run time.

Useful calls:

- Unassigned breakdown: `sessionSource`, `sessionMedium`, `sessionManualSource`, `sessionManualMedium` and `landingPagePlusQueryString` with `sessions` and `keyEvents`, filtered to `sessionDefaultChannelGroup` Unassigned. A `utm_` parameter in the landing page proves UTMs; manual values alone do not.
- Server events without a page: `eventName`, `landingPage` and `hostName` with `eventCount` and `keyEvents`, filtered to the key event. Rows with revenue and a `(not set)` landing page or host are usually server-sent (cause E).
- Payment referrals: `sourceMedium` (event scope) with `keyEvents`, filtered to the key event, to see whether a payment host takes key event credit. Then `sessionSource` with `sessions`, filtered to `sessionMedium` referral.

Check the request body shape against the live runReport reference before sending. The Data API applies the same processing, thresholds and `(other)` condensing as the UI.

## BigQuery export fields

Use the daily `events_YYYYMMDD` tables for diagnosis. The intraday table is deleted when the daily table completes, and Google's export page says streaming export is best effort and can contain gaps. Exported data has no behavioural modelling, so session counts differ from a Blended-identity report by design. Session and user counts in the UI and Data API are HyperLogLog++ estimates (Google developer blog, 2022); `COUNT(DISTINCT ...)` in the export is exact.

Traffic source records, as listed on Google's schema page on 2026-10-17:

- `traffic_source.source`, `.medium`, `.name`: the source that first acquired the user (first-user scope). They do not change with later campaigns, which is why they disagree with session reports. Not populated in intraday tables, and the streaming export lacks them for new users.
- `collected_traffic_source`: what arrived on each event. Fields: `manual_campaign_id`, `manual_campaign_name`, `manual_source`, `manual_medium`, `manual_term`, `manual_content`, `manual_creative_format`, `manual_marketing_tactic`, `manual_source_platform`, `gclid`, `dclid`, `srsltid`. Google notes `manual_source` and `manual_medium` also include parsed referral parameters.
- `session_traffic_source_last_click`: the last-click attributed session source, "where available". Records: `manual_campaign` (campaign_id, campaign_name, source, medium, term, content, source_platform, creative_format, marketing_tactic), `google_ads_campaign` (customer_id, account_name, campaign_id, campaign_name, ad_group_id, ad_group_name), `cross_channel_campaign` (campaign_name, campaign_id, source_platform, source, medium), plus `sa360_campaign`, `dv360_campaign` and `cm360_campaign`.
- Channel group fields: Google's schema page did not list a channel group field on 2026-10-17. Third-party sources report `cross_channel_campaign.default_channel_group` and `primary_channel_group` in some exports. Check your table's schema (query Q0) before using them; if absent, take the channel label from the UI or Data API.

Other fields used in the checks:

- `user_pseudo_id`: the pseudonymous client or app instance ID.
- `event_params` keys `ga_session_id` and `ga_session_number` (read from `value.int_value`; a server sender may put `ga_session_id` in `value.string_value`, so read both, and Measurement Protocol or Data Manager API rows may carry it under key `session_id`; Q0 shows which), `page_location` and `page_referrer` (read from `value.string_value`). The session key is `user_pseudo_id` plus `ga_session_id`; the session ID alone is a start timestamp in seconds and is not unique.
- `privacy_info.analytics_storage`, `privacy_info.ads_storage`: `Yes`, `No` or `Unset` when consent mode is on.
- `stream_id`, `platform` (Google's schema says Web, IOS or Android; run `SELECT DISTINCT platform` first and use the spelling your export has), `device.web_info.hostname`.
- `event_timestamp`: microseconds UTC, the time GA4 received the event.

There is no field that marks an event as sent by Measurement Protocol. Infer it from missing `page_location`, missing `ga_session_id`, odd `user_pseudo_id` values and the event name.

Practical notes:

- A wildcard `events_*` also matches `events_intraday_*`. Filtering `_TABLE_SUFFIX BETWEEN '20261001' AND '20261007'` excludes intraday tables because their suffix starts with a letter.
- Export datasets can sit in different locations for different properties. A query that unions datasets in two locations fails; run one query per location.
- Dry-run with `bq query --dry_run` or the console where available, and keep the date window short. Google's MCP pages did not document a dry-run option on 2026-10-17; check the server's tool list at run time. Through the MCP server, Google's how-to page limits results to 3,000 rows and processing to three minutes by default, so aggregate in SQL.
- An MCP query that outlasts `timeout_ms` (default 20,000 ms) returns `job_complete: false` with a `job_id`. Poll with `get_query_results` instead of rerunning it. The rules at the top of [bigquery-checks.md](bigquery-checks.md) have the details.
- Run the export coverage check (QC in [bigquery-checks.md](bigquery-checks.md)) before treating a missing row as evidence.

## Reporting limits that look like missing data

None of these is a source problem. Read the data quality indicator at the top of the report first.

- Thresholding: data can be withheld when a report includes demographics or low-volume search query data. Thresholds are system defined and cannot be adjusted. A withheld row is not zero.
- Sampling: the indicator shows the percentage of events used. Shorten the range or simplify the query.
- `(other)`: rows beyond the table's row limit are condensed. Google treats more than 500 values in a dimension as high cardinality (guidance, not a limit) and suggests standard reports, fewer high-cardinality dimensions, or BigQuery.
- Freshness: processing can take 24 to 48 hours, and Reports and Explorations may not be in sync.
