Add Business Profile Location Extensions

Java

// Copyright 2019 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package com.google.ads.googleads.examples.extensions;

import static com.google.ads.googleads.examples.utils.CodeSampleHelper.getPrintableDateTime;

import com.beust.jcommander.Parameter;
import com.google.ads.googleads.examples.utils.ArgumentNames;
import com.google.ads.googleads.examples.utils.CodeSampleParams;
import com.google.ads.googleads.lib.GoogleAdsClient;
import com.google.ads.googleads.v13.common.MatchingFunction;
import com.google.ads.googleads.v13.common.Operand;
import com.google.ads.googleads.v13.common.Operand.ConstantOperand;
import com.google.ads.googleads.v13.enums.FeedOriginEnum.FeedOrigin;
import com.google.ads.googleads.v13.enums.MatchingFunctionOperatorEnum.MatchingFunctionOperator;
import com.google.ads.googleads.v13.enums.PlaceholderTypeEnum.PlaceholderType;
import com.google.ads.googleads.v13.errors.GoogleAdsError;
import com.google.ads.googleads.v13.errors.GoogleAdsException;
import com.google.ads.googleads.v13.resources.CustomerFeed;
import com.google.ads.googleads.v13.resources.Feed;
import com.google.ads.googleads.v13.resources.Feed.PlacesLocationFeedData;
import com.google.ads.googleads.v13.resources.Feed.PlacesLocationFeedData.OAuthInfo;
import com.google.ads.googleads.v13.services.CustomerFeedOperation;
import com.google.ads.googleads.v13.services.CustomerFeedServiceClient;
import com.google.ads.googleads.v13.services.FeedOperation;
import com.google.ads.googleads.v13.services.FeedServiceClient;
import com.google.ads.googleads.v13.services.MutateCustomerFeedsResponse;
import com.google.ads.googleads.v13.services.MutateFeedsResponse;
import com.google.common.collect.ImmutableList;
import java.io.FileNotFoundException;
import java.io.IOException;

/**
 * Adds a feed that syncs feed items from a Business Profile account and associates the feed with a
 * customer.
 */
public class AddBusinessProfileLocationExtensions {
  // The required scope for setting the OAuth info.
  private final String GOOGLE_ADS_SCOPE = "https://www.googleapis.com/auth/adwords";

  // The maximum number of CustomerFeed ADD operation attempts to make before throwing an exception.
  private static final int MAX_CUSTOMER_FEED_ADD_ATTEMPTS = 10;

  private static class AddBusinessProfileLocationExtensionsParams extends CodeSampleParams {

    @Parameter(names = ArgumentNames.CUSTOMER_ID, required = true)
    private Long customerId;

    @Parameter(names = ArgumentNames.BUSINESS_PROFILE_EMAIL_ADDRESS, required = true)
    private String businessProfileEmailAddress;

    @Parameter(names = ArgumentNames.BUSINESS_ACCOUNT_IDENTIFIER)
    private String businessAccountIdentifier;

    @Parameter(names = ArgumentNames.BUSINESS_PROFILE_ACCESS_TOKEN, required = true)
    private String businessProfileAccessToken;
  }

  public static void main(String[] args) throws InterruptedException, IOException {
    AddBusinessProfileLocationExtensionsParams params =
        new AddBusinessProfileLocationExtensionsParams();

    if (!params.parseArguments(args)) {

      // Either pass the required parameters for this example on the command line, or insert them
      // into the code here. See the parameter class definition above for descriptions.
      params.customerId = Long.parseLong("INSERT_CUSTOMER_ID_HERE");
      params.businessProfileEmailAddress = "INSERT_BUSINESS_PROFILE_EMAIL_ADDRESS_HERE";
      params.businessAccountIdentifier = "INSERT_BUSINESS_ACCOUNT_IDENTIFIER_HERE";
      params.businessProfileAccessToken = "INSERT_BUSINESS_PROFILE_ACCESS_TOKEN_HERE";
    }

    GoogleAdsClient googleAdsClient = null;
    try {
      googleAdsClient = GoogleAdsClient.newBuilder().fromPropertiesFile().build();
    } catch (FileNotFoundException fnfe) {
      System.err.printf(
          "Failed to load GoogleAdsClient configuration from file. Exception: %s%n", fnfe);
      System.exit(1);
    } catch (IOException ioe) {
      System.err.printf("Failed to create GoogleAdsClient. Exception: %s%n", ioe);
      System.exit(1);
    }

    try {
      new AddBusinessProfileLocationExtensions()
          .runExample(
              googleAdsClient,
              params.customerId,
              params.businessProfileEmailAddress,
              params.businessAccountIdentifier,
              params.businessProfileAccessToken);
    } catch (GoogleAdsException gae) {
      // GoogleAdsException is the base class for most exceptions thrown by an API request.
      // Instances of this exception have a message and a GoogleAdsFailure that contains a
      // collection of GoogleAdsErrors that indicate the underlying causes of the
      // GoogleAdsException.
      System.err.printf(
          "Request ID %s failed due to GoogleAdsException. Underlying errors:%n",
          gae.getRequestId());
      int i = 0;
      for (GoogleAdsError googleAdsError : gae.getGoogleAdsFailure().getErrorsList()) {
        System.err.printf("  Error %d: %s%n", i++, googleAdsError);
      }
      System.exit(1);
    }
  }

  /**
   * Runs the example.
   *
   * @param googleAdsClient the Google Ads API client.
   * @param customerId the client customer ID.
   * @param businessProfileEmailAddress email address associated with the Business Profile account.
   * @param businessAccountIdentifier the account number of the Business Profile account (optional).
   * @param businessProfileAccessToken the access token created using the 'AdWords' scope and the
   *     client ID and client secret of with the Cloud project associated with the Business Profile
   *     account.
   * @throws GoogleAdsException if an API request failed with one or more service errors.
   * @throws InterruptedException if the Thread.sleep operation is interrupted.
   */
  private void runExample(
      GoogleAdsClient googleAdsClient,
      long customerId,
      String businessProfileEmailAddress,
      String businessAccountIdentifier,
      String businessProfileAccessToken)
      throws InterruptedException {
    // Creates a PlacesLocationFeedData object to identify the Business Profile account, specify
    // location filters, and provide authorization for Google Ads to retrieve locations from the
    // account on behalf of the user identified by businessProfileEmailAddress.
    PlacesLocationFeedData.Builder placesLocationFeedData =
        PlacesLocationFeedData.newBuilder()
            .setEmailAddress(businessProfileEmailAddress)
            // Used to filter Business Profile listings by labels. If entries exist in
            // label_filters, only listings that have at least one of the labels set are
            // candidates to be synchronized into FeedItems. If no entries exist in
            // label_filters, then all listings are candidates for syncing.
            .addLabelFilters("Stores in New York")
            // Sets the authentication info to be able to connect Google Ads to the Business Profile
            // account.
            .setOauthInfo(
                OAuthInfo.newBuilder()
                    .setHttpMethod("GET")
                    .setHttpRequestUrl(GOOGLE_ADS_SCOPE)
                    .setHttpAuthorizationHeader("Bearer " + businessProfileAccessToken)
                    .build());

    if (businessAccountIdentifier != null) {
      placesLocationFeedData.setBusinessAccountId(businessAccountIdentifier);
    }

    // Creates a feed that will sync to the Business Profile account. Do not add FeedAttributes to
    // this object as Google Ads will add them automatically because this will be a system generated
    // feed.
    Feed.Builder businessProfileFeed =
        Feed.newBuilder()
            .setName("Business Profile feed #" + getPrintableDateTime())
            // Configures the location feed populated from Business Profile Locations.
            .setPlacesLocationFeedData(placesLocationFeedData)
            // Since this feed's feed items will be managed by Google,
            // you must set its origin to GOOGLE.
            .setOrigin(FeedOrigin.GOOGLE);

    FeedOperation operation = FeedOperation.newBuilder().setCreate(businessProfileFeed).build();

    try (FeedServiceClient feedServiceClient =
        googleAdsClient.getLatestVersion().createFeedServiceClient()) {
      // Adds the feed. Since it is a system generated feed, Google Ads will automatically:
      // 1. Set up the FeedAttributes on the feed.
      // 2. Set up a FeedMapping that associates the FeedAttributes of the feed
      // with the placeholder fields of the LOCATION placeholder type.
      MutateFeedsResponse response =
          feedServiceClient.mutateFeeds(Long.toString(customerId), ImmutableList.of(operation));
      String businessProfileFeedResourceName = response.getResults(0).getResourceName();
      System.out.printf(
          "Business Profile feed created with resource name: %s%n",
          businessProfileFeedResourceName);

      // Adds a CustomerFeed that associates the feed with this customer for
      // the LOCATION placeholder type.
      CustomerFeed customerFeed =
          CustomerFeed.newBuilder()
              .setFeed(businessProfileFeedResourceName)
              .addPlaceholderTypes(PlaceholderType.LOCATION)
              // Creates a matching function that will always evaluate to true.
              .setMatchingFunction(
                  MatchingFunction.newBuilder()
                      .addLeftOperands(
                          Operand.newBuilder()
                              .setConstantOperand(
                                  ConstantOperand.newBuilder().setBooleanValue(true).build())
                              .build())
                      .setFunctionString("IDENTITY(true)")
                      .setOperator(MatchingFunctionOperator.IDENTITY)
                      .build())
              .build();

      CustomerFeedOperation customerFeedOperation =
          CustomerFeedOperation.newBuilder().setCreate(customerFeed).build();

      try (CustomerFeedServiceClient customerFeedServiceClient =
          googleAdsClient.getLatestVersion().createCustomerFeedServiceClient()) {

        // After the completion of the Feed ADD operation above the added feed will not be available
        // for usage in a CustomerFeed until the sync between the Google Ads and Business Profile
        // accounts
        // completes. The loop below will retry adding the CustomerFeed up to ten times with an
        // exponential back-off policy.
        String addedCustomerFeed = null;
        int numberOfAttempts = 0;
        do {
          numberOfAttempts++;
          try {
            MutateCustomerFeedsResponse customerFeedsResponse =
                customerFeedServiceClient.mutateCustomerFeeds(
                    Long.toString(customerId), ImmutableList.of(customerFeedOperation));
            addedCustomerFeed = customerFeedsResponse.getResults(0).getResourceName();
            System.out.printf("Customer feed created with resource name: %s%n", addedCustomerFeed);
          } catch (GoogleAdsException gae) {
            // Waits using exponential backoff policy.
            long sleepSeconds = (long) Math.scalb(5, numberOfAttempts);

            // Exits the loop early if sleepSeconds grows too large in the event that
            // MAX_CUSTOMER_FEED_ADD_ATTEMPTS is set too high.
            if (sleepSeconds > (long) Math.scalb(5, 10)) {
              break;
            }

            System.out.printf(
                "Attempt #%d to add the CustomerFeed was not successful. "
                    + "Waiting %d seconds before trying again.%n",
                numberOfAttempts, sleepSeconds);
            Thread.sleep(sleepSeconds * 1000);
          }
        } while (numberOfAttempts < MAX_CUSTOMER_FEED_ADD_ATTEMPTS && addedCustomerFeed == null);

        if (addedCustomerFeed == null) {
          throw new RuntimeException(
              "Could not create the CustomerFeed after "
                  + MAX_CUSTOMER_FEED_ADD_ATTEMPTS
                  + " attempts. Please retry "
                  + "the CustomerFeed ADD operation later.");
        }

        // OPTIONAL: Create a CampaignFeed to specify which FeedItems to use at the Campaign
        // level.

        // OPTIONAL: Create an AdGroupFeed for even more fine grained control over
        // which feed items are used at the AdGroup level.
      }
    }
  }
}

      

