The AdWords API will sunset on April 27, 2022. Migrate to the Google Ads API to take advantage of the latest Google Ads features.

Optimization Samples

The code samples below provide examples of common optimization functions using the AdWords API. Client Library.

Get keyword traffic estimates

#!/usr/bin/env ruby
# Encoding: utf-8
#
# Copyright:: Copyright 2011, Google Inc. All Rights Reserved.
#
# License:: 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 gets keyword traffic estimates.

require 'adwords_api'

def estimate_keyword_traffic()
  # AdwordsApi::Api will read a config file from ENV['HOME']/adwords_api.yml
  # when called without parameters.
  adwords = AdwordsApi::Api.new

  # To enable logging of SOAP requests, set the log_level value to 'DEBUG' in
  # the configuration file or provide your own logger:
  # adwords.logger = Logger.new('adwords_xml.log')

  traffic_estimator_srv = adwords.service(:TrafficEstimatorService, API_VERSION)

  # Create keywords. Up to 2000 keywords can be passed in a single request.
  keywords = [
      # The 'xsi_type' field allows you to specify the xsi:type of the object
      # being created. It's only necessary when you must provide an explicit
      # type that the client library can't infer.
    {:xsi_type => 'Keyword', :text => 'mars cruise', :match_type => 'BROAD'},
    {:xsi_type => 'Keyword', :text => 'cheap cruise', :match_type => 'PHRASE'},
    {:xsi_type => 'Keyword', :text => 'cruise', :match_type => 'EXACT'},
    {:xsi_type => 'Keyword', :text => 'moon walk', :match_type => 'BROAD'}
  ]

  # Create a keyword estimate request for each keyword.
  keyword_requests = keywords.map {|keyword| {:keyword => keyword}}

  # Negative keywords don't return estimates, but adjust the estimates of the
  # other keywords in the hypothetical ad group. To specify a negative keyword
  # set the is_negative field to true.
  keyword_requests[3][:is_negative] = true

  # Create ad group estimate requests.
  ad_group_request = {
      :keyword_estimate_requests => keyword_requests,
      :max_cpc => {
          :micro_amount => 1000000
      }
  }

  # Create campaign estimate requests.
  campaign_request = {
      :ad_group_estimate_requests => [ad_group_request],
      # Set targeting criteria. Only locations and languages are supported.
      :criteria => [
          {:xsi_type => 'Location', :id => 2840}, # United States
          {:xsi_type => 'Language', :id => 1000}  # English
      ]
  }

  # Create a selector.
  selector = {
    :campaign_estimate_requests => [campaign_request],
    # Optional: Request a list of campaign level estimates segmented by
    # platform.
    :platform_estimate_requested => true
  }

  # Execute the request.
  response = traffic_estimator_srv.get(selector)

  # Display traffic estimates.
  if response and response[:campaign_estimates] and
      response[:campaign_estimates].size > 0
    campaign_estimate = response[:campaign_estimates].first

    unless campaign_estimate[:platform_estimates].nil?
      # Display the campaign level estimates segmented by platform.
      campaign_estimate[:platform_estimates].each do |platform_estimate|
        platform_message = ('Results for the platform with ID %d and name ' +
            '"%s":') % [platform_estimate[:platform][:id],
            platform_estimate[:platform][:platform_name]]
        display_mean_estimates(
            platform_message,
            platform_estimate[:min_estimate],
            platform_estimate[:max_estimate]
        )
      end
    end

    # Display the keyword estimates.
    keyword_estimates =
        campaign_estimate[:ad_group_estimates].first[:keyword_estimates]
    keyword_estimates.each_with_index do |keyword_estimate, index|
      next if keyword_requests[index][:is_negative]
      keyword = keyword_requests[index][:keyword]

      keyword_message = ('Results for the keyword with text "%s" and match ' +
          'type "%s":') % [keyword[:text], keyword[:match_type]]
      display_mean_estimates(
          keyword_message,
          keyword_estimate[:min],
          keyword_estimate[:max]
      )
    end
  else
    puts 'No traffic estimates were returned.'
  end
