I'm trying to access Google Ads campaing reports from Python folowing this tutorial.

I've requested my Developer Token with Basic Access. I think it has enough privileges to execute the script. I Can see my token active when I go to "API Center" in google ads.

I've created a project in google cloud and an Oauth Token.

In google Cloud:

  • Created a new project
  • Activated the Google Ads API.
  • When I go to Manage-> Credentials I see that the Oauth token is compatible with that API.
  • I have successfully created a refresh token.

I'm using this script as proof of concept:

import os import json import sys from google.ads.google_ads.errors import GoogleAdsException # Put an account id to download stats from. Note: not MCC, no dash lines CUSTOMER_ID = "xxxxxxxxxx" def get_account_id(account_id, check_only=False):     """     Converts int to str, checks if str has dashes. Returns 10 chars str or raises error     :check_only - if True, returns None instead of Error     """     if isinstance(account_id, int) and len(str(account_id)) == 10:         return str(account_id)     if isinstance(account_id, str) and len(account_id.replace("-", "")) == 10:         return account_id.replace("-", "")     if check_only:         return None     raise ValueError(f"Couldn't recognize account id from {account_id}") def micros_to_currency(micros):     return micros / 1000000.0 def main(client, customer_id):          ga_service = client.get_service("GoogleAdsService")#     , version="v5")     query = """         SELECT           campaign.id,           campaign.name,           ad_group.id,           ad_group.name,           ad_group_criterion.criterion_id,           ad_group_criterion.keyword.text,           ad_group_criterion.keyword.match_type,           metrics.impressions,           metrics.clicks,           metrics.cost_micros         FROM keyword_view         WHERE           segments.date DURING LAST_7_DAYS           AND campaign.advertising_channel_type = 'SEARCH'           AND ad_group.status = 'ENABLED'           AND ad_group_criterion.status IN ('ENABLED', 'PAUSED')         ORDER BY metrics.impressions DESC         LIMIT 50"""     # Issues a search request using streaming.     response = ga_service.search_stream(customer_id, query) #THIS LINE GENERATES THE ERROR     keyword_match_type_enum = client.get_type(         "KeywordMatchTypeEnum"     ).KeywordMatchType     try:         for batch in response:             for row in batch.results:                 campaign = row.campaign                 ad_group = row.ad_group                 criterion = row.ad_group_criterion                 metrics = row.metrics                 keyword_match_type = keyword_match_type_enum.Name(                     criterion.keyword.match_type                 )                 print(                     f'Keyword text "{criterion.keyword.text}" with '                     f'match type "{keyword_match_type}" '                     f"and ID {criterion.criterion_id} in "                     f'ad group "{ad_group.name}" '                     f'with ID "{ad_group.id}" '                     f'in campaign "{campaign.name}" '                     f"with ID {campaign.id} "                     f"had {metrics.impressions} impression(s), "                     f"{metrics.clicks} click(s), and "                     f"{metrics.cost_micros} cost (in micros) during "                     "the last 7 days."                 )     except GoogleAdsException as ex:         print(             f'Request with ID "{ex.request_id}" failed with status '             f'"{ex.error.code().name}" and includes the following errors:'         )         for error in ex.failure.errors:             print(f'\tError with message "{error.message}".')             if error.location:                 for field_path_element in error.location.field_path_elements:                     print(f"\t\tOn field: {field_path_element.field_name}")         sys.exit(1) if __name__ == "__main__":     # credentials dictonary     creds = {"google_ads": "googleads.yaml"}     if not os.path.isfile(creds["google_ads"]):         raise FileExistsError("File googleads.yaml doesn't exists. ")     resources = {"config": "config.json"}     # This logging allows to see additional information on debugging     import logging     logging.basicConfig(level=logging.INFO, format='[%(asctime)s - %(levelname)s] %(message).5000s')     logging.getLogger('google.ads.google_ads.client').setLevel(logging.DEBUG)     # Initialize the google_ads client     from google.ads.google_ads.client import GoogleAdsClient     gads_client = GoogleAdsClient.load_from_storage(creds["google_ads"])     id_to_load = get_account_id(CUSTOMER_ID)      main(gads_client, id_to_load) 
  • I've changed CUSTOMER_ID to the account number that appears on the upper left corner
  • I've created a googleads.yaml and I've loaded the aforementioned information.

When I execute the script I get this error:

Traceback (most recent call last):  File "download_keywords_from_account.py", line 138, in <module>    main(gads_client, id_to_load)  File "download_keywords_from_account.py", line 70, in main    response = ga_service.search_stream(customer_id, query)  File "google/ads/google_ads/v6/services/google_ads_service_client.py", line 366, in search_stream    return self._inner_api_calls['search_stream'](request, retry=retry, timeout=timeout, metadata=metadata)  File google/api_core/gapic_v1/method.py", line 145, in __call__    return wrapped_func(*args, **kwargs)  File "google/api_core/retry.py", line 281, in retry_wrapped_func    return retry_target(  File "google/api_core/retry.py", line 184, in retry_target    return target()  File "google/api_core/timeout.py", line 214, in func_with_timeout    return func(*args, **kwargs)  File "google/api_core/grpc_helpers.py", line 152, in error_remapped_callable    six.raise_from(exceptions.from_grpc_error(exc), exc)  File "<string>", line 3, in raise_from google.api_core.exceptions.PermissionDenied: 403 Request had insufficient authentication scopes

The googleads.yaml file looks like this:

  #############################################################################   # Required Fields                                                           #   #############################################################################   developer_token: {developer token as seen in google ads -> tools -> api center}   #############################################################################   # Optional Fields                                                           #   #############################################################################   login_customer_id: {Id from the top left corner in google ads, only numbers}   # user_agent: INSERT_USER_AGENT_HERE   # partial_failure: True   validate_only: False   #############################################################################   # OAuth2 Configuration                                                      #   # Below you may provide credentials for either the installed application or #   # service account flows. Remove or comment the lines for the flow you're    #   # not using.                                                                #   #############################################################################   # The following values configure the client for the installed application   # flow.   client_id: {Oauth client id taken from gcloud -> api -> credentials} ends with apps.googleusercontent.com   client_secret: {got it while generating the token}   refresh_token:  1//0hr.... made with generate_refresh_token.py    # The following values configure the client for the service account flow.   path_to_private_key_file: ads.json   # delegated_account: INSERT_DOMAIN_WIDE_DELEGATION_ACCOUNT   #############################################################################   # ReportDownloader Headers                                                  #   # Below you may specify boolean values for optional headers that will be    #   # applied to all requests made by the ReportDownloader utility by default.  #   #############################################################################   # report_downloader_headers:     # skip_report_header: False     # skip_column_header: False     # skip_report_summary: False     # use_raw_enum_values: False 

NOTES:

The file ads.json contains the private key downloaded from the credentials page in gcloud.

I've seen some posts on this issue but none of them are Python + GoogleADs and I couldn't find a solution there either.

I have also tried other Python + GoogleAds examples getting the same error. This makes me think that I must be configuring something wrong in gcloud / google ads. But I don't understand what.

Please help me make the query I'm really stuck.

Thanks a lot!

Tag:google-ads-api, google-cloud-platform, python

Only one comment.

  1. jerego

    Comments of @DazWilkin solved my problem. Thanks!

Add a new comment.