C#

// Copyright 2020 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

using CommandLine;
using Google.Ads.Gax.Examples;
using Google.Ads.GoogleAds.Lib;
using Google.Ads.GoogleAds.V13.Common;
using Google.Ads.GoogleAds.V13.Errors;
using Google.Ads.GoogleAds.V13.Resources;
using Google.Ads.GoogleAds.V13.Services;
using Google.Api.Gax;
using Grpc.Core;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using static Google.Ads.GoogleAds.V13.Common.Operand.Types;
using static Google.Ads.GoogleAds.V13.Enums.FeedOriginEnum.Types;
using static Google.Ads.GoogleAds.V13.Enums.MatchingFunctionOperatorEnum.Types;
using static Google.Ads.GoogleAds.V13.Enums.PlaceholderTypeEnum.Types;
using static Google.Ads.GoogleAds.V13.Resources.Feed.Types;
using static Google.Ads.GoogleAds.V13.Resources.Feed.Types.PlacesLocationFeedData.Types;

namespace Google.Ads.GoogleAds.Examples.V13
{
    /// <summary>
    /// This code example adds a feed that syncs feed items from a Business Profile account
    /// and associates the feed with a customer.
    /// </summary>
    public class AddBusinessProfileLocationExtensions : ExampleBase
    {
        /// <summary>
        /// Command line options for running the <see cref="AddBusinessProfileLocationExtensions"/>
        /// example.
        /// </summary>
        public class Options : OptionsBase
        {
            /// <summary>
            /// The customer ID for which the call is made.
            /// </summary>
            [Option("customerId", Required = true, HelpText =
                "The customer ID for which the call is made.")]
            public long CustomerId { get; set; }

            /// <summary>
            /// The Business Profile login email address.
            /// </summary>
            [Option("businessProfileEmailAddress", Required = true, HelpText =
                "The Business Profile login email address.")]
            public string BusinessProfileEmailAddress { get; set; }

            /// <summary>
            /// The Business Profile account identifier.
            /// </summary>
            [Option("businessAccountId", Required = false, HelpText =
                "The Business Profile account identifier.")]
            public string BusinessAccountId { get; set; }

            /// <summary>
            /// The OAuth2 access token for the Business Profile account.
            /// </summary>
            [Option("businessProfileAccessToken", Required = false, HelpText =
                "The OAuth2 access token for the Business Profile account.")]
            public string BusinessProfileAccessToken { get; set; }
        }

        /// <summary>
        /// Main method, to run this code example as a standalone application.
        /// </summary>
        /// <param name="args">The command line arguments.</param>
        public static void Main(string[] args)
        {
            Options options = ExampleUtilities.ParseCommandLine<Options>(args);

            AddBusinessProfileLocationExtensions codeExample =
                new AddBusinessProfileLocationExtensions();
            Console.WriteLine(codeExample.Description);
            codeExample.Run(new GoogleAdsClient(), options.CustomerId,
                options.BusinessProfileEmailAddress,
                options.BusinessAccountId, options.BusinessProfileAccessToken);
        }

        // The required scope for setting the OAuth info.
        private const string GOOGLE_ADS_SCOPE = "https://www.googleapis.com/auth/adwords";

        // The maximum number of attempts to make to retrieve the FeedMappings before throwing an
        // exception.
        private const int MAX_FEEDMAPPING_RETRIEVAL_ATTEMPTS = 10;

        /// <summary>
        /// Returns a description about the code example.
        /// </summary>
        public override string Description =>
            "This code example adds a feed that syncs feed items from a Business Profile " +
            "account and associates the feed with a customer.";

        /// <summary>
        /// Runs the code example.
        /// </summary>
        /// <param name="client">The Google Ads client.</param>
        /// <param name="customerId">The customer ID for which the call is made.</param>
        /// <param name="businessProfileEmailAddress">The Business Profile login email address.
        /// </param>
        /// <param name="businessAccountId">The Business Profile account identifier.</param>
        /// <param name="businessProfileAccessToken">The OAuth2 access token for the Business
        /// Profile account.</param>
        public void Run(GoogleAdsClient client, long customerId,
            string businessProfileEmailAddress, string businessAccountId,
            string businessProfileAccessToken)
        {
            try
            {
                if (string.IsNullOrEmpty(businessProfileAccessToken))
                {
                    businessProfileAccessToken = client.Config.OAuth2AccessToken;
                }
                string businessProfileFeedResourceName = CreateBusinessProfileFeed(client, customerId, businessProfileEmailAddress,
                    businessAccountId, businessProfileAccessToken);
                // After the completion of the Feed ADD operation above the added feed will not be
                // available for usage in a CustomerFeed until the FeedMappings are created.
                // We will wait with an exponential back-off policy until the feedmappings have
                // been created.
                WaitForBusinessProfileFeedToBeReady(client, customerId, businessProfileFeedResourceName);
                CreateCustomerFeed(client, customerId, businessProfileFeedResourceName);
            }
            catch (GoogleAdsException e)
            {
                Console.WriteLine("Failure:");
                Console.WriteLine($"Message: {e.Message}");
                Console.WriteLine($"Failure: {e.Failure}");
                Console.WriteLine($"Request ID: {e.RequestId}");
                throw;
            }
        }