end

def display_mean_estimates(message, min_estimate, max_estimate)
  mean_average_cpc = nil
  unless min_estimate[:average_cpc].nil? || max_estimate[:average_cpc].nil?
    mean_average_cpc = calculate_mean(
        min_estimate[:average_cpc][:micro_amount],
        max_estimate[:average_cpc][:micro_amount]
    )
  end
  mean_average_position = calculate_mean(
      min_estimate[:average_position],
      max_estimate[:average_position]
  )
  mean_clicks = calculate_mean(
      min_estimate[:clicks_per_day],
      max_estimate[:clicks_per_day]
  )
  mean_total_cost = nil
  unless min_estimate[:total_cost].nil? || max_estimate[:total_cost].nil?
    mean_total_cost = calculate_mean(
        min_estimate[:total_cost][:micro_amount],
        max_estimate[:total_cost][:micro_amount]
    )
  end

  puts message
  puts "\tEstimated average CPC: %s" % format_mean(mean_average_cpc)
  puts "\tEstimated ad position: %s" % format_mean(mean_average_position)
  puts "\tEstimated daily clicks: %s" % format_mean(mean_clicks)
  puts "\tEstimated daily cost: %s" % format_mean(mean_total_cost)
end

def format_mean(mean)
  return "nil" if mean.nil?
  return "%.2f" % (mean / 1000000)
end

def calculate_mean(min_money, max_money)
  return nil if min_money.nil? || max_money.nil?
  return (min_money.to_f + max_money.to_f) / 2.0
end

if __FILE__ == $0
  API_VERSION = :v201809

  begin
    estimate_keyword_traffic()

  # Authorization error.
  rescue AdsCommon::Errors::OAuth2VerificationRequired => e
    puts "Authorization credentials are not valid. Edit adwords_api.yml for " +
        "OAuth2 client ID and secret and run misc/setup_oauth2.rb example " +
        "to retrieve and store OAuth2 tokens."
    puts "See this wiki page for more details:\n\n  " +
        'https://github.com/googleads/google-api-ads-ruby/wiki/OAuth2'

  # HTTP errors.
  rescue AdsCommon::Errors::HttpError => e
    puts "HTTP Error: %s" % e

  # API errors.
  rescue AdwordsApi::Errors::ApiException => e
    puts "Message: %s" % e.message
    puts 'Errors:'
    e.errors.each_with_index do |error, index|
      puts "\tError [%d]:" % (index + 1)
      error.each do |field, value|
        puts "\t\t%s: %s" % [field, value]
      end
    end
  end
end

Get all mobile bid modifier landscapes for a campaign

#!/usr/bin/env ruby
# Encoding: utf-8
#
# Copyright:: Copyright 2016, Google Inc. All Rights Reserved.
#
# License:: 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 gets all available campaign criterion bid modifier landscapes
# for a given campaign.
# To get campaigns, run basic_operations/get_campaigns.rb.

require 'adwords_api'

