Share your feedback about the AdWords API! Take our annual survey.

Advanced Operations Samples

The code samples below provide examples of common advanced operations using the AdWords API. Client Library.

Add an ad customizer feed

#!/usr/bin/env ruby
# Encoding: utf-8
#
# Copyright:: Copyright 2015, 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 adds an ad customizer feed using Extension Services. Then it adds
# an ad that uses the feed to populate dynamic data.

require 'adwords_api'
require 'date'

def add_ad_customizers(feed_name, ad_group_ids)
  # 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')

  feed_item_srv = adwords.service(:FeedItemService, API_VERSION)
  ad_group_ad_srv = adwords.service(:AdGroupAdService, API_VERSION)

  # Create a customizer feed. One feed per account can be used for all ads.
  feed_data = create_customizer_feed(adwords, feed_name)

  # Now adding feed items -- the values we'd like to place.
  now_date = Date.today()

  items_data = [
    {
      :name => 'Mars',
      :price => '$1234.56',
      :date => now_date.strftime('%Y%m01 000000'),
      :ad_group_id => ad_group_ids[0]
    },
    {
      :name => 'Venus',
      :price => '$1450.00',
      :date => now_date.strftime('%Y%m15 000000'),
      :ad_group_id => ad_group_ids[1]
     }
  ]

  feed_items = items_data.map do |item|
    {
      :feed_id => feed_data[:feed_id],
      :attribute_values => [
        {
          :feed_attribute_id => feed_data[:name_id],
          :string_value => item[:name]
        },
        {
          :feed_attribute_id => feed_data[:price_id],
          :string_value => item[:price]
        },
        {
          :feed_attribute_id => feed_data[:date_id],
          :string_value => item[:date]
        }
      ]
    }
  end

  feed_items_operations = feed_items.map do |item|
    {:operator => 'ADD', :operand => item}
  end

  response = feed_item_srv.mutate(feed_items_operations)
  if response and response[:value]
    response[:value].each do |feed_item|
      puts 'Feed item with ID %d was added.' % feed_item[:feed_item_id]
    end
  else
    raise new StandardError, 'No feed items were added.'
  end

  restrict_feed_item_to_ad_group(adwords, response[:value][0], ad_group_ids[0])
  restrict_feed_item_to_ad_group(adwords, response[:value][1], ad_group_ids[1])

  # All set! We can now create ads with customizations.
  expanded_text_ad = {
    :xsi_type => 'ExpandedTextAd',
    :headline_part1 => 'Luxury Cruise to {=%s.Name}' % feed_name,
    :headline_part2 => 'Only {=%s.Price}' % feed_name,
    :description => 'Offer ends in {=countdown(%s.Date)}!' % feed_name,
    :final_urls => ['http://www.example.com']
  }

  # We add the same ad to both ad groups. When they serve, they will show
  # different values, since they match different feed items.
  operations = ad_group_ids.map do |ad_group_id|
    {
      :operator => 'ADD',
      :operand => {
        :ad_group_id => ad_group_id,
        :ad => expanded_text_ad.dup()
      }
    }
  end

  response = ad_group_ad_srv.mutate(operations)
  if response and response[:value]
    ads = response[:value]
    ads.each do |ad|
      puts "\tCreated an ad with ID %d, type '%s' and status '%s'" %
          [ad[:ad][:id], ad[:ad][:ad_type], ad[:status]]
    end
  else
    raise StandardError, 'No ads were added.'
  end
end

def create_customizer_feed(adwords, feed_name)
  ad_customizer_srv = adwords.service(:AdCustomizerFeedService, API_VERSION)
  feed = {
    :feed_name => feed_name,
    :feed_attributes => [
      {:name => 'Name', :type => 'STRING'},
      {:name => 'Price', :type => 'PRICE'},
      {:name => 'Date', :type => 'DATE_TIME'}
    ]
  }
  operation = {:operand => feed, :operator => 'ADD'}
  added_feed = ad_customizer_srv.mutate([operation])[:value].first()
  puts "Created ad customizer feed with ID = %d and name = '%s'." %
      [added_feed[:feed_id], added_feed[:feed_name]]
  added_feed[:feed_attributes].each do |feed_attribute|
    puts "  ID: %d, name: '%s', type: %s" %
        [feed_attribute[:id], feed_attribute[:name], feed_attribute[:type]]
  end
  return {
    :feed_id => added_feed[:feed_id],
    :name_id => added_feed[:feed_attributes][0][:id],
    :price_id => added_feed[:feed_attributes][1][:id],
    :date_id => added_feed[:feed_attributes][2][:id]
  }
end

def restrict_feed_item_to_ad_group(adwords, feed_item, ad_group_id)
  # Optional: Restrict this feed item to only serve with ads for the
  # specified ad group ID.
  feed_item_target_srv = adwords.service(:FeedItemTargetService, API_VERSION)

  ad_group_target = {
    :xsi_type => 'FeedItemAdGroupTarget',
    :feed_id => feed_item[:feed_id],
    :feed_item_id => feed_item[:feed_item_id],
    :ad_group_id => ad_group_id
  }

  operation = {
    :operator => 'ADD',
    :operand => ad_group_target
  }

  retval = feed_item_target_srv.mutate([operation])
  new_ad_group_target = retval[:value].first
  puts ('Feed item target for feed ID %d and feed item ID %d was created to ' +
      'restrict serving to ad group ID %d') % [new_ad_group_target[:feed_id],
      new_ad_group_target[:feed_item_id], new_ad_group_target[:ad_group_id]]
end

if __FILE__ == $0
  API_VERSION = :v201809

  begin
    feed_name = 'INSERT_FEED_NAME_HERE'.to_s
    ad_group_ids = [
        'INSERT_AD_GROUP_ID_HERE'.to_i,
        'INSERT_AD_GROUP_ID_HERE'.to_i
    ]
    add_ad_customizers(feed_name, ad_group_ids)

  # 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

Add an ad group level bid modifier

#!/usr/bin/env ruby
# Encoding: utf-8
#
# Copyright:: Copyright 2013, 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 illustrates how to add an ad group level mobile bid modifier
# override for a campaign.

require 'adwords_api'

def add_ad_group_bid_modifier(ad_group_id, bid_modifier)
  # 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')

  bid_modifier_srv = adwords.service(:AdGroupBidModifierService, API_VERSION)

  # Mobile criterion ID.
  criterion_id = 30001

  # Prepare to add an ad group level override.
  operation = {
    # Use 'ADD' to add a new modifier and 'SET' to update an existing one. A
    # modifier can be removed with the 'REMOVE' operator.
    :operator => 'ADD',
    :operand => {
      :ad_group_id => ad_group_id,
      :criterion => {
        :xsi_type => 'Platform',
        :id => criterion_id
      },
      :bid_modifier => bid_modifier
    }
  }

  # Add ad group level mobile bid modifier.
  response = bid_modifier_srv.mutate([operation])
  if response and response[:value]
    modifier = response[:value].first
    value = modifier[:bid_modifier] || 'unset'
    puts ('Campaign ID %d, AdGroup ID %d, Criterion ID %d was updated with ' +
        'ad group level modifier: %s') %
           [modifier[:campaign_id], modifier[:ad_group_id],
            modifier[:criterion][:id], value]
  else
    puts 'No modifiers were added.'
  end
end

if __FILE__ == $0
  API_VERSION = :v201809

  begin
    # ID of an ad group to add an override for.
    ad_group_id = 'INSERT_AD_GROUP_ID_HERE'.to_i
    # Bid modifier to override with.
    bid_modifier = 1.5

    add_ad_group_bid_modifier(ad_group_id, bid_modifier)

  # 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

Add a page feed specifying URLs for a DSA campaign

#!/usr/bin/env ruby
# Encoding: utf-8
#
# Copyright:: Copyright 2017, 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 code example adds a page feed to specify precisely which URLs to use with
# your Dynamic Search Ads campaign. To use a Dynamic Search Ads campaign, run
# add_dynamic_search_ads_campaign.rb. To get campaigns, run get_campaigns.rb.

require 'adwords_api'