        /// <summary>
        /// Creates the Business Profile feed.
        /// </summary>
        /// <param name="client">The Google Ads client.</param>
        /// <param name="customerId">The customer ID for which the call is made.</param>
        /// <param name="businessProfileEmailAddress">The Business Profile login email address.
        /// </param>
        /// <param name="businessAccountId">The Business Profile account ID.</param>
        /// <param name="businessProfileAccessToken">The OAuth2 access token for the Business
        /// Profile account.</param>
        /// <returns>ID of the newly created Business Profile feed.</returns>
        private static string CreateBusinessProfileFeed(GoogleAdsClient client, long customerId,
            string businessProfileEmailAddress, string businessAccountId,
            string businessProfileAccessToken)
        {
            // Optional: Delete all existing location extension feeds. This is an optional step,
            // and is required for this code example to run correctly more than once.
            // 1. Google Ads only allows one location extension feed per email address.
            // 2. A Google Ads account cannot have a location extension feed and an affiliate
            // location extension feed at the same time.
            DeleteLocationExtensionFeeds(client, customerId);

            // Get the FeedServiceClient.
            FeedServiceClient feedService = client.GetService(Services.V13.FeedService);

            // Creates a feed that will sync to the Business Profile account specified by
            // businessProfileEmailAddress. Do not add FeedAttributes to this object as Google Ads
            // will add them automatically because this will be a system generated feed.
            Feed businessProfileFeed = new Feed()
            {
                Name = "Business Profile feed #" + ExampleUtilities.GetRandomString(),

                PlacesLocationFeedData = new PlacesLocationFeedData()
                {
                    EmailAddress = businessProfileEmailAddress,
                    // If the EmailAddress is for a Business Profile manager instead of the
                    // Business Profile account owner, then set BusinessAccountId to the Google+
                    // Page ID of a location for which the manager has access. This information is
                    // available through the Business Profile API. See
                    // https://developers.google.com/my-business/reference/rest/v4/accounts.locations#locationkey
                    // for details.
                    BusinessAccountId = string.IsNullOrEmpty(businessAccountId) ?
                        null : businessAccountId,
                    // Used to filter Business Profile listings by labels. If entries exist in
                    // label_filters, only listings that have at least one of the labels set are
                    // candidates to be synchronized into FeedItems. If no entries exist in
                    // label_filters, then all listings are candidates for syncing.
                    LabelFilters = { "Stores in New York" },
                    // Sets the authentication info to be able to connect Google Ads to the
                    // Business Profile account.
                    OauthInfo = new OAuthInfo()
                    {
                        HttpMethod = "GET",
                        HttpRequestUrl = GOOGLE_ADS_SCOPE,
                        HttpAuthorizationHeader = $"Bearer {businessProfileAccessToken}"
                    },
                },
                // Since this feed's feed items will be managed by Google,
                // you must set its origin to GOOGLE.
                Origin = FeedOrigin.Google
            };

            FeedOperation operation = new FeedOperation()
            {
                Create = businessProfileFeed
            };

            // Adds the feed.
            MutateFeedsResponse response =
                feedService.MutateFeeds(customerId.ToString(), new[] { operation });

            // Displays the results.
            string businessProfileFeedResourceName = response.Results[0].ResourceName;
            Console.WriteLine($"Business Profile feed created with resource name: " +
                $"{businessProfileFeedResourceName}.");
            return businessProfileFeedResourceName;
        }

        /// <summary>
        /// Deletes the old location extension feeds.
        /// </summary>
        /// <param name="client">The Google Ads client.</param>
        /// <param name="customerId">The customer ID for which the call is made.</param>
        private static void DeleteLocationExtensionFeeds(GoogleAdsClient client, long customerId)
        {
            // To delete a location extension feed, you need to
            // 1. Delete the CustomerFeed so that the location extensions from the feed stop
            // serving.
            // 2. Delete the feed so that Google Ads will no longer sync from the Business Profile
            // account.
            CustomerFeed[] oldCustomerFeeds =
                GetLocationExtensionCustomerFeeds(client, customerId);
            if (oldCustomerFeeds.Length != 0)
            {
                DeleteCustomerFeeds(client, customerId, oldCustomerFeeds);
            }
            Feed[] feeds = GetLocationExtensionFeeds(client, customerId);

            if (feeds.Length != 0)
            {
                RemoveFeeds(client, customerId, feeds);
            }
        }

        /// <summary>
        /// Gets the location extension feeds.
        /// </summary>
        /// <param name="client">The Google Ads client.</param>
        /// <param name="customerId">The customer ID for which the call is made.</param>
        /// <returns>The list of location extension feeds.</returns>
        private static Feed[] GetLocationExtensionFeeds(GoogleAdsClient client, long customerId)
        {
            List<Feed> feeds = new List<Feed>();
            GoogleAdsServiceClient googleAdsService = client.GetService(
                Services.V13.GoogleAdsService);

            // Create the query.
            string query = $"SELECT feed.resource_name, feed.status, " +
                $"feed.places_location_feed_data.email_address, " +
                $"feed.affiliate_location_feed_data.chain_ids " +
                $" from feed where feed.status = ENABLED";

            PagedEnumerable<SearchGoogleAdsResponse, GoogleAdsRow> result =
                googleAdsService.Search(customerId.ToString(), query);

            foreach (GoogleAdsRow row in result)
            {
                // A location extension feed can be identified by checking whether the
                // PlacesLocationFeedData field is set (Location extensions feeds) or
                // AffiliateLocationFeedData field is set (Affiliate location extension feeds)
                Feed feed = row.Feed;
                if (feed.PlacesLocationFeedData != null || feed.AffiliateLocationFeedData != null)
                {
                    feeds.Add(feed);
                }
            }
            return feeds.ToArray();
        }

        /// <summary>
        /// Removes the feeds.
        /// </summary>
        /// <param name="client">The Google Ads client.</param>
        /// <param name="customerId">The customer ID for which the call is made.</param>
        /// <param name="feeds">The list of feeds to remove.</param>
        private static void RemoveFeeds(GoogleAdsClient client, long customerId, Feed[] feeds)
        {
            List<FeedOperation> operations = new List<FeedOperation>();
            foreach (Feed feed in feeds)
            {
                FeedOperation operation = new FeedOperation()
                {
                    Remove = feed.ResourceName,
                };
                operations.Add(operation);
            }
            FeedServiceClient feedService = client.GetService(
                Services.V13.FeedService);

            feedService.MutateFeeds(customerId.ToString(), operations.ToArray());
        }

        private static CustomerFeed[] GetLocationExtensionCustomerFeeds(GoogleAdsClient client,
            long customerId)
        {
            List<CustomerFeed> customerFeeds = new List<CustomerFeed>();
            GoogleAdsServiceClient googleAdsService = client.GetService(
                Services.V13.GoogleAdsService);

            // Create the query. A location extension customer feed can be identified by filtering
            // for placeholder_types=LOCATION (location extension feeds) or
            // placeholder_types =AFFILIATE_LOCATION (affiliate location extension feeds)
            string query = $"SELECT customer_feed.resource_name, customer_feed.feed, " +
                $"customer_feed.status, customer_feed.matching_function.function_string from " +
                $"customer_feed " +
                $"WHERE customer_feed.placeholder_types CONTAINS " +
                $"ANY(LOCATION, AFFILIATE_LOCATION) and customer_feed.status=ENABLED";

            PagedEnumerable<SearchGoogleAdsResponse, GoogleAdsRow> result =
                googleAdsService.Search(customerId.ToString(), query);

            foreach (GoogleAdsRow row in result)
            {
                customerFeeds.Add(row.CustomerFeed);
            }
            return customerFeeds.ToArray();
        }

        /// <summary>
        /// Deletes the customer feeds.
        /// </summary>
        /// <param name="client">The Google Ads client.</param>
        /// <param name="customerId">The customer ID for which the call is made.</param>
        /// <param name="customerFeeds">The customer feeds to delete.</param>
        private static void DeleteCustomerFeeds(GoogleAdsClient client, long customerId,
            CustomerFeed[] customerFeeds)
        {
            List<CustomerFeedOperation> operations = new List<CustomerFeedOperation>();
            foreach (CustomerFeed customerFeed in customerFeeds)
            {
                CustomerFeedOperation operation = new CustomerFeedOperation()
                {
                    Remove = customerFeed.ResourceName,
                };
                operations.Add(operation);
            }

            CustomerFeedServiceClient feedService = client.GetService(
                Services.V13.CustomerFeedService);

            feedService.MutateCustomerFeeds(customerId.ToString(), operations.ToArray());
        }

        /// <summary>
        /// Gets the Business Profile feed mapping.
        /// </summary>
        /// <param name="client">The Google Ads client.</param>
        /// <param name="customerId">The customer ID for which the call is made.</param>
        /// <param name="businessProfileFeedResourceName">The Business Profile feed resource name.
        /// </param>
        /// <returns>The newly created feed mapping.</returns>
        private static FeedMapping GetBusinessProfileFeedMapping(GoogleAdsClient client,
            long customerId, string businessProfileFeedResourceName)
        {
            // Get the GoogleAdsService.
            GoogleAdsServiceClient googleAdsService = client.GetService(
                Services.V13.GoogleAdsService);

            // Create the query.
            string query = $"SELECT feed_mapping.resource_name, feed_mapping.status FROM " +
                $"feed_mapping WHERE feed_mapping.feed = '{businessProfileFeedResourceName}' and " +
                $"feed_mapping.status = ENABLED and feed_mapping.placeholder_type = LOCATION" +
                $" LIMIT 1";

            // Issue a search request.
            PagedEnumerable<SearchGoogleAdsResponse, GoogleAdsRow> result =
                googleAdsService.Search(customerId.ToString(), query);

            // Display the results.
            GoogleAdsRow googleAdsRow = result.FirstOrDefault();
            return (googleAdsRow == null) ? null : googleAdsRow.FeedMapping;
        }