def get_campaign_criterion_bid_modifier_simulations(campaign_id)
  # AdwordsApi::Api will read a config file from ENV['HOME']/adwords_api.yml
  # when called without parameters.
  adwords = AdwordsApi::Api.new

  # To enable logging of SOAP requests, set the log_level value to 'DEBUG' in
  # the configuration file or provide your own logger:
  # adwords.logger = Logger.new('adwords_xml.log')

  data_srv = adwords.service(:DataService, API_VERSION)

  selector = {
    :fields => [
      'CampaignId',
      'CriterionId',
      'StartDate',
      'EndDate',
      'BidModifier',
      'LocalClicks',
      'LocalCost',
      'LocalImpressions',
      'TotalLocalImpressions',
      'TotalLocalClicks',
      'TotalLocalCost',
      'RequiredBudget'
    ],
    :predicates => [
      {:field => 'CampaignId', :operator => 'IN', :values => [campaign_id]}
    ],
    :paging => {
      :start_index => 0,
      :number_results => PAGE_SIZE
    }
  }

  landscape_points_in_previous_page = 0
  start_index = 0
  begin
    # Offset the start index by the number of landscape points in the last
    # retrieved page, NOT the number of entries (bid landscapes) in the page.
    start_index += landscape_points_in_previous_page
    selector[:paging][:start_index] = start_index

    # Reset the count of landscape points in preparation for processing the
    # next page.
    landscape_points_in_previous_page = 0

    # Request the next page of bid landscapes.
    page = data_srv.get_campaign_criterion_bid_landscape(selector)

    if page[:entries]
      page[:entries].each do |bid_modifier_landscape|
        puts ("Found campaign-level criterion bid modifier landscapes for " +
            "criterion with id '%d', start date '%s', end date '%s', and " +
            "landscape points:") % [
              bid_modifier_landscape[:criterion_id],
              bid_modifier_landscape[:start_date],
              bid_modifier_landscape[:end_date]
            ]
        bid_modifier_landscape[:landscape_points].each do |landscape_point|
          landscape_points_in_previous_page += 1
          puts ("  bid modifier: %f => clicks: %d, cost: %d, impressions: %d" +
              " total clicks: %d, total cost: %d, total impressions: %d" +
              " and required budget: %f") % [
                landscape_point[:bid_modifier],
                landscape_point[:clicks],
                landscape_point[:cost][:micro_amount],
                landscape_point[:impressions],
                landscape_point[:total_local_clicks],
                landscape_point[:total_local_cost][:micro_amount],
                landscape_point[:total_local_impressions],
                landscape_point[:required_budget][:micro_amount]
              ]
        end
        puts
      end
    end
  end while landscape_points_in_previous_page >= PAGE_SIZE
end

if __FILE__ == $0
  API_VERSION = :v201809
  PAGE_SIZE = 100

  begin
    campaign_id = 'INSERT_CAMPAIGN_ID_HERE'.to_i

    get_campaign_criterion_bid_modifier_simulations(campaign_id)

  # Authorization error.
  rescue AdsCommon::Errors::OAuth2VerificationRequired => e
    puts "Authorization credentials are not valid. Edit adwords_api.yml for " +
        "OAuth2 client ID and secret and run misc/setup_oauth2.rb example " +
        "to retrieve and store OAuth2 tokens."
    puts "See this wiki page for more details:\n\n  " +
        'https://github.com/googleads/google-api-ads-ruby/wiki/OAuth2'

  # HTTP errors.
  rescue AdsCommon::Errors::HttpError => e
    puts "HTTP Error: %s" % e

  # API errors.
  rescue AdwordsApi::Errors::ApiException => e
    puts "Message: %s" % e.message
    puts 'Errors:'
    e.errors.each_with_index do |error, index|
      puts "\tError [%d]:" % (index + 1)
      error.each do |field, value|
        puts "\t\t%s: %s" % [field, value]
      end
    end
  end
end

Get a bid landscape for an ad group and criterion

#!/usr/bin/env ruby
# Encoding: utf-8
#
# Copyright:: Copyright 2011, Google Inc. All Rights Reserved.
#
# License:: 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 gets bid landscapes for a keyword. To get keywords, run
# get_keywords.rb.

require 'adwords_api'

