0

On a Ubuntu server I want to regularly execute a script which calls the Google Analytics Data API in order to extract the 20 most visited pages of a website.

  • I have successfully installed the gcloud CLI (Google Cloud CLI) on my Ubuntu 24 server.
  • I have created a service account in my Google Cloud Account and a assigned a Google created secret Key to it and downloaded the key.json file (used for authentication later).
  • In the relevant Google Analytics Admin section i created a new user (with the email address of the service account) that has at least read permission to the analytics data.

According to the Google Cloud CLI Documentation i created the following bash script:

#!/bin/bash

# Set environment variables
export KEY_FILE="/home/yesitsme/utilities/scripts/gcloud.json"
export PROPERTY_ID="278767000"

# Authenticate using the service account
gcloud auth activate-service-account --key-file=$KEY_FILE

# Get access token
ACCESS_TOKEN=$(gcloud auth print-access-token)

# Make API request for page views in the last 24 hours
curl -X POST \
  "https://analyticsdata.googleapis.com/v1beta/properties/$PROPERTY_ID:runReport" \
  -H "Authorization: Bearer $ACCESS_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
        "dateRanges": [
          {
            "startDate": "yesterday",
            "endDate": "today"
          }
        ],
        "dimensions": [
          {
            "name": "pagePath"
          }
        ],
        "metrics": [
          {
            "name": "screenPageViews"
          }
        ],
        "limit": 5
      }'

If i execute the gcloud auth command only, i see that the access token gets created successfully.

But when executing the script, i get this output with an error:

Activated service account credentials for: [[email protected]]
{
  "error": {
    "code": 403,
    "message": "Request had insufficient authentication scopes.",
    "status": "PERMISSION_DENIED",
    "details": [
      {
        "@type": "type.googleapis.com/google.rpc.ErrorInfo",
        "reason": "ACCESS_TOKEN_SCOPE_INSUFFICIENT",
        "domain": "googleapis.com",
        "metadata": {
          "method": "google.analytics.data.v1beta.BetaAnalyticsData.RunReport",
          "service": "analyticsdata.googleapis.com"
        }
      }
    ]
  }
}

So the question is clear: why does my newly created service account have not enough permissions? Any hint?

5
  • 1
    I think the issue is that the Service Account has insufficient OAuth Scopes for Google Analytics. There are probably better ways but I've always used the obscure-but-useful oauth2l for this purpose. Instead (!) of gcloud auth activate-service-account and ACCESS_TOKEN=$(gcloud auth print-access-token), you should (!?) be able to ACCESS_TOKEN=$(oauth2l fetch --credentials=${KEY_FILE} --scope=https://www.googleapis.com/auth/analytics).
    – DazWilkin
    Commented Dec 3 at 16:04
  • You can verify this behavior by taking ACCESS_TOKEN=$(gcloud auth print-access-token) (your original credentials) and browsing https://www.googleapis.com/oauth2/v1/tokeninfo?access_token=${ACCESS_TOKEN}, this should have the default Cloud Platform scopes and not Google Analytics. Then use the ACCESS_TOKEN value using oauth2l .... and you should see Google Analytics scope.
    – DazWilkin
    Commented Dec 3 at 16:06
  • 1
    You can also use oauth2l to view token scopes: oauth2l info --token ${ACCESS_TOKEN}
    – DazWilkin
    Commented Dec 3 at 16:07
  • Why is Google not officially mentioning oauth2l in all their documentation when it comes to retrieve data from Google Analytics Data API?
    – basZero
    Commented Dec 3 at 21:11
  • It's not specific to Google Analytics. It's a general-purpose tool for working with Google credentials, tokens and scopes. There are probably better ways to achieve the same result but that's the way I know/prefer.
    – DazWilkin
    Commented Dec 3 at 21:52

1 Answer 1

0

Thanks to the hint of @DazWilkin i found a working solution. Here is what I have done:

  • In the Google Cloud Console, setup a new Service Account and let Google create the secret.json (login credentials containing the private key)
  • In the Google Cloud Console, enable the Google Analytics Data API
  • Write down your Google Analytics Property ID
  • Install gcloud on your server, initialize it with gcloud init and configure the newly created service account.
  • Install oauth2l on your server (https://github.com/google/oauth2l), verify it by executing oauth2l --help

Here is the full shell script that accesses your Google Analytics:

#!/bin/bash

# Set variables
KEY_FILE="/home/myuser/gcloud-service-account-secret.json"
PROPERTY_ID="27yy67xxx"
SCOPE="https://www.googleapis.com/auth/analytics.readonly"

# Get an access token
ACCESS_TOKEN=$(oauth2l fetch --credentials=$KEY_FILE --scope=$SCOPE)

# Check if token retrieval was successful
if [ -z "$ACCESS_TOKEN" ]; then
  echo "Failed to retrieve access token"
  exit 1
fi

# Query Google Analytics Data API
curl -s -X POST \
  "https://analyticsdata.googleapis.com/v1beta/properties/$PROPERTY_ID:runReport" \
  -H "Authorization: Bearer $ACCESS_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
        "dateRanges": [
          {
            "startDate": "yesterday",
            "endDate": "today"
          }
        ],
        "dimensions": [
          {
            "name": "pagePath"
          }
        ],
        "metrics": [
          {
            "name": "screenPageViews"
          }
        ],
  "dimensionFilter": {
         "orGroup": {
            "expressions": [
              {
                "filter": {
                  "fieldName": "pagePath",
                  "stringFilter": {
                    "matchType": "BEGINS_WITH",
                    "value": "/path1_i_want_to_include/"
                  }
                }
              },
              {
                "filter": {
                  "fieldName": "pagePath",
                  "stringFilter": {
                    "matchType": "BEGINS_WITH",
                    "value": "/path2/i/want/to_include/"
                  }
                }
              }
            ]
          }
  },
        
        "limit": 50
      }'

The result is simply a JSON file that you can store on disk and further process with your backend software.

Not the answer you're looking for? Browse other questions tagged or ask your own question.