        /// <summary>
        /// Waits for the Business Profile feed to be ready.
        /// </summary>
        /// <param name="client">The Google Ads client.</param>
        /// <param name="customerId">The customer ID for which the call is made.</param>
        /// <param name="businessProfileFeedResourceName">The Business Profile feed resource name.
        /// </param>
        private static void WaitForBusinessProfileFeedToBeReady(GoogleAdsClient client,
            long customerId, string businessProfileFeedResourceName)
        {
            int numAttempts = 0;
            while (numAttempts < MAX_FEEDMAPPING_RETRIEVAL_ATTEMPTS)
            {
                // Once you create a feed, Google's servers will setup the feed by creating feed
                // attributes and feedmapping. Once the feedmapping is created, it is ready to be
                // used for creating customer feed.
                // This process is asynchronous, so we wait until the feed mapping is created,
                // peforming exponential backoff.
                FeedMapping feedMapping = GetBusinessProfileFeedMapping(client,
                    customerId, businessProfileFeedResourceName);

                if (feedMapping == null)
                {
                    numAttempts++;
                    int sleepSeconds = (int)(5 * Math.Pow(2, numAttempts));
                    Console.WriteLine($"Checked: #{numAttempts} time(s). Business Profile feed " +
                        $"is not ready yet. Waiting {sleepSeconds} seconds before trying again.");
                    Thread.Sleep(sleepSeconds * 1000);
                }
                else
                {
                    Console.WriteLine($"Business Profile Feed {businessProfileFeedResourceName} " +
                        $"is now ready.");
                    return;
                }
            }
            throw new RpcException(new Status(StatusCode.DeadlineExceeded,
                $"Business Profile Feed is not ready after {MAX_FEEDMAPPING_RETRIEVAL_ATTEMPTS} " +
                $"retries."));
        }

        /// <summary>
        /// Creates the customer feed.
        /// </summary>
        /// <param name="client">The client.</param>
        /// <param name="customerId">The customer identifier.</param>
        /// <param name="businessProfileFeedResourceName">The Business Profile feed resource name.
        /// </param>
        private static void CreateCustomerFeed(GoogleAdsClient client, long customerId,
            string businessProfileFeedResourceName)
        {
            // Get the CustomerFeedService.
            CustomerFeedServiceClient customerFeedService = client.GetService(
                Services.V13.CustomerFeedService);

            // Adds a CustomerFeed that associates the feed with this customer for
            // the LOCATION placeholder type.
            CustomerFeed customerFeed = new CustomerFeed()
            {
                Feed = businessProfileFeedResourceName,
                PlaceholderTypes = { PlaceholderType.Location },
                MatchingFunction = new MatchingFunction()
                {
                    LeftOperands =
                    {
                        new Operand()
                        {
                            ConstantOperand = new ConstantOperand()
                            {
                                BooleanValue = true
                            }
                        }
                    },
                    // Specify the function string as IDENTITY(true) to mark this feed as enabled.
                    FunctionString = "IDENTITY(true)",
                    Operator = MatchingFunctionOperator.Identity
                },
            };

            CustomerFeedOperation operation = new CustomerFeedOperation()
            {
                Create = customerFeed
            };

            MutateCustomerFeedsResponse customerFeedsResponse =
                customerFeedService.MutateCustomerFeeds(
                    customerId.ToString(), new[] { operation });

            // Displays the result.
            string addedCustomerFeed = customerFeedsResponse.Results[0].ResourceName;
            Console.WriteLine($"Customer feed created with resource name: {addedCustomerFeed}.");
            return;
        }
    }
}

      

PHP

<?php

/**
 * Copyright 2019 Google LLC
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     https://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

namespace Google\Ads\GoogleAds\Examples\Extensions;

require __DIR__ . '/../../vendor/autoload.php';

use GetOpt\GetOpt;
use Google\Ads\GoogleAds\Examples\Utils\ArgumentNames;
use Google\Ads\GoogleAds\Examples\Utils\ArgumentParser;
use Google\Ads\GoogleAds\Examples\Utils\Helper;
use Google\Ads\GoogleAds\Lib\OAuth2TokenBuilder;
use Google\Ads\GoogleAds\Lib\V13\GoogleAdsClient;
use Google\Ads\GoogleAds\Lib\V13\GoogleAdsClientBuilder;
use Google\Ads\GoogleAds\Lib\V13\GoogleAdsException;
use Google\Ads\GoogleAds\V13\Common\MatchingFunction;
use Google\Ads\GoogleAds\V13\Common\Operand;
use Google\Ads\GoogleAds\V13\Common\Operand\ConstantOperand;
use Google\Ads\GoogleAds\V13\Enums\FeedOriginEnum\FeedOrigin;
use Google\Ads\GoogleAds\V13\Enums\MatchingFunctionOperatorEnum\MatchingFunctionOperator;
use Google\Ads\GoogleAds\V13\Enums\PlaceholderTypeEnum\PlaceholderType;
use Google\Ads\GoogleAds\V13\Errors\GoogleAdsError;
use Google\Ads\GoogleAds\V13\Resources\CustomerFeed;
use Google\Ads\GoogleAds\V13\Resources\Feed;
use Google\Ads\GoogleAds\V13\Resources\Feed\PlacesLocationFeedData;
use Google\Ads\GoogleAds\V13\Resources\Feed\PlacesLocationFeedData\OAuthInfo;
use Google\Ads\GoogleAds\V13\Services\CustomerFeedOperation;
use Google\Ads\GoogleAds\V13\Services\FeedOperation;
use Google\ApiCore\ApiException;

/**
 * This example adds a feed that syncs feed items from a Business Profile account
 * and associates the feed with a customer.
 */
class AddBusinessProfileLocationExtensions
{
    private const CUSTOMER_ID = 'INSERT_CUSTOMER_ID_HERE';
    private const BUSINESS_PROFILE_EMAIL = 'INSERT_BUSINESS_PROFILE_EMAIL_HERE';
    private const BUSINESS_PROFILE_ACCESS_TOKEN = 'INSERT_BUSINESS_PROFILE_ACCESS_TOKEN_HERE';
    private const BUSINESS_ACCOUNT_IDENTIFIER = 'INSERT_BUSINESS_ACCOUNT_IDENTIFIER_HERE';

    // The required scope for setting the OAuth info.
    private const GOOGLE_ADS_SCOPE = 'https://www.googleapis.com/auth/adwords';
    // The maximum number of customer feed ADD operation attempts to make before throwing an
    // exception.
    private const MAX_CUSTOMER_FEED_ADD_ATTEMPTS = 10;
    private const POLL_FREQUENCY_SECONDS = 5;

    public static function main()
    {
        // Either pass the required parameters for this example on the command line, or insert them
        // into the constants above.
        $options = (new ArgumentParser())->parseCommandArguments([
            ArgumentNames::CUSTOMER_ID => GetOpt::REQUIRED_ARGUMENT,
            ArgumentNames::BUSINESS_PROFILE_EMAIL => GetOpt::REQUIRED_ARGUMENT,
            ArgumentNames::BUSINESS_PROFILE_ACCESS_TOKEN => GetOpt::REQUIRED_ARGUMENT,
            ArgumentNames::BUSINESS_ACCOUNT_IDENTIFIER => GetOpt::REQUIRED_ARGUMENT
        ]);

        // Generate a refreshable OAuth2 credential for authentication.
        $oAuth2Credential = (new OAuth2TokenBuilder())->fromFile()->build();

        // Construct a Google Ads client configured from a properties file and the
        // OAuth2 credentials above.
        $googleAdsClient = (new GoogleAdsClientBuilder())->fromFile()
            ->withOAuth2Credential($oAuth2Credential)
            ->build();

        try {
            self::runExample(
                $googleAdsClient,
                $options[ArgumentNames::CUSTOMER_ID] ?: self::CUSTOMER_ID,
                $options[ArgumentNames::BUSINESS_PROFILE_EMAIL]
                    ?: self::BUSINESS_PROFILE_EMAIL,
                $options[ArgumentNames::BUSINESS_PROFILE_ACCESS_TOKEN]
                    ?: self::BUSINESS_PROFILE_ACCESS_TOKEN,
                $options[ArgumentNames::BUSINESS_ACCOUNT_IDENTIFIER]
                    ?: self::BUSINESS_ACCOUNT_IDENTIFIER
            );
        } catch (GoogleAdsException $googleAdsException) {
            printf(
                "Request with ID '%s' has failed.%sGoogle Ads failure details:%s",
                $googleAdsException->getRequestId(),
                PHP_EOL,
                PHP_EOL
            );
            foreach ($googleAdsException->getGoogleAdsFailure()->getErrors() as $error) {
                /** @var GoogleAdsError $error */
                printf(
                    "\t%s: %s%s",
                    $error->getErrorCode()->getErrorCode(),
                    $error->getMessage(),
                    PHP_EOL
                );
            }
            exit(1);
        } catch (ApiException $apiException) {
            printf(
                "ApiException was thrown with message '%s'.%s",
                $apiException->getMessage(),
                PHP_EOL
            );
            exit(1);
        }
    }