def get_criterion_bid_landscapes(ad_group_id, keyword_id)
  # AdwordsApi::Api will read a config file from ENV['HOME']/adwords_api.yml
  # when called without parameters.
  adwords = AdwordsApi::Api.new

  # To enable logging of SOAP requests, set the log_level value to 'DEBUG' in
  # the configuration file or provide your own logger:
  # adwords.logger = Logger.new('adwords_xml.log')

  data_srv = adwords.service(:DataService, API_VERSION)

  # Get keyword bid landscape.
  query_builder = adwords.service_query_builder do |b|
    b.select(%w[
      AdGroupId CriterionId StartDate EndDate Bid BiddableConversions
      BiddableConversionsValue LocalClicks LocalCost LocalImpressions
    ])
    b.where('AdGroupId').equal_to(ad_group_id)
    b.where('CriterionId').equal_to(keyword_id)
    b.limit(0, PAGE_SIZE)
  end
  query = query_builder.build

  loop do
    # Request the next page of bid landscapes.
    page = data_srv.query_criterion_bid_landscape(query.to_s)

    if page and page[:entries]
      puts "Bid landscape(s) retrieved: %d." % [page[:entries].length]
      page[:entries].each do |bid_landscape|
        puts ("Retrieved keyword bid landscape with ad group ID %d" +
            ", keyword ID %d, start date '%s', end date '%s'" +
            ", with landscape points:") %
            [bid_landscape[:ad_group_id], bid_landscape[:criterion_id],
            bid_landscape[:start_date], bid_landscape[:end_date]]
        bid_landscape[:landscape_points].each do |point|
          landscape_points_in_previous_page += 1
          puts ("\t%d => clicks: %d, cost: %d, impressions: %d, biddable " +
              "conversions: %.2f, biddable conversions value: %.2f") %
              [point[:bid][:micro_amount], point[:clicks],
              point[:cost][:micro_amount], point[:impressions],
              point[:biddable_conversions], point[:biddable_conversions_value]]
        end
      end
    end
    break unless query.has_next_landscape_page(page)
    query.next_page()
  end
end

if __FILE__ == $0
  API_VERSION = :v201809
  PAGE_SIZE = 100

  begin
    ad_group_id = 'INSERT_ADGROUP_ID_HERE'.to_i
    keyword_id = 'INSERT_KEYWORD_ID_HERE'.to_i
    get_criterion_bid_landscapes(ad_group_id, keyword_id)

  # Authorization error.
  rescue AdsCommon::Errors::OAuth2VerificationRequired => e
    puts "Authorization credentials are not valid. Edit adwords_api.yml for " +
        "OAuth2 client ID and secret and run misc/setup_oauth2.rb example " +
        "to retrieve and store OAuth2 tokens."
    puts "See this wiki page for more details:\n\n  " +
        'https://github.com/googleads/google-api-ads-ruby/wiki/OAuth2'

  # HTTP errors.
  rescue AdsCommon::Errors::HttpError => e
    puts "HTTP Error: %s" % e

  # API errors.
  rescue AdwordsApi::Errors::ApiException => e
    puts "Message: %s" % e.message
    puts 'Errors:'
    e.errors.each_with_index do |error, index|
      puts "\tError [%d]:" % (index + 1)
      error.each do |field, value|
        puts "\t\t%s: %s" % [field, value]
      end
    end
  end
end

#!/usr/bin/env ruby
# Encoding: utf-8
#
# Copyright:: Copyright 2011, Google Inc. All Rights Reserved.
#
# License:: 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 retrieves keywords that are related to a given keyword.

require 'adwords_api'

