# google-analytics-api-ga4 — Google Analytics 4 (GA4) Data API client for Rust
[](https://crates.io/crates/google-analytics-api-ga4)
[](https://docs.rs/google-analytics-api-ga4)
[](https://crates.io/crates/google-analytics-api-ga4)
[](https://github.com/uiuifree/rust-google-analytics-api-ga4/blob/main/LICENSE)
**google-analytics-api-ga4** is a lightweight, async Rust client library for the
[Google Analytics Data API v1beta (GA4)](https://developers.google.com/analytics/devguides/reporting/data/v1/rest).
Fetch GA4 reports, realtime reports, pivot reports, and metadata from Rust with
strongly typed requests and responses — no code generation, no heavy dependencies
(just `reqwest`, `serde`, and `serde_json`).
Use it to build analytics dashboards, export GA4 data to your database or data
warehouse, automate SEO / marketing reporting, or monitor realtime active users
from a Rust backend.
## Features
| [`runReport`](https://developers.google.com/analytics/devguides/reporting/data/v1/rest/v1beta/properties/runReport) | `AnalyticsDataApi::run_report` | Core report of dimensions and metrics (sessions, pageviews, events, …) |
| [`batchRunReports`](https://developers.google.com/analytics/devguides/reporting/data/v1/rest/v1beta/properties/batchRunReports) | `AnalyticsDataApi::batch_run_reports` | Up to 5 reports in a single HTTP request |
| [`runPivotReport`](https://developers.google.com/analytics/devguides/reporting/data/v1/rest/v1beta/properties/runPivotReport) | `AnalyticsDataApi::run_pivot_report` | Pivot-table style reports |
| [`batchRunPivotReports`](https://developers.google.com/analytics/devguides/reporting/data/v1/rest/v1beta/properties/batchRunPivotReports) | `AnalyticsDataApi::batch_run_pivot_reports` | Multiple pivot reports in one request |
| [`runRealtimeReport`](https://developers.google.com/analytics/devguides/reporting/data/v1/rest/v1beta/properties/runRealtimeReport) | `AnalyticsDataApi::run_realtime_report` | Realtime active users for the last 30 minutes |
| [`checkCompatibility`](https://developers.google.com/analytics/devguides/reporting/data/v1/rest/v1beta/properties/checkCompatibility) | `AnalyticsDataApi::check_compatibility` | Check which dimensions/metrics can be combined |
| [`getMetadata`](https://developers.google.com/analytics/devguides/reporting/data/v1/rest/v1beta/properties/getMetadata) | `AnalyticsDataApi::get_metadata` | List all available dimensions and metrics, including custom ones |
- **Async / await** — built on `reqwest` + `tokio`, with a shared connection pool
- **Strongly typed** — request and response structs mirror the official GA4 REST schema (`FilterExpression`, `OrderBy`, `CohortSpec`, `Pivot`, …)
- **Filter support** — string / in-list / numeric / between filters with `andGroup`, `orGroup`, `notExpression`
- **Proper error handling** — `GoogleApiError` implements `std::error::Error`, distinguishing network errors, non-2xx API responses, and JSON parse failures
- **Auth-agnostic** — pass any OAuth2 / service-account access token as `&str` (works with `yup-oauth2`, `gcp_auth`, etc.)
## Installation
```toml
[dependencies]
google-analytics-api-ga4 = "0.3"
```
## Quick start
### 1. Get an access token (service account)
Any library that produces a Google OAuth2 access token works. Example with
[yup-oauth2](https://crates.io/crates/yup-oauth2):
```rust
async fn token() -> yup_oauth2::AccessToken {
let secret = yup_oauth2::read_service_account_key("./service_account.json")
.await
.expect("service_account.json");
let auth = yup_oauth2::ServiceAccountAuthenticator::builder(secret)
.build()
.await
.unwrap();
let scopes = &["https://www.googleapis.com/auth/analytics.readonly"];
auth.token(scopes).await.unwrap()
}
```
> Remember to add the service account's email address as a viewer of your GA4
> property (Admin → Property Access Management).
### 2. Run a report (`runReport`)
```rust
use google_analytics_api_ga4::types::{
DateRange, Dimension, Filter, FilterExpression, FilterExpressionList, MatchType, Metric,
OrderBy, StringFilter,
};
use google_analytics_api_ga4::{AnalyticsDataApi, RunReportRequest};
async fn example(token: &str) {
let property_id = "123456789";
// WHERE fullPageUrl BEGINS_WITH "example.com"
let dimension_filter = FilterExpression {
and_group: Some(FilterExpressionList {
expressions: Some(vec![FilterExpression {
filter: Some(Filter {
field_name: Some("fullPageUrl".to_string()),
string_filter: Some(StringFilter {
match_type: Some(MatchType::BeginsWith),
value: Some("example.com".to_string()),
..StringFilter::default()
}),
..Filter::default()
}),
..FilterExpression::default()
}]),
}),
..FilterExpression::default()
};
let request = RunReportRequest {
property: format!("properties/{}", property_id),
dimensions: Dimension::from_string_vec(vec!["fullPageUrl", "eventName"]),
metrics: Metric::from_string_vec(vec!["sessions", "screenPageViews", "eventCount"]),
date_ranges: vec![DateRange::new("range", "2024-01-01", "2024-01-31")],
dimension_filter: Some(dimension_filter),
order_bys: Some(vec![OrderBy::metric("sessions", true)]), // ORDER BY sessions DESC
limit: Some("20".to_string()),
..RunReportRequest::default()
};
let response = AnalyticsDataApi::run_report(token, property_id, request)
.await
.unwrap();
for row in response.rows.unwrap_or_default() {
println!("{:?} {:?}", row.dimension_values, row.metric_values);
}
}
```
### 3. Realtime active users (`runRealtimeReport`)
```rust
use google_analytics_api_ga4::types::{Dimension, Metric};
use google_analytics_api_ga4::{AnalyticsDataApi, MinuteRange, RunRealtimeReportRequest};
async fn example(token: &str) {
let property_id = "123456789";
let request = RunRealtimeReportRequest {
dimensions: Dimension::from_string_vec(vec!["unifiedScreenName"]),
metrics: Metric::from_string_vec(vec!["activeUsers"]),
minute_ranges: Some(vec![MinuteRange::new("last 5 min", 5, 0)]),
..RunRealtimeReportRequest::default()
};
let response = AnalyticsDataApi::run_realtime_report(token, property_id, request)
.await
.unwrap();
println!("{:?}", response.rows);
}
```
### 4. Discover dimensions and metrics (`getMetadata`)
```rust
use google_analytics_api_ga4::AnalyticsDataApi;
async fn example(token: &str) {
let metadata = AnalyticsDataApi::get_metadata(token, "123456789")
.await
.unwrap();
for dimension in metadata.dimensions.unwrap_or_default() {
println!("{:?}: {:?}", dimension.api_name, dimension.ui_name);
}
}
```
## Error handling
Every method returns `Result<_, GoogleApiError>`:
| `GoogleApiError::Connection(message)` | Network-level failure (DNS, TLS, timeout) |
| `GoogleApiError::Response(status, body)` | The API returned a non-2xx status (quota exceeded, permission denied, invalid argument, …) |
| `GoogleApiError::JsonParse(body)` | The response body could not be deserialized |
`GoogleApiError` implements `std::error::Error` and `Display`, so it composes
with `?`, `anyhow`, and `thiserror`.
## FAQ
**Q. Does this crate support Universal Analytics (UA / GA3)?**
No. It targets Google Analytics 4 (GA4) only, via the Data API v1beta. Universal
Analytics was shut down by Google in 2023–2024.
**Q. Which authentication methods are supported?**
Any Google OAuth2 access token: service accounts (recommended for servers),
user OAuth flows, workload identity, etc. The token is passed as a plain `&str`,
so you can use `yup-oauth2`, `gcp_auth`, or your own token source.
**Q. How is this different from `google-analyticsdata1_beta` (google-apis-rs)?**
This crate is hand-written and minimal: a handful of files, three dependencies,
and idiomatic builder-free structs. The auto-generated alternative covers more
surface area but is heavier and harder to read.
**Q. Which GA4 dimensions and metrics can I use?**
Any from the official
[API schema](https://developers.google.com/analytics/devguides/reporting/data/v1/api-schema)
(e.g. `sessions`, `activeUsers`, `screenPageViews`, `eventCount`, `country`,
`fullPageUrl`, `eventName`), plus your custom definitions. Use
`AnalyticsDataApi::get_metadata` to list everything available on your property,
and `check_compatibility` to verify combinations.
**Q. Is the Data API quota handled?**
Set `return_property_quota: Some(true)` on a request and read
`response.property_quota` to monitor consumed/remaining tokens per property.
## 日本語での概要
**google-analytics-api-ga4** は、Google アナリティクス 4(GA4)の Data API v1beta を
Rust から呼び出すための非同期クライアントライブラリです。`runReport` による
レポート取得、`runRealtimeReport` によるリアルタイムのアクティブユーザー数取得、
ピボットレポート、ディメンション・指標のメタデータ取得に対応しています。
サービスアカウントのアクセストークンを文字列で渡すだけで利用でき、GA4 データの
データベース連携・ダッシュボード構築・SEO レポート自動化などに使えます。
## Testing
```shell
# Offline serialization tests (no credentials needed)
cargo test
# Integration tests against the live API (requires ./test.json service account key)
cargo test -- --ignored
```
## Links
- [API reference on docs.rs](https://docs.rs/google-analytics-api-ga4)
- [crates.io page](https://crates.io/crates/google-analytics-api-ga4)
- [Google Analytics Data API v1beta reference](https://developers.google.com/analytics/devguides/reporting/data/v1/rest)
- [GA4 dimensions & metrics schema](https://developers.google.com/analytics/devguides/reporting/data/v1/api-schema)
- [Realtime API schema](https://developers.google.com/analytics/devguides/reporting/data/v1/realtime-api-schema)
## Breaking changes in 0.3.0
- Upgraded `reqwest` from 0.12 to 0.13. The default TLS backend is now `rustls` (with `aws-lc-rs`) instead of `native-tls` / OpenSSL, so OpenSSL development headers are no longer required to build. If you depend on `reqwest` yourself with `default-features = false`, make sure you enable a TLS backend (`rustls` or `native-tls`).
- Minimum supported Rust version is now 1.85 (required by `reqwest` 0.13).
## Breaking changes in 0.2.0
- `AnalyticsDataApi::run_pivot_report` now takes `RunPivotReportRequest` and returns `RunPivotReportResponse` (previously it mistakenly used the checkCompatibility types).
- `AnalyticsDataApi::batch_run_reports` now returns `BatchRunReportsResponse` (with a `reports` field).
- `AnalyticsDataApi::batch_run_pivot_reports` now takes `Vec<RunPivotReportRequest>` and returns `BatchRunPivotReportsResponse`.
- `RunReportRequest::order_bys` / `RunRealtimeReportRequest::order_bys` are now `Option<Vec<OrderBy>>` (matching the API schema; previously `DimensionOrderBy` was serialized without the `OrderBy` wrapper).
- `MinuteRange::start_minutes_ago` / `end_minutes_ago` are now `Option<i32>`.
- `FilterExpression::not_expression` is now `Option<Box<FilterExpression>>`.
- `NumericValue::double_value` is now `Option<f64>`; `int64_value` was renamed from `int_64_value`.
- `MetricMetadata` fields are now all `Option` (robust deserialization).
- All struct fields are now public, and `None` fields are no longer serialized as `null`.
- `GoogleApiError` gained a `Response(u16, String)` variant for non-2xx responses.
## License
MIT