    /**
     * Runs the example.
     *
     * @param GoogleAdsClient $googleAdsClient the Google Ads API client
     * @param int $customerId the client customer ID
     * @param string $businessProfileEmail the email address associated with the Business
     *     Profile account
     * @param string $businessProfileAccessToken the access token created using the 'AdWords' scope
     *     and the client ID and client secret of with the Cloud project associated with the
     *     Business Profile account
     * @param string $businessAccountIdentifier the account number of the Business Profile account
     */
    public static function runExample(
        GoogleAdsClient $googleAdsClient,
        int $customerId,
        string $businessProfileEmail,
        string $businessProfileAccessToken,
        string $businessAccountIdentifier
    ) {
        $businessProfileFeedResourceName = self::createFeed(
            $googleAdsClient,
            $customerId,
            $businessProfileEmail,
            $businessProfileAccessToken,
            $businessAccountIdentifier
        );
        self::createCustomerFeed($googleAdsClient, $customerId, $businessProfileFeedResourceName);

        // OPTIONAL: Create a campaign feed to specify which feed items to use at the campaign
        // level.

        // OPTIONAL: Create an ad group feed for even more fine grained control over which feed
        // items are used at the ad group level.
    }

    /**
     * Creates a location feed that will sync to the Business Profile account specified by
     * `$businessProfileEmailAddress`. Do not add feed attributes to this object as Google Ads will
     * add them automatically because this will be a system generated feed.
     *
     * @param GoogleAdsClient $googleAdsClient the Google Ads API client
     * @param int $customerId the client customer ID
     * @param string $businessProfileEmail the email address associated with the Business
     *     Profile account
     * @param string $businessProfileAccessToken the access token created using the 'AdWords' scope
     *     and the client ID and client secret of with the Cloud project associated with the
     *     Business Profile account
     * @param string $businessAccountIdentifier the account number of the Business Profile account
     * @return string the feed's resource name
     */
    private static function createFeed(
        GoogleAdsClient $googleAdsClient,
        int $customerId,
        string $businessProfileEmail,
        string $businessProfileAccessToken,
        string $businessAccountIdentifier
    ) {

        $businessProfileFeed = new Feed([
            'name' => 'Business Profile feed #' . Helper::getPrintableDatetime(),
            'origin' => FeedOrigin::GOOGLE,
            'places_location_feed_data' => new PlacesLocationFeedData([
                'email_address' => $businessProfileEmail,
                'business_account_id' => $businessAccountIdentifier,
                // Used to filter Business Profile listings by labels. If entries exist in
                // label_filters, only listings that have at least one of the labels set are
                // candidates to be synchronized into FeedItems. If no entries exist in
                // label_filters, then all listings are candidates for syncing.
                'label_filters' => ['Stores in New York'],
                // Sets the authentication info to be able to connect Google Ads to the Business
                // Profile account.
                'oauth_info' => new OAuthInfo([
                    'http_method' => 'GET',
                    'http_request_url' => self::GOOGLE_ADS_SCOPE,
                    'http_authorization_header' => 'Bearer ' . $businessProfileAccessToken
                ])

            ])
        ]);
        // Creates a feed operation.
        $feedOperation = new FeedOperation();
        $feedOperation->setCreate($businessProfileFeed);

        // Issues a mutate request to add the feed and print its information.
        // Since it is a system generated feed, Google Ads will automatically:
        // 1. Set up the feed attributes on the feed.
        // 2. Set up a feed mapping that associates the feed attributes of the feed with the
        //    placeholder fields of the LOCATION placeholder type.
        $feedServiceClient = $googleAdsClient->getFeedServiceClient();
        $response = $feedServiceClient->mutateFeeds(
            $customerId,
            [$feedOperation]
        );
        $businessProfileFeedResourceName = $response->getResults()[0]->getResourceName();
        printf(
            "Business Profile feed created with resource name: '%s'.%s",
            $businessProfileFeedResourceName,
            PHP_EOL
        );

        return $businessProfileFeedResourceName;
    }

    /**
     * Creates a customer feed to attach the previously created Business Profile feed to the
     * specified customer ID.
     *
     * @param GoogleAdsClient $googleAdsClient the Google Ads API client
     * @param int $customerId the client customer ID
     * @param string $businessProfileFeedResourceName the feed's resource name to be used to create
     *     a customer feed
     */
    private static function createCustomerFeed(
        GoogleAdsClient $googleAdsClient,
        int $customerId,
        string $businessProfileFeedResourceName
    ) {
        // Creates a customer feed that associates the feed with this customer for the LOCATION
        // placeholder type.
        $customerFeed = new CustomerFeed([
            'feed' => $businessProfileFeedResourceName,
            'placeholder_types' => [PlaceholderType::LOCATION],
            // Creates a matching function that will always evaluate to true.
            'matching_function' => new MatchingFunction([
                'left_operands' => [new Operand([
                    'constant_operand' => new ConstantOperand(['boolean_value' => true])
                ])],
                'function_string' => 'IDENTITY(true)',
                'operator' => MatchingFunctionOperator::IDENTITY
            ])
        ]);
        // Creates a customer feed operation.
        $customerFeedOperation = new CustomerFeedOperation();
        $customerFeedOperation->setCreate($customerFeed);

        // After the completion of the feed ADD operation above the added feed will not be available
        // for usage in a customer feed until the sync between the Google Ads and Business Profile
        // accounts completes. The loop below will retry adding the customer feed up to ten times
        // with an exponential back-off policy.
        $numberOfAttempts = 0;
        $addedCustomerFeed = null;
        $customerFeedServiceClient = $googleAdsClient->getCustomerFeedServiceClient();
        do {
            $numberOfAttempts++;
            try {
                // Issues a mutate request to add a customer feed and print its information if the
                // request succeeded.
                $addedCustomerFeed = $customerFeedServiceClient->mutateCustomerFeeds(
                    $customerId,
                    [$customerFeedOperation]
                );
                printf(
                    "Customer feed created with resource name: '%s'.%s",
                    $addedCustomerFeed->getResults()[0]->getResourceName(),
                    PHP_EOL
                );
            } catch (GoogleAdsException $googleAdsException) {
                // Waits using exponential backoff policy.
                $sleepSeconds = self::POLL_FREQUENCY_SECONDS * pow(2, $numberOfAttempts);
                // Exits the loop early if $sleepSeconds grows too large in the event that
                // MAX_CUSTOMER_FEED_ADD_ATTEMPTS is set too high.
                if (
                    $sleepSeconds > self::POLL_FREQUENCY_SECONDS
                    * pow(2, self::MAX_CUSTOMER_FEED_ADD_ATTEMPTS)
                ) {
                    break;
                }
                printf(
                    "Attempt #%d to add the customer feed was not successful."
                    . " Waiting %d seconds before trying again.%s",
                    $numberOfAttempts,
                    $sleepSeconds,
                    PHP_EOL
                );
                sleep($sleepSeconds);
            }
        } while (
            $numberOfAttempts < self::MAX_CUSTOMER_FEED_ADD_ATTEMPTS
            && is_null($addedCustomerFeed)
        );

        if (is_null($addedCustomerFeed)) {
            throw new \RuntimeException(
                'Could not create the customer feed after ' . self::MAX_CUSTOMER_FEED_ADD_ATTEMPTS
                . ' attempts. Please retry the customer feed ADD operation later.'
            );
        }
    }
}

AddBusinessProfileLocationExtensions::main();

      

Python

#!/usr/bin/env python
# Copyright 2020 Google LLC
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     https://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
"""Adds a feed that syncs feed items from a Business Profile account.

The feed will also be associated with a customer.
"""

import argparse
import sys
import time
from uuid import uuid4

from google.ads.googleads.client import GoogleAdsClient
from google.ads.googleads.errors import GoogleAdsException

MAX_CUSTOMER_FEED_ADD_ATTEMPTS = 9
DEFAULT_OAUTH2_SCOPE = "https://www.googleapis.com/auth/adwords"


