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!

I am having a hard time to find a documentation for web conversion tracking for Google Ads.

I would like to understand more about the HTTP POST request that is sent to Google. I can see there is a good documentation for mobile apps: https://developers.google.com/app-conversion-tracking/api/request-response-specs?hl=en#conversion_tracking_request

Is there similar documentation for web? I would like to understand more how i could send conversions to Google creating manually my HTTP request.

I am aware of the Offline conversions API (https://developers.google.com/google-ads/api/docs/samples/upload-offline-conversion), but it doesn't support the new parameters as wbraid

We are using the BigQuery Data Transfer Service that is based on the AdWords API, but we're missing some of the campaigns. If we write a custom transfer for Google Ads we can get around the issue, but was wondering if there is a timeline yet for a Google Ads transfer seeing as Adwords is being discontinued in April 2022.

Just trying to work out whether to write something custom or hang in there if the new transfer service is imminent. Is there any news on this please?

I'm looking into the Twilio Whatsapp API docs, and cannot find anywhere if it is possible to receive a message sent from a website webchat on a Whatsapp Business Account?

I would like to develop the following: a user is on my website, and has a quesiton. He/she then opens the chatbox and types their question (non-whatsapp). This message is then sent to the Whatsapp Business Account of the Client and they can talk back and forth with the user.

I hope I have been clear enough, please let me know if I need to supply more information.