def add_dynamic_page_feed(campaign_id, 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')

  dsa_page_url_label = "discounts"

  # Get the page feed details. This code example creates a new feed, but you can
  # fetch and re-use an existing feed.
  feed_details = create_feed(adwords)
  create_feed_mapping(adwords, feed_details)
  create_feed_items(adwords, feed_details, dsa_page_url_label)

  # Associate the page feed with the campaign.
  update_campaign_dsa_setting(adwords, campaign_id, feed_details)

  # Optional: Target web pages matching the feed's label in the ad group.
  add_dsa_targeting(adwords, ad_group_id, dsa_page_url_label)

  puts "Dynamic page feed setup is complete for campaign ID %d." % campaign_id
end

def create_feed(adwords)
  feed_srv = adwords.service(:FeedService, API_VERSION)

  url_attribute = {
    :type => 'URL_LIST',
    :name => 'Page URL'
  }

  label_attribute = {
    :type => 'STRING_LIST',
    :name => 'Label'
  }

  dsa_page_feed = {
    :name => 'DSA Feed #%s' % (Time.now.to_f * 1000).to_i,
    :attributes => [url_attribute, label_attribute],
    :origin => 'USER'
  }

  operation = {
    :operand => dsa_page_feed,
    :operator => 'ADD'
  }

  new_feed = feed_srv.mutate([operation])[:value].first

  feed_details = {
    :feed_id => new_feed[:id],
    :url_attribute_id => new_feed[:attributes][0][:id],
    :label_attribute_id => new_feed[:attributes][1][:id]
  }
  puts ("Feed with name '%s' and ID %d with urlAttributeId %d and " +
      "labelAttributeId %d was created.") % [
        new_feed[:name],
        feed_details[:feed_id],
        feed_details[:url_attribute_id],
        feed_details[:label_attribute_id]
      ]

  return feed_details
end

def create_feed_mapping(adwords, feed_details)
  feed_mapping_srv = adwords.service(:FeedMappingService, API_VERSION)

  url_field_mapping = {
    :feed_attribute_id => feed_details[:url_attribute_id],
    :field_id => DSA_PAGE_URLS_FIELD_ID
  }

  label_field_mapping = {
    :feed_attribute_id => feed_details[:label_attribute_id],
    :field_id => DSA_LABEL_FIELD_ID
  }

  feed_mapping = {
    :criterion_type => DSA_PAGE_FEED_CRITERION_TYPE,
    :feed_id => feed_details[:feed_id],
    :attribute_field_mappings => [url_field_mapping, label_field_mapping]
  }

  operation = {
    :operand => feed_mapping,
    :operator => 'ADD'
  }

  new_feed_mapping = feed_mapping_srv.mutate([operation])[:value].first
  puts ("Feed mapping with ID %d and criterionType %d was saved for feed " +
      "with ID %d.") % [
        new_feed_mapping[:feed_mapping_id],
        new_feed_mapping[:criterion_type],
        new_feed_mapping[:feed_id]
      ]
end

def create_feed_items(adwords, feed_details, label_name)
  feed_item_srv = adwords.service(:FeedItemService, API_VERSION)

  operations = [
    create_dsa_url_add_operation(
      feed_details,
      'http://www.example.com/discounts/rental-cars',
      label_name
    ),
    create_dsa_url_add_operation(
      feed_details,
      'http://www.example.com/discounts/hotel-deals',
      label_name
    ),
    create_dsa_url_add_operation(
      feed_details,
      'http://www.example.com/discounts/flight-deals',
      label_name
    )
  ]

  result = feed_item_srv.mutate(operations)
  result[:value].each do |item|
    puts "Feed item with feed item ID %d was added." % item[:feed_item_id]
  end
end

def create_dsa_url_add_operation(feed_details, url, label_name)
  # Optional: Add the {feeditem} valuetrack parameter to track which page
  # feed items lead to each click.
  url = url + '?id={feeditem}'

  url_attribute_value = {
    :feed_attribute_id => feed_details[:url_attribute_id],
    # See https://support.google.com/adwords/answer/7166527 for page feed URL
    # recommendations and rules.
    :string_values => [url]
  }

  label_attribute_value = {
    :feed_attribute_id => feed_details[:label_attribute_id],
    :string_values => [label_name]
  }

  feed_item = {
    :feed_id => feed_details[:feed_id],
    :attribute_values => [url_attribute_value, label_attribute_value]
  }

  operation = {
    :operand => feed_item,
    :operator => 'ADD'
  }

  return operation
end

def update_campaign_dsa_setting(adwords, campaign_id, feed_details)
  campaign_srv = adwords.service(:CampaignService, API_VERSION)

  selector = {
    :fields => ['Id', 'Settings'],
    :predicates => [
      {:field => 'CampaignId', :operator => 'IN', :values => [campaign_id]}
    ]
  }

  campaign_page = campaign_srv.get(selector)
  if campaign_page.nil? or campaign_page[:entries].nil? or
      campaign_page[:total_num_entries] == 0
    raise 'No campaign found with ID: %d' % campaign_id
  end

  campaign = campaign_page[:entries].first

  if campaign[:settings].nil?
    raise 'Campaign with ID %d is not a DSA campaign.' % campaign_id
  end

  dsa_setting = nil
  campaign[:settings].each do |setting|
    if setting[:setting_type] == 'DynamicSearchAdsSetting'
      dsa_setting = setting
      break
    end
  end

  if dsa_setting.nil?
    raise 'Campaign with ID %d is not a DSA campaign.' % campaign_id
  end

  # Use a page feed to specify precisely which URLs to use with your Dynamic
  # Search Ads.
  page_feed = {
    :feed_ids => [feed_details[:feed_id]]
  }
  dsa_setting[:page_feed] = page_feed

  # Optional: Specify whether only the supplied URLs should be used with your
  # Dynamic Search Ads.
  dsa_setting[:use_supplied_urls_only] = true

  updated_campaign = {
    :id => campaign_id,
    :settings => campaign[:settings]
  }

  operation = {
    :operand => updated_campaign,
    :operator => 'SET'
  }

  updated_campaign = campaign_srv.mutate([operation])[:value].first
  puts "DSA page feed for campaign ID %d was updated with feed ID %d." % [
    updated_campaign[:id], feed_details[:feed_id]
  ]
end

def add_dsa_targeting(adwords, ad_group_id, dsa_page_url_label)
  ad_group_criterion_srv =
      adwords.service(:AdGroupCriterionService, API_VERSION)

  webpage = {
    :xsi_type => 'Webpage',
    :parameter => {
      :criterion_name => 'Test criterion',
      :conditions => [{
        :operand => 'CUSTOM_LABEL',
        :argument => dsa_page_url_label
      }]
    }
  }

  bidding_strategy_configuration = {
    :bids => [{
      :xsi_type => 'CpcBid',
      :bid => {
        :micro_amount => 1_500_000
      }
    }]
  }

  criterion = {
    :xsi_type => 'BiddableAdGroupCriterion',
    :ad_group_id => ad_group_id,
    :criterion => webpage,
    :bidding_strategy_configuration => bidding_strategy_configuration
  }

  operation = {
    :operand => criterion,
    :operator => 'ADD'
  }

  new_criterion = ad_group_criterion_srv.mutate([operation])[:value].first
  puts "Web page criterion with ID %d and status '%s' was created." %
      [new_criterion[:criterion][:id], new_criterion[:user_status]]
end

if __FILE__ == $0
  API_VERSION = :v201809

  # The criterion type to be used for DSA page feeds. DSA page feeds use
  # criterionType field instead of the placeholderType field unlike most other
  # feed types.
  DSA_PAGE_FEED_CRITERION_TYPE = 61

  # ID that corresponds to the page URLs.
  DSA_PAGE_URLS_FIELD_ID = 1

  # ID that corresponds to the labels.
  DSA_LABEL_FIELD_ID = 2

  begin
    campaign_id = 'INSERT_CAMPAIGN_ID_HERE'.to_i
    ad_group_id = 'INSERT_AD_GROUP_ID_HERE'.to_i

    add_dynamic_page_feed(campaign_id, 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

Add a DSA campaign

#!/usr/bin/env ruby
# Encoding: utf-8
#
# Copyright:: Copyright 2017, 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 code example adds a Dynamic Search Ads campaign. To get campaigns, run
# get_campaigns.rb.

require 'date'

require 'adwords_api'

def add_dynamic_search_ads_campaign()
  # 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')

  budget = create_budget(adwords)

  campaign = create_campaign(adwords, budget)
  ad_group = create_ad_group(adwords, campaign)
  create_expanded_dsa(adwords, ad_group)
  add_web_page_criteria(adwords, ad_group)
end

def create_budget(adwords)
  budget_srv = adwords.service(:BudgetService, API_VERSION)

  shared_budget = {
    :name => "Interplanetary Cruise #%d" % (Time.now.to_f * 1000).to_i,
    :amount => {
      :micro_amount => 50_000_000
    },
    :delivery_method => 'STANDARD'
  }

  budget_operation = {
    :operand => shared_budget,
    :operator => 'ADD'
  }

  budget = budget_srv.mutate([budget_operation])[:value].first

  return budget
end

def create_campaign(adwords, budget)
  campaign_srv = adwords.service(:CampaignService, API_VERSION)

  campaign = {
    :name => "Interplanetary Cruise #%d" % (Time.now.to_f * 1000).to_i,
    :advertising_channel_type => 'SEARCH',
    # Recommendation: Set the campaign to PAUSED when creating it to prevent the
    # ads from immediately serving. Set to ENABLED once you've added targeting
    # and the ads are ready to serve.
    :status => 'PAUSED',
    :bidding_strategy_configuration => {
      :bidding_strategy_type => 'MANUAL_CPC'
    },
    # Only the budgetId should be sent; all other fields will be ignored by
    # CampaignService.
    :budget => {
      :budget_id => budget[:budget_id]
    },
    :settings => [
      :xsi_type => 'DynamicSearchAdsSetting',
      :domain_name => 'example.com',
      :language_code => 'en'
    ],
    # Optional: Set the start and end dates.
    :start_date => DateTime.parse((Date.today + 1).to_s).strftime('%Y%m%d'),
    :end_date => DateTime.parse(Date.today.next_year.to_s).strftime('%Y%m%d')
  }

  operation = {
    :operand => campaign,
    :operator => 'ADD'
  }

  new_campaign = campaign_srv.mutate([operation])[:value].first
  puts "Campaign with name '%s' and ID %d was added." %
      [new_campaign[:name], new_campaign[:id]]

  return new_campaign
end

def create_ad_group(adwords, campaign)
  ad_group_srv = adwords.service(:AdGroupService, API_VERSION)

  ad_group = {
    # Required: Set the ad group's tpe to Dynamic Search Ads.
    :ad_group_type => 'SEARCH_DYNAMIC_ADS',
    :name => "Earth to Mars Cruises #%d" % (Time.now.to_f * 1000).to_i,
    :campaign_id => campaign[:id],
    :status => 'PAUSED',
    # Recommended: Set a tracking URL template for your ad group if you want to
    # use URL tracking software.
    :tracking_url_template =>
        'http://tracker.example.com/traveltracker/{escapedlpurl}',
    :bidding_strategy_configuration => {
      :bids => [{
        :xsi_type => 'CpcBid',
        :bid => {
          :micro_amount => 3_000_000
        }
      }]
    }
  }

  operation = {
    :operand => ad_group,
    :operator => 'ADD'
  }

  new_ad_group = ad_group_srv.mutate([operation])[:value].first
  puts "Ad group with name '%s' and ID %d was added." %
      [new_ad_group[:name], new_ad_group[:id]]

  return new_ad_group
end

def create_expanded_dsa(adwords, ad_group)
  ad_group_ad_srv = adwords.service(:AdGroupAdService, API_VERSION)

  # Create the expanded Dynamic Search Ad. This ad will have its headline and
  # final URL auto-generated at serving time according to domain name specific
  # information provided by DynamicSearchAdsSetting at the campaign level.
  expanded_dsa = {
    :xsi_type => 'ExpandedDynamicSearchAd',
    :description => 'Buy your tickets now!',
    :description2 => 'Discount ends soon'
  }

  ad_group_ad = {
    :ad_group_id => ad_group[:id],
    :ad => expanded_dsa,
    # Optional: Set the status.
    :status => 'PAUSED'
  }

  operation = {
    :operand => ad_group_ad,
    :operator => 'ADD'
  }

  new_ad_group_ad = ad_group_ad_srv.mutate([operation])[:value].first
  new_expanded_dsa = new_ad_group_ad[:ad]
  puts ("Expanded Dynamic Search Ad with ID %d, description '%s', and " +
      "description 2 '%s' was added.") % [new_expanded_dsa[:id],
      new_expanded_dsa[:description], new_expanded_dsa[:description2]]
end

def add_web_page_criteria(adwords, ad_group)
  ad_group_criterion_srv =
      adwords.service(:AdGroupCriterionService, API_VERSION)

  webpage = {
    :xsi_type => 'Webpage',
    :parameter => {
      :criterion_name => 'Special offers',
      :conditions => [
        {
          :operand => 'URL',
          :argument => '/specialoffers'
        },
        {
          :operand => 'PAGE_TITLE',
          :argument => 'Special Offer'
        }
      ]
    }
  }

  biddable_ad_group_criterion = {
    :xsi_type => 'BiddableAdGroupCriterion',
    :ad_group_id => ad_group[:id],
    :criterion => webpage,
    :user_status => 'PAUSED',
    # Optional: set a custom bid.
    :bidding_strategy_configuration => {
      :bids => [{
        :xsi_type => 'CpcBid',
        :bid => {
          :micro_amount => 10_000_000
        }
      }]
    }
  }

  operation = {
    :operand => biddable_ad_group_criterion,
    :operator => 'ADD'
  }

  new_ad_group_criterion =
      ad_group_criterion_srv.mutate([operation])[:value].first
  puts "Webpage criterion with ID %d was added to ad group ID %d." % [
    new_ad_group_criterion[:criterion][:id],
    new_ad_group_criterion[:ad_group_id]
  ]
end

if __FILE__ == $0
  API_VERSION = :v201809

  begin
    add_dynamic_search_ads_campaign()

  # 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

Add an expanded text ad with Upgraded URLs

#!/usr/bin/env ruby
# Encoding: utf-8
#
# Copyright:: Copyright 2014, 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 code example adds an expanded text ad that uses advanced features of
# upgraded URLs.

require 'adwords_api'

def add_text_ad_with_upgraded_urls(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')

  ad_group_ad_srv = adwords.service(:AdGroupAdService, API_VERSION)

  expanded_text_ad = {
    :xsi_type => 'ExpandedTextAd',
    :headline_part1 => 'Luxury Cruise to Mars',
    :headline_part2 => 'Visit the Red Planet in style.',
    :description => 'Low-gravity fun for everyone!',
    # Specify a list of final URLs. This field cannot be set if :url field
    # is set. This may be specified at ad, criterion, and feed item levels.
    :final_urls => [
      'http://www.example.com/cruise/space/',
      'http://www.example.com/locations/mars/'
    ],
    # Specify a list of final mobile URLs. This field cannot be set if :url
    # field is set. This may be specificed at ad, criterion, and
    # feed item levels.
    :final_mobile_urls => [
      'http://mobile.example.com/cruise/space/',
      'http://mobile.example.com/locations/mars/'
    ]
  }

  # Specify a tracking URL for 3rd party tracking provider. You may specify
  # one at customer, campaign, ad group, ad, criterion, or feed item levels.
  expanded_text_ad[:tracking_url_template] = 'http://tracker.example.com/' +
      '?season={_season}&promocode={_promocode}&u={lpurl}'

  # Since your tracking URL will have two custom parameters, provide their
  # values too. This can be provided at campaign, ad group, ad, criterion, or
  # feed item levels.
  season_parameter = {
    :key => 'season',
    :value => 'christmas'
  }

  promo_code_parameter = {
    :key => 'promocode',
    :value => 'NYC123'
  }

  tracking_url_parameters = {
    :parameters => [season_parameter, promo_code_parameter]
  }

  expanded_text_ad[:url_custom_parameters] = tracking_url_parameters

  operation = {
    :operator => 'ADD',
    :operand => {:ad_group_id => ad_group_id, :ad => expanded_text_ad}
  }

  # Add ads.
  response = ad_group_ad_srv.mutate([operation])
  if response and response[:value]
    response[:value].each do |ad|
      text_ad = ad[:ad]
      puts "Ad with ID %d was added." % [text_ad[:id]]
      puts "\tFinal URLs: %s" % [text_ad[:final_urls].join(', ')]
      puts "\tFinal Mobile URLs: %s" % [text_ad[:final_mobile_urls].join(', ')]
      puts "\tTracking URL template: %s" % [text_ad[:tracking_url_template]]
      custom_parameters =
          text_ad[:url_custom_parameters][:parameters].map do |custom_parameter|
            "%s=%s" % [custom_parameter[:key], custom_parameter[:value]]
          end
      puts "\tCustom parameters: %s" % [custom_parameters.join(', ')]
    end
  else
    raise StandardError, 'No ads were added.'
  end
end

if __FILE__ == $0
  API_VERSION = :v201809

  begin
    ad_group_id = 'INSERT_AD_GROUP_ID_HERE'.to_i
    add_text_ad_with_upgraded_urls(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

Add a Gmail ad to an ad group

#!/usr/bin/env ruby
# Encoding: utf-8
#
# Copyright:: Copyright 2018, 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 adds a Gmail ad to a given ad group. The ad group's campaign
# needs to have an AdvertisingChannelType of DISPLAY and
# AdvertisingChannelSubType of DISPLAY_GMAIL_AD. To get ad groups, run
# get_ad_groups.rb.

require 'adwords_api'
require 'base64'

def add_gmail_ad(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')

  ad_group_ad_srv = adwords.service(:AdGroupAdService, API_VERSION)

  # This ad format does not allow the creation of an image using the
  # Image.data field. An image must first be created using the MediaService,
  # and Image.mediaId must be populated when creating the ad.
  logo_image_id = upload_image(adwords, 'https://goo.gl/mtt54n')
  logo_image = {
    :xsi_type => 'Image',
    :media_id => logo_image_id
  }

  marketing_image_id = upload_image(adwords, 'http://goo.gl/3b9Wfh')
  ad_image = {
    :xsi_type => 'Image',
    :media_id => marketing_image_id
  }

  teaser = {
    :headline => 'Dream',
    :description => 'Create your own adventure',
    :business_name => 'Interplanetary Ships',
    :logo_image => logo_image
  }

  gmail_ad = {
    :xsi_type => 'GmailAd',
    :teaser => teaser,
    :marketing_image => ad_image,
    :marketing_image_headline => 'Travel',
    :marketing_image_description => 'Take to the skies!',
    :final_urls => ['http://www.example.com']
  }

  ad_group_ad = {
    :ad_group_id => ad_group_id,
    :ad => gmail_ad,
    :status => 'PAUSED'
  }

  operation = {
    :operator => 'ADD',
    :operand => ad_group_ad
  }

  result = ad_group_ad_srv.mutate([operation])
  result[:value].each do |ad_group_ad|
    puts 'A Gmail ad with id %d and short headline "%s" was added.' %
        [ad_group_ad[:ad][:id], ad_group_ad[:ad][:teaser][:headline]]
  end
end

def upload_image(adwords, url)
  media_srv = adwords.service(:MediaService, API_VERSION)

  image_data = AdsCommon::Http.get(url, adwords.config)
  base64_image_data = Base64.encode64(image_data)

  image = {
    :xsi_type => 'Image',
    :data => base64_image_data,
    :type => 'IMAGE'
  }

  response = media_srv.upload([image])

  return response.first[:media_id]
end

if __FILE__ == $0
  API_VERSION = :v201809

  begin
    ad_group_id = 'INSERT_AD_GROUP_ID_HERE'.to_i

    add_gmail_ad(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

Add an HTML 5 ad to an ad group

#!/usr/bin/env ruby
# Encoding: utf-8
#
# Copyright:: Copyright 2015, 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 code example adds an HTML5 ad to a given ad group.
# To get ad groups, run basic_operations/get_ad_groups.rb.

require 'adwords_api'
require 'base64'

def add_html5_ad(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')

  ad_group_ad_srv = adwords.service(:AdGroupAdService, API_VERSION)

  # The HTML5 zip file contains all the HTML, CSS, and images needed for the
  # HTML5 ad. For help on creating an HTML5 zip file, check out Google Web
  # Designer (https://www.google.com/webdesigner).
  html5_url = 'http://goo.gl/9Y7qI2'
  html5_data = AdsCommon::Http.get(html5_url, adwords.config)
  html5_data_base64 = Base64.encode64(html5_data)

  # Create a media bundle containing the zip file with all the HTML5
  # components.
  media_bundle = {
    :xsi_type => 'MediaBundle',
    :data => html5_data_base64,
    :entry_point => 'carousel/index.html',
    :type => 'MEDIA_BUNDLE'
  }

  # Create the template elements for the ad. You can refer to
  # https://developers.google.com/adwords/api/docs/appendix/templateads
  # for the list of available template fields.
  ad_data = {
    :unique_name => 'adData',
    :fields => [
      {
        :name => 'Custom_layout',
        :field_media => media_bundle,
        :type => 'MEDIA_BUNDLE'
      },
      {
        :name => 'layout',
        :field_text => 'Custom',
        :type => 'ENUM'
      }
    ]
  }

  html5_ad = {
    :xsi_type => 'TemplateAd',
    :name => 'Ad for HTML5',
    :template_id => 419,
    :final_urls => ['http://example.com/html5'],
    :display_url => 'www.example.com/html5',
    :dimensions => {
      :width => 300,
      :height => 250
    },
    :template_elements => [ad_data]
  }

  ad_group_ad = {
    :ad_group_id => ad_group_id,
    :ad => html5_ad,
    :status => 'PAUSED'
  }

  operation = {
    :operator => 'ADD',
    :operand => ad_group_ad
  }

  response = ad_group_ad_srv.mutate([operation])
  if response and !response.empty?
    response[:value].each do |ad_group_ad|
      puts "New HTML5 ad with ID %d and display url '%s' was added." %
          [ad_group_ad[:ad][:id], ad_group_ad[:ad][:display_url]]
    end
  else
    puts "No HTML5 ads were added."
  end
end

if __FILE__ == $0
  API_VERSION = :v201809

  begin
    # Ad group ID to add text ads to.
    ad_group_id = 'INSERT_AD_GROUP_ID_HERE'.to_i
    add_html5_ad(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

Add a multi-asset responsive display ad to an ad group

#!/usr/bin/env ruby
# Encoding: utf-8
#
# Copyright 2018 Google LLC
#
# 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 code example adds a responsive display ad (MultiAssetResponsiveDisplayAd)
# to an ad group. Image assets are uploaded using AssetService. To get
# ad groups, run get_ad_groups.rb.


require 'adwords_api'

def add_multi_asset_responsive_display_ad(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')

  ad_group_srv = adwords.service(:AdGroupAdService, API_VERSION)

  # Create the ad.
  ad = {
    :xsi_type => 'MultiAssetResponsiveDisplayAd',
    :headlines => [
      {
        # Text assets can be specified directly in the asset field
        # when creating the ad.
        :asset => {
          :xsi_type => 'TextAsset',
          :asset_text => 'Travel to Mars'
        }
      },
      {
        :asset => {
          :xsi_type => 'TextAsset',
          :asset_text => 'Travel to Jupiter'
        }
      },
      {
        :asset => {
          :xsi_type => 'TextAsset',
          :asset_text => 'Travel to Pluto'
        }
      }
    ],
    :descriptions => [
      {
        :asset => {
          :xsi_type => 'TextAsset',
          :asset_text => 'Visit the planet in a luxury spaceship.'
        }
      },
      {
        :asset => {
          :xsi_type => 'TextAsset',
          :asset_text => 'See the planet in style.'
        }
      }
    ],
    :business_name => 'Galactic Luxury Cruises',
    :long_headline => {
      :asset => {
        :xsi_type => 'TextAsset',
        :asset_text => 'Visit the planet in a luxury spaceship.'
      }
    },
    # This ad format does not allow the creation of an image asset by setting
    # the asset.imageData field. An image asset must first be created using the
    # AssetService, and asset.assetId must be populated when creating the ad.
    :marketing_images => [
      {
        :asset => {
          :xsi_type => 'ImageAsset',
          :asset_id => upload_image_asset('https://goo.gl/3b9Wfh')
        }
      }
    ],
    :square_marketing_images => [
      {
        :asset => {
          :xsi_type => 'ImageAsset',
          :asset_id => upload_image_asset('https://goo.gl/mtt54n')
        }
      }
    ],
    :final_urls => [
      'http://www.example.com'
    ],
    :call_to_action_text => 'Shop Now',
    :main_color => '#0000ff',
    :accent_color => '#ffff00',
    :allow_flexible_color => false,
    :format_setting => 'NON_NATIVE',
    :dynamic_settings_price_prefix => 'as low as',
    :dynamic_settings_promo_text => 'Free shipping!',
    :logo_images => [
      {
        :asset => {
          :xsi_type => 'ImageAsset',
          :asset_id => upload_image_asset('https://goo.gl/mtt54n')
        }
      }
    ],
  }

  # Create the ad group ad.
  ad_group_ad = {
    :xsi_type => 'AdGroupAd',
    :ad => ad,
    :ad_group_id => ad_group_id
  };

  # Create the operation.
  ad_group_operation = {:operator => 'ADD', :operand => ad_group_ad}

  begin
    # Make the mutate request.
    result = ad_group_srv.mutate([ad_group_operation])

    # Display the results
    if result[:value].length > 0
      result[:value].each do |entry|
        new_ad = entry[:ad]
        puts ('Responsive display ad v2 with ID "%s" ' +
            'and short headline "%s" was added.') %
            [new_ad[:id], new_ad[:long_headline][:asset][:asset_text]]
      end
    else
      puts 'No responsive display ad v2 were created.'
    end
  rescue Exception => e
    puts 'Failed to create responsive display ad v2.'
  end
end

def upload_image_asset(image_url, adwords_api_instance)

  asset_srv = adwords_api_instance.service(:AssetService, API_VERSION)

  # The image needs to be in a BASE64 encoded form
  image_data = AdsCommon::Http.get(image_url, adwords_api_instance.config)
  image_data_base64 = Base64.encode64(image_data)

  # Create the image asset
  image_asset = {
    :xsi_type => 'ImageAsset',
    # Optional: Provide a unique friendly name to identify your asset. If you
    # specify the assetName field, then both the asset name and the image being
    # uploaded should be unique, and should not match another ACTIVE asset in
    # this customer account.
    # :asset_name => 'Image asset %s' % (Time.new.to_f * 1000).to_i,
    :image_data => image_data_base64
  }

  # Create the operation.
  asset_operation = {:operator => 'ADD', :operand => image_asset}

  # Make the mutate request.
  result = asset_srv.mutate([asset_operation])

  # return the generated Id
  result[:value].first[:asset_id]
end

if __FILE__ == $0
  API_VERSION = :v201809

  begin
    ad_group_id = 'INSERT_AD_GROUP_ID_HERE'.to_i

    add_multi_asset_responsive_display_ad(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

Add a responsive display ad

#!/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 code example adds an image representing the ad using the MediaService and
# then adds a responsive display ad to a given ad group.
# To get ad groups, run basic_operations/get_ad_groups.rb.

require 'adwords_api'
require 'base64'

def add_responsive_display_ad(adwords, ad_group_id)
  ad_group_ad_srv = adwords.service(:AdGroupAdService, API_VERSION)

  # This ad format does not allow the creation of an image using the Image.data
  # field. An image must first be created using the MediaService, and
  # Image.mediaId must be populated when creating the ad.
  ad_image = upload_image(adwords, 'https://goo.gl/3b9Wfh')

  # Create the responsive display ad.
  responsive_display_ad = {
    :xsi_type => 'ResponsiveDisplayAd',
    # This ad format does not allow the creation of an image using the
    # Image.data field. An image must first be created using the MediaService,
    # and Image.mediaId must be populated when creating the ad.
    :marketing_image => {:media_id => ad_image[:media_id]},
    :short_headline => 'Travel',
    :long_headline => 'Traver the World',
    :description => 'Take to the air!',
    :business_name => 'Interplanetary Cruises',
    :final_urls => ['http://www.example.com/'],

    # Optional: Set call to action text.
    :call_to_action_text => 'Shop Now'

    # Whitelisted accounts only: Set color settings using hexadecimal values.
    # Set 'allow_flexible_color' to false if you want your ads to render by
    # always using your colors strictly.
    #:main_color => '#0000ff',
    #:accent_color => '#ffff00',
    #:allow_flexible_color => false,

    # Whitelisted accounts only: Set the format setting that the ad will be
    # served in.
    #:format_setting => 'NON_NATIVE'
  }

  # Optional: Create a square marketing image using MediaService, and set it to
  # the ad.
  square_image = upload_image(adwords, 'https://goo.gl/mtt54n')
  responsive_display_ad[:square_marketing_image] = {
    :media_id => square_image[:media_id]
  }

  # Optional: Set dynamic display ad settings, composed of landscape logo image,
  # promotion text, and price prefix.
  logo_image = upload_image(adwords, 'https://goo.gl/dEvQeF')
  responsive_display_ad[:dynamic_display_ad_settings] = {
    :landscape_logo_image => {:media_id => logo_image[:media_id]},
    :price_prefix => 'as low as',
    :promo_text => 'Free shipping!'
  }

  # Create an ad group ad for the responsive display ad.
  responsive_display_ad_group_ad = {
    :ad_group_id => ad_group_id,
    :ad => responsive_display_ad,
    # Additional propertires (non-required).
    :status => 'PAUSED'
  }

  # Create operation.
  responsive_display_ad_group_ad_operations = {
    :operator => 'ADD',
    :operand => responsive_display_ad_group_ad
  }

  # Add the responsive display ad.
  result = ad_group_ad_srv.mutate([responsive_display_ad_group_ad_operations])

  # Display results.
  if result && result[:value]
    result[:value].each do |ad_group_ad|
      puts ("New responsive display ad with id %d and short headline '%s' was" +
          "added.") % [ad_group_ad[:ad][:id], ad_group_ad[:ad][:short_headline]]
    end
  else
    puts 'No responsive display ads were added.'
  end
end

def upload_image(adwords, image_url)
  media_srv = adwords.service(:MediaService, API_VERSION)

  # Create an image.
  raw_image_data = AdsCommon::Http.get(image_url, adwords.config)
  image = {
    :xsi_type => 'Image',
    :data => Base64.encode64(raw_image_data),
    :type => 'IMAGE'
  }

  # Upload the image.
  response = media_srv.upload([image])
  if response and !response.empty?
    return response.first
  else
    raise StandardError, 'Could not upload image, aborting.'
  end
end

if __FILE__ == $0
  API_VERSION = :v201809

  begin
    # 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')

    # Ad group ID to add text ads to.
    ad_group_id = 'INSERT_AD_GROUP_ID_HERE'.to_i
    add_responsive_display_ad(adwords, 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

Add a Shopping dynamic remarketing campaign

#!/usr/bin/env ruby
# Encoding: utf-8
#
# Copyright:: Copyright 2018, 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 adds a Shopping dynamic remarketing campaign for the Display
# Network via the following steps:
# * Creates a new Display Network campaign.
# * Links the campaign with Merchant Center.
# * Links the user list to the ad group.
# * Creates a responsive display ad to render the dynamic text.

require 'adwords_api'

def add_shopping_dynamic_remarketing_campaign(merchant_id, budget_id,
    user_list_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')

  campaign = create_campaign(adwords, merchant_id, budget_id)
  puts "Campaign with name '%s' and ID %d was added." %
      [campaign[:name], campaign[:id]]

  ad_group = create_ad_group(adwords, campaign)
  puts "Ad group with name '%s' and ID :%d was added." %
      [ad_group[:name], ad_group[:id]]

  ad_group_ad = create_ad(adwords, ad_group)
  puts "Responsive display ad with ID %d was added." % ad_group_ad[:ad][:id]

  attach_user_list(adwords, ad_group, user_list_id)
  puts "User list with ID %d was attached to ad group with ID %d." %
      [user_list_id, ad_group[:id]]
end

def create_campaign(adwords, merchant_id, budget_id)
  campaign_srv = adwords.service(:CampaignService, API_VERSION)

  campaign = {
    :name => 'Shopping campaign #%d' % (Time.new.to_f * 1000).to_i,
    # Dynamic remarketing campaigns are only available on the Google Display
    # Network
    :advertising_channel_type => 'DISPLAY',
    :status => 'PAUSED',
    :budget => {
      :budget_id => budget_id
    },
    # This example uses a Manual CPC bidding strategy, but you should select the
    # strategy that best aligns with your sales goals. More details here:
    # https://support.google.com/adwords/answer/2472725
    :bidding_strategy_configuration => {
      :bidding_strategy_type => 'MANUAL_CPC'
    },
    :settings => [{
      :xsi_type => 'ShoppingSetting',
      # Campaigns with numerically higher priorities take precedence over those
      # with lower priorities.
      :campaign_priority => 0,
      # Set the Merchant Center account ID from which to source products.
      :merchant_id => merchant_id,
      # Display Network campaigns do not support partition by country. The only
      # supported value is "ZZ". This signals that products from all countries
      # are available in the campaign. The actual products which serve are based
      # on the products tagged in the user list entry.
      :sales_country => 'ZZ',
      # Optional: Enable local inventory ads (items for sale in physical
      # stores.)
      :enable_local => true
    }]
  }

  operation = {
    :operator => 'ADD',
    :operand => campaign
  }

  result = campaign_srv.mutate([operation])
  return result[:value].first
end

def create_ad_group(adwords, campaign)
  ad_group_srv = adwords.service(:AdGroupService, API_VERSION)

  ad_group = {
    :name => 'Dynamic remarketing ad group',
    :campaign_id => campaign[:id],
    :status => 'ENABLED'
  }

  operation = {
    :operator => 'ADD',
    :operand => ad_group
  }

  result = ad_group_srv.mutate([operation])
  return result[:value].first
end

def create_ad(adwords, ad_group)
  ad_srv = adwords.service(:AdGroupAdService, API_VERSION)

  optional_media_id = upload_image(adwords, 'https://goo.gl/mtt54n')

  ad = {
    :xsi_type => 'ResponsiveDisplayAd',
    # This ad format does not allow the creation of an image using the
    # Image.data field. An image must first be created using the MediaService,
    # and Image.mediaId must be populated when creating the ad.
    :marketing_image => {
      :xsi_type => 'Image',
      :media_id => upload_image(adwords, 'https://goo.gl/3b9Wfh')
    },
    :short_headline => 'Travel',
    :long_headline => 'Travel the World',
    :description => 'Take to the air!',
    :business_name => 'Interplanetary Cruises',
    :final_urls => ['http://www.example.com'],
    # Optional: Call to action text.
    # Valid texts: https://support.google.com/adwords/answer/7005917
    :call_to_action_text => 'Apply Now',
    # Optional: Set dynamic display ad settings, composed of landscape logo
    # image, promotion text, and price prefix.
    :dynamic_display_ad_settings => create_dynamic_display_ad_settings(adwords),
    # Optional: Create a logo image and set it to the ad.
    :logo_image => {
      :xsi_type => 'Image',
      :media_id => optional_media_id
    },
    # Optional: Create a square marketing image and set it to the ad.
    :square_marketing_image => {
      :xsi_type => 'Image',
      :media_id => optional_media_id
    },
    # Whitelisted accounts only: Set color settings using hexadecimal values.
    # Set allowFlexibleColor to false if you want your ads to render by always
    # using your colors strictly.
    # :main_color => '#0000ff',
    # :accent_color => '#ffff00',
    # :allow_flexible_color => false,
    # Whitelisted accounts only: Set the format setting that the ad will be
    # served in.
    # :format_setting => 'NON_NATIVE'
  }

  ad_group_ad = {
    :ad => ad,
    :ad_group_id => ad_group[:id]
  }

  operation = {
    :operator => 'ADD',
    :operand => ad_group_ad
  }

  result = ad_srv.mutate([operation])
  return result[:value].first
end

def attach_user_list(adwords, ad_group, user_list_id)
  ad_group_criterion_srv = adwords.service(:AdGroupCriterionService,
      API_VERSION)

  user_list = {
    :xsi_type => 'CriterionUserList',
    :user_list_id => user_list_id
  }

  ad_group_criterion = {
    :xsi_type => 'BiddableAdGroupCriterion',
    :criterion => user_list,
    :ad_group_id => ad_group[:id]
  }

  operation = {
    :operator => 'ADD',
    :operand => ad_group_criterion
  }

  result = ad_group_criterion_srv.mutate([operation])
end

def upload_image(adwords, url)
  media_srv = adwords.service(:MediaService, API_VERSION)

  image_data = AdsCommon::Http.get(url, adwords.config)
  base64_image_data = Base64.encode64(image_data)

  image = {
    :xsi_type => 'Image',
    :data => base64_image_data,
    :type => 'IMAGE'
  }

  response = media_srv.upload([image])
  return response.first[:media_id]
end

def create_dynamic_display_ad_settings(adwords)
  logo = {
    :xsi_type => 'Image',
    :media_id => upload_image(adwords, 'https://goo.gl/dEvQeF')
  }

  dynamic_settings = {
    :landscape_logo_image => logo,
    :price_prefix => 'as low as',
    :promo_text => 'Free shipping!'
  }

  return dynamic_settings
end

if __FILE__ == $0
  API_VERSION = :v201809

  begin
    merchant_id = 'INSERT_MERCHANT_CENTER_ID_HERE'.to_i
    budget_id = 'INSERT_BUDGET_ID_HERE'.to_i
    user_list_id = 'INSERT_USER_LIST_ID_HERE'.to_i

    add_shopping_dynamic_remarketing_campaign(merchant_id, budget_id,
        user_list_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

Add a universal app 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 adds a universal app campaign.
#
# To get campaigns, run get_campaigns.rb. To upload image assets for this
# campaign, run upload_image.rb.

require 'adwords_api'
require 'date'

def add_universal_app_campaigns()
  # 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')

  campaign_srv = adwords.service(:CampaignService, API_VERSION)
  budget_srv = adwords.service(:BudgetService, API_VERSION)

  # Create a budget.
  budget = {
    :name => 'Interplanetary budget #%d' % (Time.new.to_f * 1000).to_i,
    :amount => {:micro_amount => 50000000},
    :delivery_method => 'STANDARD',
    # Universal app campaigns don't support shared budgets.
    :is_explicitly_shared => false
  }
  budget_operation = {:operator => 'ADD', :operand => budget}

  # Add the budget.
  return_budget = budget_srv.mutate([budget_operation])
  budget_id = return_budget[:value].first[:budget_id]

  # Create campaigns.
  universal_app_campaign = {
    :name => "Interplanetary Cruise #%d" % (Time.new.to_f * 1000).to_i,
    # Recommendation: Set the campaign to PAUSED when creating it to stop the
    # ads from immediately serving. Set it to ENABLED once you've added
    # targeting and the ads are ready to serve.
    :status => 'PAUSED',
    # Set the advertising channel and subchannel types for universal app
    # campaigns.
    :advertising_channel_type => 'MULTI_CHANNEL',
    :advertising_channel_sub_type => 'UNIVERSAL_APP_CAMPAIGN',
    # Set the campaign's bidding strategy. Universal app campaigns only support
    # the TARGET_CPA bidding strategy.
    :bidding_strategy_configuration => {
      :bidding_scheme => {
        :xsi_type => 'TargetCpaBiddingScheme',
        :target_cpa => {
          :micro_amount => 1000000
        }
      }
    },
    # Budget (required) - note only the budget ID is required.
    :budget => {:budget_id => budget_id},
    # Optional fields:
    :start_date =>
        DateTime.parse((Date.today + 1).to_s).strftime('%Y%m%d'),
    :end_date =>
        DateTime.parse((Date.today + 365).to_s).strftime('%Y%m%d')
  }

  universal_app_setting = {
    # Set the campaign's assets and ad text ideas. These values will be used to
    # generate ads.
    :xsi_type => 'UniversalAppCampaignSetting',
    :app_id => 'com.labpixies.colordrips',
    :app_vendor => 'VENDOR_GOOGLE_MARKET',
    :description1 => 'A cool puzzle game',
    :description2 => 'Remove connected blocks',
    :description3 => '3 difficulty levels',
    :description4 => '4 colorful fun skins'
    # Optional: You can set up to 20 image assets for your campaign. See
    # upload_image.rb for an example on how to upload images.
    #
    # :image_media_ids => [INSERT_IMGAGE_MEDIA_ID(s)_HERE]
  }

  # Optimize this campaign for getting new users for your app.
  universal_app_setting[:universal_app_bidding_strategy_goal_type] =
      'OPTIMIZE_FOR_INSTALL_CONVERSION_VOLUME'

  # Optional: If you select OPTIMIZE_FOR_IN_APP_CONVERSION_VOLUME goal type,
  # then also specify your in-app conversion types so AdWords can focus your
  # campaign on people who are most likely to complete the corresponding in-app
  # actions.
  # Conversion type IDs can be retrieved using ConversionTrackerService.get.
  #
  # universal_app_campaign[:selective_optimization] = {
  #   :conversion_type_ids => [INSERT_CONVERSION_TYPE_ID(s)_HERE]
  # }

  # Optional: Set the campaign settings for Advanced location options.
  geo_setting = {
    :xsi_type => 'GeoTargetTypeSetting',
    :positive_geo_target_type => 'DONT_CARE',
    :negative_geo_target_type => 'DONT_CARE'
  }

  universal_app_campaign[:settings] = [
    universal_app_setting,
    geo_setting
  ]

  # Construct the operation and add the campaign.
  operations = [{
    :operator => 'ADD',
    :operand => universal_app_campaign
  }]

  campaigns = campaign_srv.mutate(operations)[:value]

  if campaigns
    campaigns.each do |campaign|
      puts "Universal app campaign with name '%s' and ID %d was added." %
          [campaign[:name], campaign[:id]]
      set_campaign_targeting_criteria(adwords, campaign)
    end
  else
    raise new StandardError, 'No universal app campaigns were added.'
  end
end

def set_campaign_targeting_criteria(adwords, campaign)
  campaign_criterion_service =
      adwords.service(:CampaignCriterionService, API_VERSION)

  criteria = [
    {
      :xsi_type => 'Location',
      :id => 21137 # California
    },
    {
      :xsi_type => 'Location',
      :id => 2484 # Mexico
    },
    {
      :xsi_type => 'Language',
      :id => 1000 # English
    },
    {
      :xsi_type => 'Language',
      :id => 1003 # Spanish
    }
  ]

  operations = criteria.map do |criterion|
    {
      :operator => 'ADD',
      :operand => {
        :campaign_id => campaign[:id],
        :criterion => criterion
      }
    }
  end

  response = campaign_criterion_service.mutate(operations)

  if response and response[:value]
    # Display the added campaign targets.
    response[:value].each do |criterion|
      puts 'Campaign criteria of type "%s" and id %d was added.' % [
          criterion[:criterion][:criterion_type],
          criterion[:criterion][:id]
      ]
    end
  end
end

if __FILE__ == $0
  API_VERSION = :v201809

  begin
    add_universal_app_campaigns()

  # 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

Create a negative broad match keywords list and attach it to a campaign

#!/usr/bin/env ruby
# Encoding: utf-8
#
# Copyright:: Copyright 2015, 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 creates a shared list of negative broad match keywords, it then
# attaches them to a campaign.

require 'adwords_api'
require 'date'

def create_and_attach_shared_keyword_set(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')

  shared_set_srv = adwords.service(:SharedSetService, API_VERSION)
  shared_criterion_srv = adwords.service(:SharedCriterionService, API_VERSION)
  campaign_shared_set_srv =
      adwords.service(:CampaignSharedSetService, API_VERSION)

  # Keywords to create a shared set of.
  keywords = ['mars cruise', 'mars hotels']

  # Create shared negative keyword set.
  shared_set = {
    :name => 'API Negative keyword list - %d' % (Time.new.to_f * 1000).to_i,
    :type => 'NEGATIVE_KEYWORDS'
  }

  # Add shared set.
  response = shared_set_srv.mutate([
      {:operator => 'ADD', :operand => shared_set}
  ])

  if response and response[:value]
    shared_set = response[:value].first
    shared_set_id = shared_set[:shared_set_id]
    puts "Shared set with ID %d and name '%s' was successfully added." %
        [shared_set_id, shared_set[:name]]
  else
    raise StandardError, 'No shared set was added'
  end

  # Add negative keywords to shared set.
  shared_criteria = keywords.map do |keyword|
    {
      :criterion => {
        :xsi_type => 'Keyword',
        :text => keyword,
        :match_type => 'BROAD'
      },
      :negative => true,
      :shared_set_id => shared_set_id
    }
  end

  operations = shared_criteria.map do |criterion|
    {:operator => 'ADD', :operand => criterion}
  end

  response = shared_criterion_srv.mutate(operations)
  if response and response[:value]
    response[:value].each do |shared_criterion|
      puts "Added shared criterion ID %d '%s' to shared set with ID %d." %
          [shared_criterion[:criterion][:id],
           shared_criterion[:criterion][:text],
           shared_criterion[:shared_set_id]]
    end
  else
    raise StandardError, 'No shared keyword was added'
  end

  # Attach the articles to the campaign.
  campaign_set = {
    :campaign_id => campaign_id,
    :shared_set_id => shared_set_id
  }

  response = campaign_shared_set_srv.mutate([
    {:operator => 'ADD', :operand => campaign_set}
  ])
  if response and response[:value]
    campaign_shared_set = response[:value].first
    puts 'Shared set ID %d was attached to campaign ID %d' %
        [campaign_shared_set[:shared_set_id], campaign_shared_set[:campaign_id]]
  else
    raise StandardError, 'No campaign shared set was added'
  end
end

if __FILE__ == $0
  API_VERSION = :v201809

  begin
    # Campaign ID to attach shared keyword set to.
    campaign_id = 'INSERT_CAMPAIGN_ID_HERE'.to_i

    create_and_attach_shared_keyword_set(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

Find and remove shared sets and shared set criteria

#!/usr/bin/env ruby
# Encoding: utf-8
#
# Copyright:: Copyright 2015, 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 demonstrates how to find shared sets, shared set criterions and
# how to remove them.

require 'adwords_api'

def find_and_remove_criteria_from_shared_set(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')

  campaign_shared_set_srv =
      adwords.service(:CampaignSharedSetService, API_VERSION)
  shared_criterion_srv = adwords.service(:SharedCriterionService, API_VERSION)

  shared_set_ids = []
  criterion_ids = []

  # First, retrieve all shared sets associated with the campaign.

  # Create selector for shared sets to:
  # - filter by campaign ID,
  # - filter by shared set type.
  selector = {
    :fields => ['SharedSetId', 'CampaignId', 'SharedSetName', 'SharedSetType',
        'Status'],
    :predicates => [
      {:field => 'CampaignId', :operator => 'EQUALS', :values => [campaign_id]},
      {:field => 'SharedSetType', :operator => 'IN', :values =>
          ['NEGATIVE_KEYWORDS', 'NEGATIVE_PLACEMENTS']}
    ],
    :paging => {
      :start_index => 0,
      :number_results => PAGE_SIZE
    }
  }

  # Set initial values.
  offset, page = 0, {}

  begin
    page = campaign_shared_set_srv.get(selector)
    if page[:entries]
      page[:entries].each do |shared_set|
        puts "Campaign shared set ID %d and name '%s'" %
            [shared_set[:shared_set_id], shared_set[:shared_set_name]]
        shared_set_ids << shared_set[:shared_set_id]
      end
      # Increment values to request the next page.
      offset += PAGE_SIZE
      selector[:paging][:start_index] = offset
    end
  end while page[:total_num_entries] > offset

  # Next, Retrieve criterion IDs for all found shared sets.
  selector = {
    :fields => ['SharedSetId', 'Id', 'KeywordText', 'KeywordMatchType',
        'PlacementUrl'],
    :predicates => [
      {:field => 'SharedSetId', :operator => 'IN', :values => shared_set_ids}
    ],
    :paging => {
      :start_index => 0,
      :number_results => PAGE_SIZE
    }
  }

  # Set initial values.
  offset, page = 0, {}

  begin
    page = shared_criterion_srv.get(selector)
    if page[:entries]
      page[:entries].each do |shared_criterion|
        if shared_criterion[:criterion][:type] == 'KEYWORD'
          puts "Shared negative keyword with ID %d and text '%s' was found." %
            [shared_criterion[:criterion][:id],
             shared_criterion[:criterion][:text]]
        elsif shared_criterion[:criterion][:type] == 'PLACEMENT'
          puts "Shared negative placement with ID %d and url '%s' was found." %
            [shared_criterion[:criterion][:id],
             shared_criterion[:criterion][:url]]
        else
          puts 'Shared criterion with ID %d was found.' %
            [shared_criterion[:criterion][:id]]
        end
        criterion_ids << {
           :shared_set_id => shared_criterion[:shared_set_id],
           :criterion_id => shared_criterion[:criterion][:id]
        }
      end
      # Increment values to request the next page.
      offset += PAGE_SIZE
      selector[:paging][:start_index] = offset
    end
  end while page[:total_num_entries] > offset

  # Finally, remove the criteria.
  operations = criterion_ids.map do |criterion|
    {
      :operator => 'REMOVE',
      :operand => {
        :criterion => {:id => criterion[:criterion_id]},
        :shared_set_id => criterion[:shared_set_id]
      }
    }
  end

  response = shared_criterion_srv.mutate(operations)
  if response and response[:value]
    response[:value].each do |criterion|
      puts "Criterion ID %d was successfully removed from shared set ID %d." %
          [criterion[:criterion][:id], criterion[:shared_set_id]]
    end
  else
    puts 'No shared criteria were removed.'
  end
end

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

  begin
    # ID of a campaign to remove shared criteria from.
    campaign_id = 'INSERT_CAMPAIGN_ID_HERE'.to_i

    find_and_remove_criteria_from_shared_set(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 ad group level bid modifiers

#!/usr/bin/env ruby
# Encoding: utf-8
#
# Copyright:: Copyright 2013, 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 illustrates how to retrieve ad group level bid modifiers for a
# campaign.

require 'adwords_api'

def get_ad_group_bid_modifiers(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')

  bid_modifier_srv = adwords.service(:AdGroupBidModifierService, API_VERSION)

  # Get all ad group bid modifiers for the campaign.
  selector = {
    :fields => ['CampaignId', 'AdGroupId', 'Id', 'BidModifier'],
    :predicates => [
      {:field => 'CampaignId', :operator => 'EQUALS', :values => [campaign_id]}
    ],
    :paging => {
      :start_index => 0,
      :number_results => PAGE_SIZE
    }
  }

  # Set initial values.
  offset, page = 0, {}

  begin
    page = bid_modifier_srv.get(selector)
    if page[:entries]
      page[:entries].each do |modifier|
        value = modifier[:bid_modifier] || 'unset'
        puts ('Campaign ID %d, AdGroup ID %d, Criterion ID %d has ad group ' +
           'level modifier: %s') %
           [modifier[:campaign_id], modifier[:ad_group_id],
            modifier[:criterion][:id], value]
      end
      # Increment values to request the next page.
      offset += PAGE_SIZE
      selector[:paging][:start_index] = offset
    else
      puts 'No ad group level bid overrides returned.'
    end
  end while page[:total_num_entries] > offset
end

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

  begin
    # ID of a campaign to pull overrides for.
    campaign_id = 'INSERT_CAMPAIGN_ID_HERE'.to_i

    get_ad_group_bid_modifiers(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

Add a portfolio bidding strategy to a campaign

#!/usr/bin/env ruby
# Encoding: utf-8
#
# Copyright:: Copyright 2013, 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 adds a Portfolio Bidding Strategy and uses it to construct a
# campaign.

require 'adwords_api'
require 'date'

def use_portfolio_bidding_strategy()
  # 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')

  budget_srv = adwords.service(:BudgetService, API_VERSION)
  bidding_srv = adwords.service(:BiddingStrategyService, API_VERSION)
  campaign_srv = adwords.service(:CampaignService, API_VERSION)

  # Create a budget, which can be shared by multiple campaigns.
  budget = {
    :name => 'Interplanetary budget #%d' % (Time.new.to_f * 1000).to_i,
    :amount => {:micro_amount => 50000000},
    :delivery_method => 'STANDARD',
    :is_explicitly_shared => true
  }
  return_budget = budget_srv.mutate([
    {:operator => 'ADD', :operand => budget}])
  budget_id = return_budget[:value].first[:budget_id]

  # Create a portfolio bidding strategy.
  portfolio_bidding_strategy = {
    :name => 'Maximize Clicks #%d' % (Time.new.to_f * 1000).to_i,
    :bidding_scheme => {
      :xsi_type => 'TargetSpendBiddingScheme',
      # Optionally set additional bidding scheme parameters.
      :bid_ceiling => {:micro_amount => 20000000},
      :spend_target => {:micro_amount => 40000000}
    }
  }
  return_strategy = bidding_srv.mutate([
    {:operator => 'ADD', :operand => portfolio_bidding_strategy}])

  bidding_strategy = return_strategy[:value].first
  puts ("Portfolio bidding strategy with name '%s' and ID %d of type '%s'" +
      ' was created') %
      [bidding_strategy[:name], bidding_strategy[:id], bidding_strategy[:type]]

  # Create campaigns.
  campaigns = [
    {
      :name => "Interplanetary Cruise #%d" % (Time.new.to_f * 1000).to_i,
      # Recommendation: Set the campaign to PAUSED when creating it to stop the
      # ads from immediately serving. Set to ENABLED once you've added
      # targeting and the ads are ready to serve.
      :status => 'PAUSED',
      :bidding_strategy_configuration => {
        :bidding_strategy_id => bidding_strategy[:id]
      },
      # Budget (required) - note only the budget ID is required.
      :budget => {:budget_id => budget_id},
      :advertising_channel_type => 'SEARCH',
      :network_setting => {
        :target_google_search => true,
        :target_search_network => true,
        :target_content_network => true
      }
    },
    {
      :name => "Interplanetary Cruise banner #%d" % (Time.new.to_f * 1000).to_i,
      :status => 'PAUSED',
      :bidding_strategy_configuration => {
        :bidding_strategy_id => bidding_strategy[:id]
      },
      :budget => {:budget_id => budget_id},
      :advertising_channel_type => 'DISPLAY',
      :network_setting => {
        :target_google_search => false,
        :target_search_network => false,
        :target_content_network => true
      }
    }
  ]

  # Prepare for adding campaign.
  operations = campaigns.map do |campaign|
    {:operator => 'ADD', :operand => campaign}
  end

  # Add campaign.
  response = campaign_srv.mutate(operations)
  if response and response[:value]
    response[:value].each do |campaign|
      puts "Campaign with name '%s' and ID %d was added." %
          [campaign[:name], campaign[:id]]
    end
  else
    raise new StandardError, 'No campaigns were added.'
  end
end

if __FILE__ == $0
  API_VERSION = :v201809

  begin
    use_portfolio_bidding_strategy()

  # 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

Send feedback about...

AdWords API
AdWords API
Need help? Visit our support page.