def main(
    client,
    customer_id,
    business_profile_email,
    business_account_id,
    business_profile_access_token,
):
    """Adds a feed that syncs feed items from a Business Profile account.

    The feed will also be associated with a customer.

    Args:
        client: An initialized GoogleAdsClient instance.
        customer_id: The Google Ads customer ID.
        business_profile_email: The email address associated with the Business
          Profile account.
        business_account_id: The account ID of the managed business.
        business_profile_access_token: The access token created using the
          'AdWords' scope and the client ID and client secret of with the Cloud
          project associated with the Business Profile account.
    """
    # Get the FeedService and CustomerFeedService clients.
    feed_service = client.get_service("FeedService")
    customer_feed_service = client.get_service("CustomerFeedService")

    # Create a feed operation and configure the new feed.
    # The feed will sync to the Business Profile account specified by
    # business_profile_email. Do not add FeedAttributes to this object as Google Ads
    # will add them automatically because this will be a system generated feed.
    # See here for more details:
    # https://developers.google.com/google-ads/api/docs/location-extensions/google-ads-location-extensions
    feed_operation = client.get_type("FeedOperation")
    business_profile_feed = feed_operation.create
    business_profile_feed.name = f"Business Profile Feed #{uuid4()}"
    # Configure the location feed populated from Business Profile Locations.
    business_profile_feed.places_location_feed_data.email_address = (
        business_profile_email
    )

    if business_account_id is not None:
        business_profile_feed.places_location_feed_data.business_account_id = (
            business_account_id
        )

    # Used to filter Business Profile listings by labels. If entries exist in
    # label_filters, only listings that have at least one of the labels set are
    # candidates to be synchronized into FeedItems. If no entries exist in
    # label_filters, then all listings are candidates for syncing.
    business_profile_feed.places_location_feed_data.label_filters.append(
        "Stores in New York"
    )

    # Set the authentication info to be able to connect Google Ads to the
    # Business Profile account.
    business_profile_feed.places_location_feed_data.oauth_info.http_method = (
        "GET"
    )
    business_profile_feed.places_location_feed_data.oauth_info.http_request_url = (
        DEFAULT_OAUTH2_SCOPE
    )
    business_profile_feed.places_location_feed_data.oauth_info.http_authorization_header = (
        f"Bearer {business_profile_access_token}"
    )
    # Since this feed's feed items will be managed by Google, you must set its
    # origin to GOOGLE.
    business_profile_feed.origin = client.enums.FeedOriginEnum.GOOGLE

    # Optional: Delete all existing location extension feeds. This is an
    # optional step, and is required for this code example to run correctly
    # more than once; Google Ads only allows one location extension feed
    # per email address, and a Google Ads account cannot have a location
    # extension feed and an affiliate location extension feed at the same
    # time.
    delete_location_extension_feeds(client, customer_id)

    # Add the feed. Since it is a system generated feed, Google Ads will
    # automatically:
    # 1. Set up the FeedAttributes on the feed.
    # 2. Set up a FeedMapping that associates the FeedAttributes of the feed
    #   with the placeholder fields of the LOCATION placeholder type.
    feed_response = feed_service.mutate_feeds(
        customer_id=customer_id, operations=[feed_operation]
    )
    feed_resource_name = feed_response.results[0].resource_name
    print(
        "Business Profile feed created with resource name "
        f"'{feed_resource_name}'."
    )

    # After the completion of the Feed ADD operation above the added feed
    # will not be available for usage in a CustomerFeed until the sync
    # between the Google Ads and Business Profile accounts completes.
    # This process is asynchronous, so we wait until the feed mapping is
    # created, performing exponential backoff.
    customer_feed_resource_name = None
    number_of_attempts = 0

    while number_of_attempts < MAX_CUSTOMER_FEED_ADD_ATTEMPTS:
        feed_mapping = get_business_profile_feed_mapping(
            client, customer_id, feed_resource_name
        )

        if feed_mapping is None:
            number_of_attempts += 1
            sleep_seconds = 5 * (2 ** number_of_attempts)

            print(
                f"Attempt #{number_of_attempts} was not successful. "
                f"Waiting {sleep_seconds}s before trying again."
            )

            time.sleep(sleep_seconds)
        else:
            customer_feed_resource_name = feed_mapping.resource_name
            print(f"Business Profile feed {feed_resource_name} is now ready.")
            break

    if customer_feed_resource_name is None:
        print(
            "Could not create the CustomerFeed after "
            f"{MAX_CUSTOMER_FEED_ADD_ATTEMPTS} attempts. Please retry "
            "the CustomerFeed ADD operation later."
        )
        sys.exit(1)
    else:
        # Create a CustomerFeed operation and configure the CustomerFeed to
        # associate the feed with this customer for the LOCATION placeholder
        # type.

        # OPTIONAL: Create a CampaignFeed to specify which FeedItems to use at
        # the Campaign level.

        # OPTIONAL: Create an AdGroupFeed for even more fine grained control
        # over which feed items are used at the AdGroup level.
        customer_feed_operation = client.get_type("CustomerFeedOperation")
        customer_feed = customer_feed_operation.create
        customer_feed.feed = feed_resource_name
        customer_feed.placeholder_types.append(
            client.enums.PlaceholderTypeEnum.LOCATION
        )
        # The function string "IDENTITY(true)" will enable this feed.
        true_operand = client.get_type("Operand")
        true_operand.constant_operand.boolean_value = True
        customer_feed.matching_function.left_operands.append(true_operand)
        customer_feed.matching_function.function_string = "IDENTITY(true)"
        customer_feed.matching_function.operator = (
            client.enums.MatchingFunctionOperatorEnum.IDENTITY
        )

        customer_feed_response = customer_feed_service.mutate_customer_feeds(
            customer_id=customer_id, operations=[customer_feed_operation]
        )
        print(
            "Customer feed created with resource name "
            f"'{customer_feed_response.results[0].resource_name}'."
        )


def delete_location_extension_feeds(client, customer_id):
    """Deletes the existing location extension feeds.

    Args:
        client: An initialized Google Ads API client.
        customer_id: The Google Ads customer ID.
    """
    # To delete a location extension feed, you need to:
    # 1. Delete the CustomerFeed so that the location extensions from the feed
    # stop serving.
    # 2. Delete the feed so that Google Ads will no longer sync from the
    # Business Profile account.
    old_customer_feeds = get_location_extension_customer_feeds(
        client, customer_id
    )
    if old_customer_feeds:
        delete_customer_feeds(client, customer_id, old_customer_feeds)

    old_feeds = get_location_extension_feeds(client, customer_id)
    if old_feeds:
        delete_feeds(client, customer_id, old_feeds)


def get_location_extension_customer_feeds(client, customer_id):
    """Gets the existing location extension customer feeds.

    Args:
        client: An initialized Google Ads API client.
        customer_id: The Google Ads customer ID.

    Returns:
        A list of location extension feeds.
    """
    googleads_service = client.get_service("GoogleAdsService")

    # Create the query. A location extension customer feed can be identified by
    # filtering for placeholder_types=LOCATION (location extension feeds) or
    # placeholder_types=AFFILIATE_LOCATION (affiliate location extension feeds).
    query = """
        SELECT
          customer_feed.resource_name,
          customer_feed.feed,
          customer_feed.status,
          customer_feed.matching_function.function_string
        FROM customer_feed
        WHERE
          customer_feed.placeholder_types CONTAINS ANY(LOCATION, AFFILIATE_LOCATION)
          AND customer_feed.status = ENABLED"""

    result = googleads_service.search(customer_id=customer_id, query=query)

    return [row.customer_feed for row in result]


def get_location_extension_feeds(client, customer_id):
    """Gets the existing location extension feeds.

    Args:
        client: An initialized Google Ads API client.
        customer_id: The Google Ads customer ID.

    Returns:
        A list of location extension feeds.
    """
    googleads_service = client.get_service("GoogleAdsService")

    # Create the query.
    query = """
        SELECT
          feed.resource_name,
          feed.status,
          feed.places_location_feed_data.email_address,
          feed.affiliate_location_feed_data.chain_ids
        FROM feed
        WHERE feed.status = ENABLED"""

    result = googleads_service.search(customer_id=customer_id, query=query)

    # A location extension feed can be identified by checking whether the
    # places_location_feed_data field is set or the
    # affiliate_location_feed_data field is set.
    return [
        row.feed
        for row in result
        if row.feed.places_location_feed_data
        or row.feed.affiliate_location_feed_data
    ]


def delete_customer_feeds(client, customer_id, old_customer_feeds):
    """Removes the customer feeds.

    Args:
        client: An initialized Google Ads API client.
        customer_id: The Google Ads customer ID.
        old_customer_feeds: The list of customer feeds to delete.
    """
    operations = []
    customer_feed_service = client.get_service("CustomerFeedService")

    for customer_feed in old_customer_feeds:
        operation = client.get_type("CustomerFeedOperation")
        operation.remove = customer_feed.resource_name
        operations.append(operation)

    customer_feed_service.mutate_customer_feeds(
        customer_id=customer_id, operations=operations
    )


def delete_feeds(client, customer_id, old_feeds):
    """Removes the specified feeds.

    Args:
        client: An initialized Google Ads API client.
        customer_id: The Google Ads customer ID.
        old_feeds: The list of feeds to delete.
    """
    operations = []
    feed_service = client.get_service("FeedService")

    for feed in old_feeds:
        operation = client.get_type("FeedOperation")
        operation.remove = feed.resource_name
        operations.append(operation)

    feed_service.mutate_feeds(customer_id=customer_id, operations=operations)


def get_business_profile_feed_mapping(client, customer_id, feed_resource_name):
    """Gets a Business Profile Feed mapping.

    Args:
        client: An initialized Google Ads client.
        customer_id: The customer ID for which the call is made.
        feed_resource_name: The string Business Profile feed resource name.

    Returns:
        The requested FeedMapping, or None if it is not available.
    """
    googleads_service = client.get_service("GoogleAdsService")

    query = f"""
        SELECT feed_mapping.resource_name, feed_mapping.status
        FROM feed_mapping
        WHERE
          feed_mapping.feed = '{feed_resource_name}'
          AND feed_mapping.status = ENABLED
          AND feed_mapping.placeholder_type = LOCATION
        LIMIT 1"""

    result = googleads_service.search(customer_id=customer_id, query=query)

    try:
        return next(iter(result)).feed_mapping
    except StopIteration:
        return None


