Summary and recommendation
The Google Analytics Admin API (v1beta) manages GA4 user access through the `accessBindings` resource, replacing the deprecated v1alpha `userLinks` endpoints. Authentication requires OAuth 2.0; service accounts are supported for server-to-server flows but must themselves be granted a GA4 role (e.g., Administrator) at the target account or property before they can manage other users' bindings.
The minimum required scope for write operations is `https://www.googleapis.com/auth/analytics.manage.users`. Read-only audits can use `analytics.manage.users.readonly`. Account-level and property-level bindings are entirely separate resources - there is no unified endpoint that returns all bindings across both levels in a single call.
This API is a natural integration target for identity graph construction: by paginating `accessBindings` across all accounts and properties, you can build a complete map of which Google identities hold which roles at which GA4 resource level, then reconcile that against your IdP's source of truth.
API quick reference
| Has user API | Yes |
| Auth method | OAuth 2.0 |
| Base URL | Official docs |
| SCIM available | No |
| SCIM plan required | N/A - GA4 does not expose a SCIM endpoint. SCIM provisioning for Google accounts is handled at the Google Cloud Identity / Google Workspace level, not within GA4 directly. |
Authentication
Auth method: OAuth 2.0
Setup steps
- Create a project in Google Cloud Console (console.cloud.google.com).
- Enable the 'Google Analytics Admin API' for the project.
- Create OAuth 2.0 credentials (Web, Desktop, or Service Account depending on use case).
- For service accounts: grant the service account a GA4 role (e.g., Editor or Admin) at the account or property level in GA4 UI or via API.
- Request the appropriate OAuth scopes during the authorization flow.
- Exchange authorization code for access token and refresh token (OAuth 2.0 Authorization Code flow) or use service account JWT for server-to-server.
Required scopes
| Scope | Description | Required for |
|---|---|---|
| https://www.googleapis.com/auth/analytics.manage.users | Read and write GA4 user access bindings (create, update, delete users). | Creating, updating, and deleting accessBindings |
| https://www.googleapis.com/auth/analytics.manage.users.readonly | Read-only access to GA4 user access bindings. | Listing and getting accessBindings |
| https://www.googleapis.com/auth/analytics.readonly | Read-only access to GA4 configuration data including account/property metadata. | Reading account and property metadata |
| https://www.googleapis.com/auth/analytics.edit | Read and write GA4 configuration data. | Editing account and property settings |
User object / data model
| Field | Type | Description | On create | On update | Notes |
|---|---|---|---|---|---|
| name | string | Resource name of the access binding, e.g. accounts/123/accessBindings/456 | server-assigned | immutable | Used as identifier in GET/PATCH/DELETE |
| user | string | Email address of the user being granted access. | required | immutable | Must be a valid Google Account email. Mutually exclusive with 'groupEmail'. |
| roles | array |
List of GA4 predefined roles assigned to the user. | required | updatable | Valid values: predefinedRoles/viewer, predefinedRoles/analyst, predefinedRoles/editor, predefinedRoles/marketer, predefinedRoles/admin |
Core endpoints
List access bindings (users) on an account
- Method: GET
- URL:
https://analyticsadmin.googleapis.com/v1beta/accounts/{account}/accessBindings - Watch out for: Returns only direct account-level bindings; property-level bindings must be queried separately per property.
Request example
GET /v1beta/accounts/123456/accessBindings?pageSize=200
Authorization: Bearer {token}
Response example
{
"accessBindings": [
{
"name": "accounts/123456/accessBindings/789",
"user": "user@example.com",
"roles": ["predefinedRoles/viewer"]
}
],
"nextPageToken": "abc123"
}
Get a single access binding on an account
- Method: GET
- URL:
https://analyticsadmin.googleapis.com/v1beta/accounts/{account}/accessBindings/{accessBinding} - Watch out for: Requires the exact resource name; no lookup by email directly.
Request example
GET /v1beta/accounts/123456/accessBindings/789
Authorization: Bearer {token}
Response example
{
"name": "accounts/123456/accessBindings/789",
"user": "user@example.com",
"roles": ["predefinedRoles/editor"]
}
Create access binding (add user) on an account
- Method: POST
- URL:
https://analyticsadmin.googleapis.com/v1beta/accounts/{account}/accessBindings - Watch out for: User must have a Google Account. Adding at account level does not automatically grant property-level access unless inherited.
Request example
POST /v1beta/accounts/123456/accessBindings
Authorization: Bearer {token}
{
"user": "newuser@example.com",
"roles": ["predefinedRoles/viewer"]
}
Response example
{
"name": "accounts/123456/accessBindings/999",
"user": "newuser@example.com",
"roles": ["predefinedRoles/viewer"]
}
Update access binding (change roles) on an account
- Method: PATCH
- URL:
https://analyticsadmin.googleapis.com/v1beta/accounts/{account}/accessBindings/{accessBinding} - Watch out for: Full roles array must be provided; partial updates replace the entire roles list.
Request example
PATCH /v1beta/accounts/123456/accessBindings/999
Authorization: Bearer {token}
{
"name": "accounts/123456/accessBindings/999",
"roles": ["predefinedRoles/editor"]
}
Response example
{
"name": "accounts/123456/accessBindings/999",
"user": "newuser@example.com",
"roles": ["predefinedRoles/editor"]
}
Delete access binding (remove user) from an account
- Method: DELETE
- URL:
https://analyticsadmin.googleapis.com/v1beta/accounts/{account}/accessBindings/{accessBinding} - Watch out for: Returns empty body on success (HTTP 200). Does not remove property-level bindings for the same user.
Request example
DELETE /v1beta/accounts/123456/accessBindings/999
Authorization: Bearer {token}
Response example
{}
Batch create access bindings on an account
- Method: POST
- URL:
https://analyticsadmin.googleapis.com/v1beta/accounts/{account}/accessBindings:batchCreate - Watch out for: Max 1000 bindings per batch call. All requests in a batch must reference the same parent account.
Request example
POST /v1beta/accounts/123456/accessBindings:batchCreate
{
"requests": [
{"accessBinding": {"user": "a@example.com", "roles": ["predefinedRoles/viewer"]}},
{"accessBinding": {"user": "b@example.com", "roles": ["predefinedRoles/analyst"]}}
]
}
Response example
{
"accessBindings": [
{"name": "accounts/123456/accessBindings/1001", "user": "a@example.com", "roles": ["predefinedRoles/viewer"]},
{"name": "accounts/123456/accessBindings/1002", "user": "b@example.com", "roles": ["predefinedRoles/analyst"]}
]
}
List access bindings (users) on a property
- Method: GET
- URL:
https://analyticsadmin.googleapis.com/v1beta/properties/{property}/accessBindings - Watch out for: Property-level bindings are independent of account-level bindings. A user can have different roles at each level.
Request example
GET /v1beta/properties/987654/accessBindings?pageSize=200
Authorization: Bearer {token}
Response example
{
"accessBindings": [
{
"name": "properties/987654/accessBindings/111",
"user": "user@example.com",
"roles": ["predefinedRoles/analyst"]
}
]
}
Batch delete access bindings on an account
- Method: POST
- URL:
https://analyticsadmin.googleapis.com/v1beta/accounts/{account}/accessBindings:batchDelete - Watch out for: Returns empty body on success. All names must belong to the same parent account.
Request example
POST /v1beta/accounts/123456/accessBindings:batchDelete
{
"names": [
"accounts/123456/accessBindings/1001",
"accounts/123456/accessBindings/1002"
]
}
Response example
{}
Rate limits, pagination, and events
- Rate limits: Google Analytics Admin API enforces per-project and per-user quotas. Default quota is 10 queries per second (QPS) per project for most Admin API methods. Batch operations count as multiple requests.
- Rate-limit headers: Yes
- Retry-After header: No
- Rate-limit notes: HTTP 429 returned on quota exhaustion. Use exponential backoff. Batch endpoints (batchCreate, batchUpdate, batchDelete) accept up to 1000 bindings per call but each binding counts toward quota. Quota increases requested via Google Cloud Console.
- Pagination method: token
- Default page size: 200
- Max page size: 200
- Pagination pointer: pageToken
| Plan | Limit | Concurrent |
|---|---|---|
| GA4 Standard (Free) | 10 QPS per project; 50,000 requests/day default | 0 |
| GA4 360 | Higher quota available on request via Google Cloud Console quota increase | 0 |
- Webhooks available: No
- Webhook notes: Google Analytics Admin API does not provide webhook or push notification support for user management events.
- Alternative event strategy: Poll the accessBindings.list endpoint periodically to detect changes. Google Cloud Audit Logs (via Cloud Logging) can capture Admin API write operations if the GA4 account is linked to a Google Cloud project.
SCIM API status
- SCIM available: No
- SCIM version: Not documented
- Plan required: N/A - GA4 does not expose a SCIM endpoint. SCIM provisioning for Google accounts is handled at the Google Cloud Identity / Google Workspace level, not within GA4 directly.
- Endpoint: Not documented
Limitations:
- GA4 has no native SCIM endpoint.
- User provisioning to Google accounts can be done via Google Workspace SCIM (supported by Okta, Entra ID, etc.), but this only creates/manages the Google account, not GA4 property/account access.
- GA4 role assignments must be managed separately via the Admin API or GA4 UI.
- No automated sync between IdP group membership and GA4 roles.
Common scenarios
Bulk onboarding to a property uses POST /v1beta/properties/{property}/accessBindings:batchCreate with up to 1000 user objects per request. Each binding in the batch counts individually toward the 10 QPS / 50,000 requests-per-day default quota.
Batch operations are not transactional - partial failures return per-item errors while successful items are committed; callers must inspect each response item and handle retries independently.
Full access audits require two separate pagination loops: one against accounts/{account}/accessBindings and one against properties/{property}/accessBindings for each property under the account. A user can hold different roles at both levels simultaneously; account-level roles do not override property-level roles. Merging both result sets client-side is required to produce an accurate access report.
Offboarding has no single 'remove from all resources' endpoint. Each account-level and property-level binding must be located by filtering the user field client-side and deleted individually via DELETE. Full offboarding also requires revoking the underlying Google account at the Google Workspace or Cloud Identity level - the GA4 API only removes GA4 role bindings, not the Google account itself.
Bulk onboard a team to a GA4 property
- Authenticate with OAuth 2.0 using scope https://www.googleapis.com/auth/analytics.manage.users.
- Prepare a list of user emails and desired roles (e.g., predefinedRoles/analyst).
- POST to /v1beta/properties/{property}/accessBindings:batchCreate with up to 1000 user objects per request.
- Parse the response to confirm each binding was created; log any errors for retry.
- Optionally repeat for account-level bindings if account access is also required.
Watch out for: batchCreate is atomic per request but not transactional across requests; partial failures return errors for individual items while others succeed. Check each item in the response.
Audit all users with access to a GA4 account and its properties
- Authenticate with scope https://www.googleapis.com/auth/analytics.manage.users.readonly.
- GET /v1beta/accounts/{account}/accessBindings with pageSize=200; paginate using nextPageToken until exhausted.
- List all properties under the account: GET /v1beta/properties?filter=parent:accounts/{account}.
- For each property, GET /v1beta/properties/{property}/accessBindings and paginate.
- Merge account-level and property-level bindings to produce a full access report.
Watch out for: A user may appear at both account and property level with different roles. Account-level roles do not override property-level roles; both apply independently.
Remove a departed employee's access from all GA4 resources
- Authenticate with scope https://www.googleapis.com/auth/analytics.manage.users.
- List account-level bindings: GET /v1beta/accounts/{account}/accessBindings; filter results client-side for the target email.
- If found, DELETE /v1beta/accounts/{account}/accessBindings/{bindingName}.
- List all properties and for each, GET /v1beta/properties/{property}/accessBindings; filter for the target email.
- DELETE each matching property-level binding.
- Verify removal by re-listing bindings for the user's email.
Watch out for: There is no single 'remove user from all resources' endpoint. Each account and property binding must be deleted individually. Also revoke the user's Google account access at the Google Workspace/Cloud Identity level if full offboarding is required.
Why building this yourself is a trap
The roles array in a PATCH (update) request replaces the entire roles list - there is no partial update or append operation. Sending an incomplete roles array silently removes any roles not included in the payload.
The user field (email address) is immutable after a binding is created. Changing a user's email requires deleting the existing binding and creating a new one; there is no update path for the email field itself.
Quota exhaustion returns HTTP 429 with no Retry-After header. Exponential backoff must be implemented manually. Quota increase requests are handled through Google Cloud Console, not the Analytics Admin API.
Webhooks are not available. There is no push notification mechanism for access change events. The only options for change detection are periodic polling of accessBindings.list or routing Google Cloud Audit Logs through Cloud Logging if the GA4 account is linked to a Google Cloud project.
GA4 Standard has no native audit log for user removal events within the GA4 UI itself.
Automate Google Analytics workflows without one-off scripts
Stitchflow builds and maintains end-to-end IT automation across your SaaS stack, including apps without APIs. Built for exactly how your company works, with human approvals where they matter.