def get_keyword_ideas(keyword_text, ad_group_id)
  # AdwordsApi::Api will read a config file from ENV['HOME']/adwords_api.yml
  # when called without parameters.
  adwords = AdwordsApi::Api.new

  # To enable logging of SOAP requests, set the log_level value to 'DEBUG' in
  # the configuration file or provide your own logger:
  # adwords.logger = Logger.new('adwords_xml.log')

  targeting_idea_srv = adwords.service(:TargetingIdeaService, API_VERSION)

  # Construct selector object.
  selector = {
    :idea_type => 'KEYWORD',
    :request_type => 'IDEAS'
  }
  selector[:requested_attribute_types] = [
    'KEYWORD_TEXT',
    'SEARCH_VOLUME',
    'AVERAGE_CPC',
    'COMPETITION',
    'CATEGORY_PRODUCTS_AND_SERVICES',
  ]

  selector[:paging] = {
    :start_index => 0,
    :number_results => PAGE_SIZE
  }

  search_parameters = []
  search_parameters << {
      # The 'xsi_type' field allows you to specify the xsi:type of the object
      # being created. It's only necessary when you must provide an explicit
      # type that the client library can't infer.
      :xsi_type => 'RelatedToQuerySearchParameter',
      :queries => [keyword_text]
  }

  search_parameters << {
    # Language setting (optional).
    # The ID can be found in the documentation:
    #  https://developers.google.com/adwords/api/docs/appendix/languagecodes
    # Only one LanguageSearchParameter is allowed per request.
    :xsi_type => 'LanguageSearchParameter',
    :languages => [{:id => 1000}]
  }

  search_parameters << {
    # Network search parameter (optional).
    :xsi_type => 'NetworkSearchParameter',
    :network_setting => {
      :target_google_search => true,
      :target_search_network => false,
      :target_content_network => false,
      :target_partner_search_network => false
    }
  }

  unless ad_group_id.nil?
    search_parameters << {
      :xsi_type => 'SeedAdGroupIdSearchParameter',
      :ad_group_id => ad_group_id
    }
  end

  selector[:search_parameters] = search_parameters

  # Define initial values.
  offset = 0
  results = []

  begin
    # Perform request. If this loop executes too many times in quick suggestion,
    # you may get a RateExceededError. See here for more info on handling these:
    # https://developers.google.com/adwords/api/docs/guides/rate-limits
    page = targeting_idea_srv.get(selector)
    results += page[:entries] if page and page[:entries]

    # Prepare next page request.
    offset += PAGE_SIZE
    selector[:paging][:start_index] = offset
  end while offset < page[:total_num_entries]

  # Display results.
  results.each do |result|
    data = result[:data]
    keyword = data['KEYWORD_TEXT'][:value]
    average_cpc = data['AVERAGE_CPC'][:value]
    competition = data['COMPETITION'][:value]
    products_and_services = data['CATEGORY_PRODUCTS_AND_SERVICES'][:value]
    average_monthly_searches = data['SEARCH_VOLUME'][:value]
    puts ("Keyword with text '%s', average monthly search volume %d, " +
        "average CPC %d, and competition %.2f was found with categories: %s") %
        [
          keyword,
          average_monthly_searches,
          average_cpc[:micro_amount],
          competition,
          products_and_services
        ]
  end
  puts "Total keywords related to '%s': %d." % [keyword_text, results.length]
end

if __FILE__ == $0
  API_VERSION = :v201809
  PAGE_SIZE = 100

  begin
    keyword_text = 'INSERT_KEYWORD_TEXT_HERE'
    # Optional:
    ad_group_id = 'INSERT_AD_GROUP_ID_HERE'

    ad_group_id = nil if ad_group_id == 'INSERT_AD_GROUP_ID_HERE'
    get_keyword_ideas(keyword_text, ad_group_id)

  # Authorization error.
  rescue AdsCommon::Errors::OAuth2VerificationRequired => e
    puts "Authorization credentials are not valid. Edit adwords_api.yml for " +
        "OAuth2 client ID and secret and run misc/setup_oauth2.rb example " +
        "to retrieve and store OAuth2 tokens."
    puts "See this wiki page for more details:\n\n  " +
        'https://github.com/googleads/google-api-ads-ruby/wiki/OAuth2'

  # HTTP errors.
  rescue AdsCommon::Errors::HttpError => e
    puts "HTTP Error: %s" % e

  # API errors.
  rescue AdwordsApi::Errors::ApiException => e
    puts "Message: %s" % e.message
    puts 'Errors:'
    e.errors.each_with_index do |error, index|
      puts "\tError [%d]:" % (index + 1)
      error.each do |field, value|
        puts "\t\t%s: %s" % [field, value]
      end
    end
  end
end