if __name__ == "__main__":
    # GoogleAdsClient will read the google-ads.yaml configuration file in the
    # home directory if none is specified.
    googleads_client = GoogleAdsClient.load_from_storage(version="v13")

    parser = argparse.ArgumentParser(
        description="Adds a feed that syncs feed items from a Business Profile "
        "account."
    )
    # The following argument(s) should be provided to run the example.
    parser.add_argument(
        "-c",
        "--customer_id",
        type=str,
        required=True,
        help="The Google Ads customer ID.",
    )
    parser.add_argument(
        "-e",
        "--business_profile_email",
        type=str,
        required=True,
        help="The email address associated with the Business Profile account.",
    )
    parser.add_argument(
        "-b",
        "--business_account_id",
        type=str,
        required=False,
        help="The account ID of the managed business.\n"
        "If the email_address is for a Business Profile manager instead of the "
        "Business Profile account owner, then set business_account_id to the"
        "Google+ Page ID of a location for which the manager has access. This "
        "information is available through the Business Profile API. See "
        "https://developers.google.com/my-business/reference/rest/v4/accounts.locations#locationkey"
        "for details.",
    )
    parser.add_argument(
        "-t",
        "--business_profile_access_token",
        type=str,
        required=False,
        default=googleads_client.credentials.token,
        help="If the business_profile_email above is the same user you used to "
        "generate your Google Ads API refresh token, do not pass a value to "
        "this argument.\nOtherwise, to obtain an access token for your "
        "Business Profile account, run the "
        "authenticate_in_standalone_application code example while logged in "
        "as the same user as business_profile_email. Pass the "
        "Access Token value to this argument.",
    )

    args = parser.parse_args()

    try:
        main(
            googleads_client,
            args.customer_id,
            args.business_profile_email,
            args.business_account_id,
            args.business_profile_access_token,
        )
    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)

      

Ruby

#!/usr/bin/env ruby
# Encoding: utf-8
#
# Copyright 2020 Google LLC
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     https://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
# This example adds a feed that syncs feed items from a Business Profile
# account and associates the feed with a customer.

require 'optparse'
require 'google/ads/google_ads'
require 'date'

def add_business_profile_location_extensions(
  customer_id,
  business_profile_email_address,
  business_profile_access_token,
  business_account_identifier)
  # GoogleAdsClient will read a config file from
  # ENV['HOME']/google_ads_config.rb when called without parameters
  client = Google::Ads::GoogleAds::GoogleAdsClient.new

  business_profile_feed_resource_name = create_feed(
    client,
    customer_id,
    business_profile_email_address,
    business_profile_access_token,
    business_account_identifier,
  )

  create_customer_feed(client, customer_id, business_profile_feed_resource_name)
end

def create_feed(
  client,
  customer_id,
  business_profile_email_address,
  business_profile_access_token,
  business_account_identifier)
  # Creates a feed operation.
  operation = client.operation.create_resource.feed do |feed|
    feed.name = "Business Profile feed #{(Time.new.to_f * 1000).to_i}"
    feed.origin = :GOOGLE
    feed.places_location_feed_data = client.resource.places_location_feed_data do |data|
      data.email_address = business_profile_email_address
      data.business_account_id = business_account_identifier
      data.label_filters << "Stores in New York"
      data.oauth_info = client.resource.o_auth_info do |oauth|
        oauth.http_method = "GET"
        oauth.http_request_url = "https://www.googleapis.com/auth/adwords"
        oauth.http_authorization_header = "Bearer #{business_profile_access_token}"
      end
    end
  end

  # Issues a mutate request to add the feed and print its information.
  # Since it is a system generated feed, Google Ads will automatically:
  # 1. Set up the feed attributes on the feed.
  # 2. Set up a feed mapping that associates the feed attributes of the feed with the
  #    placeholder fields of the LOCATION placeholder type.
  response = client.service.feed.mutate_feeds(
    customer_id: customer_id,
    operations: [operation],
  )

  # Prints out the Business Profile feed resource name.
  business_profile_feed_resource_name = response.results.first.resource_name
  puts "Business Profile feed created with resource name: #{business_profile_feed_resource_name}"

  business_profile_feed_resource_name
end

def create_customer_feed(
  client,
  customer_id,
  business_profile_feed_resource_name)
  # Creates a customer feed operation.
  operation = client.operation.create_resource.customer_feed do |cf|
    cf.feed = business_profile_feed_resource_name
    cf.placeholder_types << :LOCATION
    cf.matching_function = client.resource.matching_function do |m|
      m.left_operands << client.resource.operand do |op|
        op.constant_operand = client.resource.constant_operand do |co|
          co.boolean_value = true
        end
      end
      m.function_string = "IDENTITY(true)"
      m.operator = :IDENTITY
    end
  end

  # After the completion of the feed ADD operation above the added feed will
  # not be available for usage in a customer feed until the sync between the
  # Google Ads and Business Profile accounts completes. The loop below will
  # retry adding the customer feed up to ten times with an exponential back-off
  # policy.
  number_of_attempts = 0
  added_customer_feed = nil
  customer_feed_service_client = client.service.customer_feed

  loop do
    number_of_attempts += 1
    begin
      # Issues a mutate request to add a customer feed and print its information
      # if the request succeeded.
      response = customer_feed_service_client.mutate_customer_feeds(
        customer_id: customer_id,
        operations: [operation]
      )
      puts "Customer feed created with resource name: " \
        "#{response.results.first.resource_name}"
    rescue Google::Ads::GoogleAds::Errors::GoogleAdsError => e
      # Waits using exponential backoff policy
      sleep_seconds = POLL_FREQUENCY_SECONDS * (2 ** number_of_attempts)
      puts "Attempt #{number_of_attempts} to add the customer feed was " \
        "not successful. Waiting #{sleep_seconds} seconds before trying again."
      sleep sleep_seconds
    end
    break if number_of_attempts >= MAX_CUSTOMER_FEED_ADD_ATTEMPTS || added_customer_feed
  end

  if added_customer_feed.nil?
    raise "Could not create the customer feed after #{MAX_CUSTOMER_FEED_ADD_ATTEMPTS} " \
      "attempts. Please retry the customer feed ADD operation later."
  end
end

if __FILE__ == $0
  # The maximum number of customer feed ADD operation attempts to make before
  # throwing an exception.
  MAX_CUSTOMER_FEED_ADD_ATTEMPTS = 10
  POLL_FREQUENCY_SECONDS = 5

  options = {}
  # The following parameter(s) should be provided to run the example. You can
  # either specify these by changing the INSERT_XXX_ID_HERE values below, or on
  # the command line.
  #
  # Parameters passed on the command line will override any parameters set in
  # code.
  #
  # Running the example with -h will print the command line usage.
  options[:customer_id] = 'INSERT_CUSTOMER_ID_HERE'
  options[:campaign_id] = 'INSERT_CAMPAIGN_ID_HERE'

  OptionParser.new do |opts|
    opts.banner = sprintf('Usage: %s [options]', File.basename(__FILE__))

    opts.separator ''
    opts.separator 'Options:'

    opts.on('-C', '--customer-id CUSTOMER-ID', String, 'Customer ID') do |v|
      options[:customer_id] = v
    end

    opts.on('-E', '--business-profile-email-address BUSINESS-PROFILE-EMAIL-ADDRESS',
            String, 'Business Profile Email Address') do |v|
      options[:business_profile_email_address] = v
    end

    opts.on('-T', '--business-profile-access-token BUSINESS-PROFILE-ACCESS-TOKEN', String,
            'Business Profile Access Token') do |v|
      options[:business_profile_access_token] = v
    end

    opts.on('-B', '--business-account-identifier BUSINESS-ACCOUNT-IDENTIFIER', String, 'Business Account Identifier') do |v|
      options[:business_account_identifier] = v
    end

    opts.separator ''
    opts.separator 'Help:'

    opts.on_tail('-h', '--help', 'Show this message') do
      puts opts
      exit
    end
  end.parse!

  begin
    add_business_profile_location_extensions(
      options.fetch(:customer_id).tr("-", ""),
      options.fetch(:business_profile_email_address),
      options.fetch(:business_profile_access_token),
      options.fetch(:business_account_identifier),
    )
  rescue Google::Ads::GoogleAds::Errors::GoogleAdsError => e
    e.failure.errors.each do |error|
      STDERR.printf("Error with message: %s\n", error.message)
      if error.location
        error.location.field_path_elements.each do |field_path_element|
          STDERR.printf("\tOn field: %s\n", field_path_element.field_name)
        end
      end
      error.error_code.to_h.each do |k, v|
        next if v == :UNSPECIFIED
        STDERR.printf("\tType: %s\n\tCode: %s\n", k, v)
      end
    end
    raise
  end
end

      

Perl

#!/usr/bin/perl -w
#
# Copyright 2019, Google LLC
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
# This example adds a feed that syncs feed items from a Business Profile account
# and associates the feed with a customer.

use strict;
use warnings;
use utf8;

use FindBin qw($Bin);
use lib "$Bin/../../lib";
use Google::Ads::GoogleAds::Constants;
use Google::Ads::GoogleAds::Client;
use Google::Ads::GoogleAds::Utils::GoogleAdsHelper;
use Google::Ads::GoogleAds::V13::Resources::Feed;
use Google::Ads::GoogleAds::V13::Resources::PlacesLocationFeedData;
use Google::Ads::GoogleAds::V13::Resources::OAuthInfo;
use Google::Ads::GoogleAds::V13::Resources::CustomerFeed;
use Google::Ads::GoogleAds::V13::Common::MatchingFunction;
use Google::Ads::GoogleAds::V13::Common::Operand;
use Google::Ads::GoogleAds::V13::Common::ConstantOperand;
use Google::Ads::GoogleAds::V13::Enums::FeedOriginEnum      qw(GOOGLE);
use Google::Ads::GoogleAds::V13::Enums::PlaceholderTypeEnum qw(LOCATION);
use Google::Ads::GoogleAds::V13::Enums::MatchingFunctionOperatorEnum
  qw(IDENTITY);
use Google::Ads::GoogleAds::V13::Services::FeedService::FeedOperation;
use
  Google::Ads::GoogleAds::V13::Services::CustomerFeedService::CustomerFeedOperation;

use Getopt::Long qw(:config auto_help);
use Pod::Usage;
use Cwd          qw(abs_path);
use Data::Uniqid qw(uniqid);
use Time::HiRes  qw(sleep);

use constant MAX_CUSTOMER_FEED_ADD_ATTEMPTS => 10;

# The following parameter(s) should be provided to run the example. You can
# either specify these by changing the INSERT_XXX_ID_HERE values below, or on
# the command line.
#
# Parameters passed on the command line will override any parameters set in
# code.
#
# Running the example with -h will print the command line usage.
my $customer_id                   = "INSERT_CUSTOMER_ID_HERE";
my $business_profile_email        = "INSERT_BUSINESS_PROFILE_EMAIL_HERE";
my $business_profile_account_id   = "INSERT_BUSINESS_PROFILE_ACCOUNT_ID_HERE";
my $business_profile_access_token = "INSERT_BUSINESS_PROFILE_ACCESS_TOKEN_HERE";

sub add_business_profile_location_extensions {
  my ($api_client, $customer_id, $business_profile_email,
    $business_profile_account_id, $business_profile_access_token)
    = @_;

  # Create a feed that will sync to the Business Profile account specified by
  # $business_profile_email. Do not add FeedAttributes to this object as Google Ads
  # will add them automatically because this will be a system generated feed.
  my $business_profile_feed = Google::Ads::GoogleAds::V13::Resources::Feed->new(
    {
      name => "Business Profile feed #" . uniqid(),
      # Configure the location feed populated from Business Profile Locations.
      placesLocationFeedData =>
        Google::Ads::GoogleAds::V13::Resources::PlacesLocationFeedData->new({
          emailAddress      => $business_profile_email,
          businessAccountId => $business_profile_account_id,
          # Used to filter Business Profile listings by labels. If entries exist in
          # label_filters, only listings that have at least one of the labels set are
          # candidates to be synchronized into FeedItems. If no entries exist in
          # label_filters, then all listings are candidates for syncing.
          labelFilters => ["Stores in New York"],
          # Set the authentication info to be able to connect Google Ads to the
          # Business Profile account.
          oauthInfo => Google::Ads::GoogleAds::V13::Resources::OAuthInfo->new({
              httpMethod     => "GET",
              httpRequestUrl =>
                Google::Ads::GoogleAds::Constants::DEFAULT_OAUTH2_SCOPE,
              httpAuthorizationHeader => "Bearer " .
                $business_profile_access_token
            })}
        ),
      # Since this feed's feed items will be managed by Google, you must set its
      # origin to GOOGLE.
      origin => GOOGLE
    });

  # Create a feed operation.
  my $feed_operation =
    Google::Ads::GoogleAds::V13::Services::FeedService::FeedOperation->new(
    {create => $business_profile_feed});

  # Add the feed. Since it is a system generated feed, Google Ads will automatically:
  # 1. Set up the FeedAttributes on the feed.
  # 2. Set up a FeedMapping that associates the FeedAttributes of the feed with the
  #    placeholder fields of the LOCATION placeholder type.
  my $feeds_response = $api_client->FeedService()->mutate({
      customerId => $customer_id,
      operations => [$feed_operation]});

  my $feed_resource_name = $feeds_response->{results}[0]{resourceName};

  printf "Business Profile feed created with resource name: '%s'.\n",
    $feed_resource_name;

  # Add a CustomerFeed that associates the feed with this customer for the LOCATION
  # placeholder type.
  my $customer_feed = Google::Ads::GoogleAds::V13::Resources::CustomerFeed->new(
    {
      feed             => $feed_resource_name,
      placeholderTypes => LOCATION,
      # Create a matching function that will always evaluate to true.
      matchingFunction =>
        Google::Ads::GoogleAds::V13::Common::MatchingFunction->new({
          leftOperands => [
            Google::Ads::GoogleAds::V13::Common::Operand->new({
                constantOperand =>
                  Google::Ads::GoogleAds::V13::Common::ConstantOperand->new({
                    booleanValue => "true"
                  })})
          ],
          functionString => "IDENTITY(true)",
          operator       => IDENTITY
        })});

  # Create a customer feed operation.
  my $customer_feed_operation =
    Google::Ads::GoogleAds::V13::Services::CustomerFeedService::CustomerFeedOperation
    ->new({create => $customer_feed});

  # After the completion of the Feed ADD operation above the added feed will not be available
  # for usage in a CustomerFeed until the sync between the Google Ads and Business Profile
  # accounts completes. The loop below will retry adding the CustomerFeed up to ten times with an
  # exponential back-off policy.
  my $customer_feed_service       = $api_client->CustomerFeedService();
  my $customer_feed_resource_name = undef;
  my $number_of_attempts          = 0;

  while ($number_of_attempts < MAX_CUSTOMER_FEED_ADD_ATTEMPTS) {
    $number_of_attempts++;

    my $customer_feeds_response = eval {
      $customer_feed_service->mutate({
        customerId => $customer_id,
        operations => [$customer_feed_operation],
      });
    };

    if ($@) {
      # Wait using exponential backoff policy.
      my $sleep_seconds = 5 * (2**$number_of_attempts);

      # Exit the loop early if $sleep_seconds grows too large in the event that
      # MAX_CUSTOMER_FEED_ADD_ATTEMPTS is set too high.
      if ($sleep_seconds > 5 * (2**10)) {
        last;
      }

      printf "Attempt #%d to add the CustomerFeed was not successful. " .
        "Waiting %d seconds before trying again.\n",
        $number_of_attempts, $sleep_seconds;

      sleep($sleep_seconds);
    } else {
      $customer_feed_resource_name =
        $customer_feeds_response->{results}[0]{resourceName};

      printf "Customer feed created with resource name: '%s'.\n",
        $customer_feed_resource_name;

      last;
    }
  }

  printf "Could not create the CustomerFeed after %d attempts. " .
    "Please retry the CustomerFeed ADD operation later.",
    MAX_CUSTOMER_FEED_ADD_ATTEMPTS
    if not $customer_feed_resource_name;

  # OPTIONAL: Create a CampaignFeed to specify which FeedItems to use at the Campaign level.

  # OPTIONAL: Create an AdGroupFeed for even more fine grained control over which feed items
  # are used at the AdGroup level.

  return 1;
}

# Don't run the example if the file is being included.
if (abs_path($0) ne abs_path(__FILE__)) {
  return 1;
}

# Get Google Ads Client, credentials will be read from ~/googleads.properties.
my $api_client = Google::Ads::GoogleAds::Client->new();

# By default examples are set to die on any server returned fault.
$api_client->set_die_on_faults(1);

# Parameters passed on the command line will override any parameters set in code.
GetOptions(
  "customer_id=s"                   => \$customer_id,
  "business_profile_email=s"        => \$business_profile_email,
  "business_profile_account_id=s"   => \$business_profile_account_id,
  "business_profile_access_token=s" => \$business_profile_access_token,
);

# Print the help message if the parameters are not initialized in the code nor
# in the command line.
pod2usage(2)
  if not check_params($customer_id, $business_profile_email,
  $business_profile_account_id, $business_profile_access_token);

# Call the example.
add_business_profile_location_extensions($api_client, $customer_id =~ s/-//gr,
  $business_profile_email,
  $business_profile_account_id, $business_profile_access_token);

=pod

=head1 NAME

add_business_profile_location_extensions

=head1 DESCRIPTION

This example adds a feed that syncs feed items from a Business Profile account
and associates the feed with a customer.

=head1 SYNOPSIS

add_business_profile_location_extensions.pl [options]

    -help                               Show the help message.
    -customer_id                        The Google Ads customer ID.
    -business_profile_email             The email address associated with the Business Profile
                  `                     account.
    -business_profile_account_id        The account ID of the managed business.
    -business_profile_access_token      The access token created using the 'AdWords' scope
                                        and the client ID and client secret of with the
                                        Cloud project associated with the Business Profile account.

=cut